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,
appName: app.getName(),
execPath: process.execPath,
programFiles: process.env.ProgramFiles,
programFilesX86: process.env['ProgramFiles(x86)'],
});
}
@@ -140,6 +142,7 @@ function showWindowsInstallNoticeIfNeeded() {
hasShownInstallNotice = true;
const language = resolveInstallNoticeLanguage({
configLanguage: startupConfig?.language,
preferredLanguages: app.getPreferredSystemLanguages(),
locale: app.getLocale(),
});
const text = getInstallNoticeText(language);
+114 -17
View File
@@ -48,19 +48,12 @@ const installNoticeText: Record<
},
};
export function resolveInstallNoticeLanguage({
configLanguage,
locale,
}: {
configLanguage?: string | null;
locale?: string | null;
}): InstallNoticeLanguage {
const rawLanguage = configLanguage || locale;
if (!rawLanguage) {
return 'en';
}
const normalized = rawLanguage.toLowerCase();
/**
* Match a locale tag to a supported InstallNoticeLanguage.
* Returns the matched language or null if no match found.
*/
function matchLocaleTag(tag: string): InstallNoticeLanguage | null {
const normalized = tag.toLowerCase();
if (normalized.startsWith('zh')) {
return 'zh-CN';
}
@@ -77,6 +70,51 @@ export function resolveInstallNoticeLanguage({
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';
}
@@ -100,6 +138,10 @@ function normalizeWindowsInstallDirName(appName: string) {
.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({
platform,
localAppData,
@@ -122,30 +164,85 @@ export function getExpectedInstallRoot({
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({
platform,
isPackaged,
localAppData,
appName,
execPath,
programFiles,
programFilesX86,
}: {
platform: string;
isPackaged: boolean;
localAppData?: string | null;
appName: string;
execPath: string;
programFiles?: string | null;
programFilesX86?: string | null;
}) {
if (platform !== 'win32' || !isPackaged) {
return true;
}
const expectedRoot = getExpectedInstallRoot({ platform, localAppData, appName });
if (!expectedRoot) {
const roots = getAllExpectedInstallRoots({
platform,
localAppData,
appName,
programFiles,
programFilesX86,
});
if (roots.length === 0) {
return true;
}
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));
}