fix: install notice showing in Chinese and not recognizing MSI install path

- Use app.getPreferredSystemLanguages() for reliable OS language detection
  instead of relying solely on app.getLocale() which returns Chromium's
  internal locale (was returning zh-CN on pt-BR systems)
- Support both Squirrel (%LOCALAPPDATA%) and WiX/MSI (Program Files)
  install directories when checking if app runs from expected location
- Extract matchLocaleTag helper for cleaner language resolution logic
- Add getAllExpectedInstallRoots to enumerate all valid install paths
This commit is contained in:
Gemini Nexus Bot committed 2026-04-29 12:04:32 -03:00
1 parent 4ce8d266ab
commit 0753bbdce6
2 files changed
+117 -17

No files matched your search

+3
View File
@@ -116,6 +116,8 @@ function isRunningFromExpectedInstallDir() {
localAppData: process.env.LOCALAPPDATA, localAppData: process.env.LOCALAPPDATA,
appName: app.getName(), appName: app.getName(),
execPath: process.execPath, execPath: process.execPath,
programFiles: process.env.ProgramFiles,
programFilesX86: process.env['ProgramFiles(x86)'],
}); });
} }
@@ -140,6 +142,7 @@ function showWindowsInstallNoticeIfNeeded() {
hasShownInstallNotice = true; hasShownInstallNotice = true;
const language = resolveInstallNoticeLanguage({ const language = resolveInstallNoticeLanguage({
configLanguage: startupConfig?.language, configLanguage: startupConfig?.language,
preferredLanguages: app.getPreferredSystemLanguages(),
locale: app.getLocale(), locale: app.getLocale(),
}); });
const text = getInstallNoticeText(language); const text = getInstallNoticeText(language);
+114 -17
View File
@@ -48,19 +48,12 @@ const installNoticeText: Record<
}, },
}; };
export function resolveInstallNoticeLanguage({ /**
configLanguage, * Match a locale tag to a supported InstallNoticeLanguage.
locale, * Returns the matched language or null if no match found.
}: { */
configLanguage?: string | null; function matchLocaleTag(tag: string): InstallNoticeLanguage | null {
locale?: string | null; const normalized = tag.toLowerCase();
}): InstallNoticeLanguage {
const rawLanguage = configLanguage || locale;
if (!rawLanguage) {
return 'en';
}
const normalized = rawLanguage.toLowerCase();
if (normalized.startsWith('zh')) { if (normalized.startsWith('zh')) {
return 'zh-CN'; return 'zh-CN';
} }
@@ -77,6 +70,51 @@ export function resolveInstallNoticeLanguage({
return 'pt-BR'; return 'pt-BR';
} }
if (normalized.startsWith('en')) {
return 'en';
}
return null;
}
/**
* Resolve the language to use for the install notice dialog.
*
* Priority: configLanguage > preferredLanguages > locale > 'en'
*
* `preferredLanguages` should come from `app.getPreferredSystemLanguages()`
* which returns the OS-level preferred language list and is more reliable
* than `app.getLocale()` which can return Chromium's internal locale.
*/
export function resolveInstallNoticeLanguage({
configLanguage,
preferredLanguages,
locale,
}: {
configLanguage?: string | null;
preferredLanguages?: string[];
locale?: string | null;
}): InstallNoticeLanguage {
// 1. Explicit user config takes highest priority
if (configLanguage) {
return matchLocaleTag(configLanguage) ?? 'en';
}
// 2. OS preferred languages (most reliable source)
if (preferredLanguages && preferredLanguages.length > 0) {
for (const lang of preferredLanguages) {
const matched = matchLocaleTag(lang);
if (matched) {
return matched;
}
}
}
// 3. Chromium locale (fallback)
if (locale) {
return matchLocaleTag(locale) ?? 'en';
}
return 'en'; return 'en';
} }
@@ -100,6 +138,10 @@ function normalizeWindowsInstallDirName(appName: string) {
.toLowerCase(); .toLowerCase();
} }
/**
* Return the primary expected install root (Squirrel per-user location).
* Used for the dialog detail text to tell the user where to find the app.
*/
export function getExpectedInstallRoot({ export function getExpectedInstallRoot({
platform, platform,
localAppData, localAppData,
@@ -122,30 +164,85 @@ export function getExpectedInstallRoot({
return pathApi.resolve(pathApi.join(localAppData, installDirName)); return pathApi.resolve(pathApi.join(localAppData, installDirName));
} }
/**
* Return all valid install root directories for the app on Windows.
* Includes both Squirrel (%LOCALAPPDATA%) and WiX/MSI (Program Files) paths.
*/
export function getAllExpectedInstallRoots({
platform,
localAppData,
appName,
programFiles,
programFilesX86,
}: {
platform: string;
localAppData?: string | null;
appName: string;
programFiles?: string | null;
programFilesX86?: string | null;
}): string[] {
if (platform !== 'win32') {
return [];
}
const pathApi = getPathApi(platform);
const roots: string[] = [];
// Squirrel per-user install: %LOCALAPPDATA%\gemini_nexus
if (localAppData) {
const installDirName = normalizeWindowsInstallDirName(appName);
roots.push(pathApi.resolve(pathApi.join(localAppData, installDirName)));
}
// WiX/MSI system-wide install: C:\Program Files\Gemini Nexus
// and C:\Program Files (x86)\Gemini Nexus
const displayName = appName.trim();
if (programFiles) {
roots.push(pathApi.resolve(pathApi.join(programFiles, displayName)));
}
if (programFilesX86) {
roots.push(pathApi.resolve(pathApi.join(programFilesX86, displayName)));
}
return roots;
}
export function isRunningFromExpectedInstallDir({ export function isRunningFromExpectedInstallDir({
platform, platform,
isPackaged, isPackaged,
localAppData, localAppData,
appName, appName,
execPath, execPath,
programFiles,
programFilesX86,
}: { }: {
platform: string; platform: string;
isPackaged: boolean; isPackaged: boolean;
localAppData?: string | null; localAppData?: string | null;
appName: string; appName: string;
execPath: string; execPath: string;
programFiles?: string | null;
programFilesX86?: string | null;
}) { }) {
if (platform !== 'win32' || !isPackaged) { if (platform !== 'win32' || !isPackaged) {
return true; return true;
} }
const expectedRoot = getExpectedInstallRoot({ platform, localAppData, appName }); const roots = getAllExpectedInstallRoots({
if (!expectedRoot) { platform,
localAppData,
appName,
programFiles,
programFilesX86,
});
if (roots.length === 0) {
return true; return true;
} }
const pathApi = getPathApi(platform); const pathApi = getPathApi(platform);
const normalizedExecPath = pathApi.resolve(execPath); const normalizedExecPath = pathApi.resolve(execPath).toLowerCase();
return normalizedExecPath.toLowerCase().startsWith(expectedRoot.toLowerCase() + pathApi.sep); return roots.some((root) => normalizedExecPath.startsWith(root.toLowerCase() + pathApi.sep));
} }