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:
1 parent
4ce8d266ab
commit
0753bbdce6
2 files changed
+117
-17
No files matched your search
@@ -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
@@ -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));
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user