From da0a741a36b5fcc50c81fc6db72cf396271d3ca0 Mon Sep 17 00:00:00 2001 From: Jamie Kyle <113370520+jamiebuilds-signal@users.noreply.github.com> Date: Tue, 24 Jan 2023 16:54:46 -0800 Subject: [PATCH] Use preferred system locales and add Farsi font stack --- app/locale.ts | 49 ++++++----- app/main.ts | 88 +++++++++++++------ sticker-creator/root.tsx | 5 +- stylesheets/_mixins.scss | 6 ++ ts/background.ts | 2 +- .../ChatSessionRefreshedNotification.tsx | 2 +- ts/models/messages.ts | 4 +- ts/services/profiles.ts | 4 +- ts/test-node/app/menu_test.ts | 5 +- ts/types/RendererConfig.ts | 3 +- ts/window.d.ts | 3 +- ts/windows/context.ts | 16 ++-- ts/windows/main/phase1-ipc.ts | 3 +- ts/windows/main/phase2-dependencies.ts | 6 +- 14 files changed, 124 insertions(+), 72 deletions(-) diff --git a/app/locale.ts b/app/locale.ts index e47a50e7cc5d..b6e0c6a8b54a 100644 --- a/app/locale.ts +++ b/app/locale.ts @@ -49,13 +49,13 @@ function finalize( } export function load({ - appLocale, + preferredSystemLocales, logger, }: { - appLocale: string; - logger: Pick; + preferredSystemLocales: Array; + logger: Pick; }): LocaleType { - if (!appLocale) { + if (preferredSystemLocales.length === 0) { throw new TypeError('`appLocale` is required'); } @@ -68,27 +68,28 @@ export function load({ const english = getLocaleMessages('en'); - // Load locale - if we can't load messages for the current locale, we - // default to 'en' - // - // possible locales: - // https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc - const languageOnly = removeRegion(appLocale); + for (const locale of preferredSystemLocales) { + try { + logger.info(`Loading preferred system locale: '${locale}'`); + return finalize(getLocaleMessages(locale), english, locale); + } catch (e) { + logger.warn( + `Problem loading messages for locale '${locale}', ${e.toString()}` + ); + } - try { - return finalize(getLocaleMessages(appLocale), english, appLocale); - } catch (e) { - logger.warn(`Problem loading messages for locale ${appLocale}`); + const languageOnly = removeRegion(locale); + try { + logger.warn(`Falling back to parent language: '${languageOnly}'`); + // Note: messages are from parent language, but we still keep the region + return finalize(getLocaleMessages(languageOnly), english, locale); + } catch (e) { + logger.error( + `Problem loading messages for parent locale '${languageOnly}'` + ); + } } - try { - logger.warn(`Falling back to parent language: '${languageOnly}'`); - // Note: messages are from parent language, but we still keep the region - return finalize(getLocaleMessages(languageOnly), english, appLocale); - } catch (e) { - logger.error(`Problem loading messages for locale ${languageOnly}`); - - logger.warn("Falling back to 'en' locale"); - return finalize(english, english, 'en'); - } + logger.warn("Falling back to 'en' locale"); + return finalize(english, english, 'en'); } diff --git a/app/main.ts b/app/main.ts index f800b722c7ac..cae46261e198 100644 --- a/app/main.ts +++ b/app/main.ts @@ -352,7 +352,8 @@ let menuOptions: CreateTemplateOptionsType | undefined; // These will be set after app fires the 'ready' event let logger: LoggerType | undefined; -let locale: LocaleType | undefined; +let preferredSystemLocales: Array | undefined; +let resolvedTranslationsLocale: LocaleType | undefined; let settingsChannel: SettingsChannel | undefined; function getLogger(): LoggerType { @@ -364,12 +365,19 @@ function getLogger(): LoggerType { return logger; } -function getLocale(): LocaleType { - if (!locale) { - throw new Error('getLocale: Locale not yet initialized!'); +function getPreferredSystemLocales(): Array { + if (!preferredSystemLocales) { + throw new Error('getPreferredSystemLocales: Locales not yet initialized!'); + } + return preferredSystemLocales; +} + +function getResolvedMessagesLocale(): LocaleType { + if (!resolvedTranslationsLocale) { + throw new Error('getResolvedMessagesLocale: Locale not yet initialized!'); } - return locale; + return resolvedTranslationsLocale; } type PrepareUrlOptions = { forCalling?: boolean; forCamera?: boolean }; @@ -404,7 +412,8 @@ async function prepareUrl( const urlParams: RendererConfigType = { name: packageJson.productName, - locale: getLocale().name, + resolvedTranslationsLocale: getResolvedMessagesLocale().name, + preferredSystemLocales: getPreferredSystemLocales(), version: app.getVersion(), buildCreation: config.get('buildCreation'), buildExpiration: config.get('buildExpiration'), @@ -750,7 +759,7 @@ async function createWindow() { } mainWindowCreated = true; - setupSpellChecker(mainWindow, getLocale()); + setupSpellChecker(mainWindow, getResolvedMessagesLocale()); if (!startInTray && windowConfig && windowConfig.maximized) { mainWindow.maximize(); } @@ -879,8 +888,12 @@ async function createWindow() { getLogger().info('close: showing tray notice'); const n = new Notification({ - title: getLocale().i18n('minimizeToTrayNotification--title'), - body: getLocale().i18n('minimizeToTrayNotification--body'), + title: getResolvedMessagesLocale().i18n( + 'minimizeToTrayNotification--title' + ), + body: getResolvedMessagesLocale().i18n( + 'minimizeToTrayNotification--body' + ), }); n.show(); @@ -1143,7 +1156,7 @@ async function showScreenShareWindow(sourceName: string) { minimizable: false, resizable: false, show: false, - title: getLocale().i18n('screenShareWindow'), + title: getResolvedMessagesLocale().i18n('screenShareWindow'), titleBarStyle: nonMainTitleBarStyle, width, webPreferences: { @@ -1194,7 +1207,7 @@ async function showAbout() { width: 500, height: 500, resizable: false, - title: getLocale().i18n('aboutSignalDesktop'), + title: getResolvedMessagesLocale().i18n('aboutSignalDesktop'), titleBarStyle: nonMainTitleBarStyle, titleBarOverlay, autoHideMenuBar: true, @@ -1242,7 +1255,7 @@ async function showSettingsWindow() { height: 700, frame: true, resizable: false, - title: getLocale().i18n('signalDesktopPreferences'), + title: getResolvedMessagesLocale().i18n('signalDesktopPreferences'), titleBarStyle: nonMainTitleBarStyle, titleBarOverlay, autoHideMenuBar: true, @@ -1294,7 +1307,9 @@ async function getIsLinked() { let stickerCreatorWindow: BrowserWindow | undefined; async function showStickerCreator() { if (!(await getIsLinked())) { - const message = getLocale().i18n('StickerCreator--Authentication--error'); + const message = getResolvedMessagesLocale().i18n( + 'StickerCreator--Authentication--error' + ); await dialog.showMessageBox({ type: 'warning', @@ -1319,7 +1334,7 @@ async function showStickerCreator() { width: 800, minWidth: 800, height: 650, - title: getLocale().i18n('signalDesktopStickerCreator'), + title: getResolvedMessagesLocale().i18n('signalDesktopStickerCreator'), titleBarStyle: nonMainTitleBarStyle, titleBarOverlay, autoHideMenuBar: true, @@ -1338,7 +1353,7 @@ async function showStickerCreator() { }; stickerCreatorWindow = new BrowserWindow(options); - setupSpellChecker(stickerCreatorWindow, getLocale()); + setupSpellChecker(stickerCreatorWindow, getResolvedMessagesLocale()); handleCommonWindowEvents(stickerCreatorWindow, titleBarOverlay); @@ -1381,7 +1396,7 @@ async function showDebugLogWindow() { width: 700, height: 500, resizable: false, - title: getLocale().i18n('debugLog'), + title: getResolvedMessagesLocale().i18n('debugLog'), titleBarStyle: nonMainTitleBarStyle, titleBarOverlay, autoHideMenuBar: true, @@ -1445,7 +1460,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) { width: Math.min(400, size[0]), height: Math.min(150, size[1]), resizable: false, - title: getLocale().i18n('allowAccess'), + title: getResolvedMessagesLocale().i18n('allowAccess'), titleBarStyle: nonMainTitleBarStyle, autoHideMenuBar: true, backgroundColor: await getBackgroundColor(), @@ -1567,13 +1582,13 @@ const onDatabaseError = async (error: string) => { const buttonIndex = dialog.showMessageBoxSync({ buttons: [ - getLocale().i18n('deleteAndRestart'), - getLocale().i18n('copyErrorAndQuit'), + getResolvedMessagesLocale().i18n('deleteAndRestart'), + getResolvedMessagesLocale().i18n('copyErrorAndQuit'), ], defaultId: 1, cancelId: 1, detail: redactAll(error), - message: getLocale().i18n('databaseError'), + message: getResolvedMessagesLocale().i18n('databaseError'), noLink: true, type: 'error', }); @@ -1601,8 +1616,10 @@ ipc.on('database-error', (_event: Electron.Event, error: string) => { drop(onDatabaseError(error)); }); -function getAppLocale(): string { - return getEnvironment() === Environment.Test ? 'en' : app.getLocale(); +function loadPreferredSystemLocales(): Array { + return getEnvironment() === Environment.Test + ? ['en'] + : app.getPreferredSystemLanguages(); } async function getDefaultLoginItemSettings(): Promise { @@ -1654,9 +1671,17 @@ app.on('ready', async () => { await setupCrashReports(getLogger); - if (!locale) { - const appLocale = getAppLocale(); - locale = loadLocale({ appLocale, logger: getLogger() }); + if (!resolvedTranslationsLocale) { + preferredSystemLocales = loadPreferredSystemLocales(); + logger.info( + `app.ready: preferred system locales: ${preferredSystemLocales.join( + ', ' + )}}` + ); + resolvedTranslationsLocale = loadLocale({ + preferredSystemLocales, + logger: getLogger(), + }); } sqlInitPromise = initializeSQL(userDataPath); @@ -1762,7 +1787,7 @@ app.on('ready', async () => { ); } - GlobalErrors.updateLocale(locale); + GlobalErrors.updateLocale(resolvedTranslationsLocale); // If the sql initialization takes more than three seconds to complete, we // want to notify the user that things are happening @@ -1877,7 +1902,9 @@ app.on('ready', async () => { setupMenu(); - systemTrayService = new SystemTrayService({ i18n: locale.i18n }); + systemTrayService = new SystemTrayService({ + i18n: resolvedTranslationsLocale.i18n, + }); systemTrayService.setMainWindow(mainWindow); systemTrayService.setEnabled( shouldMinimizeToSystemTray(await systemTraySettingCache.get()) @@ -1920,7 +1947,10 @@ function setupMenu(options?: Partial) { // overrides ...options, }; - const template = createTemplate(menuOptions, getLocale().i18n); + const template = createTemplate( + menuOptions, + getResolvedMessagesLocale().i18n + ); const menu = Menu.buildFromTemplate(template); Menu.setApplicationMenu(menu); @@ -2235,7 +2265,7 @@ ipc.on('get-built-in-images', async () => { // Ingested in preload.js via a sendSync call ipc.on('locale-data', event => { // eslint-disable-next-line no-param-reassign - event.returnValue = getLocale().messages; + event.returnValue = getResolvedMessagesLocale().messages; }); ipc.on('user-config-key', event => { diff --git a/sticker-creator/root.tsx b/sticker-creator/root.tsx index a2db2a33cf88..5cb7118d9f35 100644 --- a/sticker-creator/root.tsx +++ b/sticker-creator/root.tsx @@ -16,7 +16,10 @@ function ColdRoot() { return ( - + { document.documentElement.setAttribute( 'lang', - window.getLocale().substring(0, 2) + window.getResolvedMessagesLocale().split(/[-_]/)[0] ); KeyChangeListener.init(window.textsecure.storage.protocol); diff --git a/ts/components/conversation/ChatSessionRefreshedNotification.tsx b/ts/components/conversation/ChatSessionRefreshedNotification.tsx index 361020863d59..3d070a966b72 100644 --- a/ts/components/conversation/ChatSessionRefreshedNotification.tsx +++ b/ts/components/conversation/ChatSessionRefreshedNotification.tsx @@ -36,7 +36,7 @@ export function ChatSessionRefreshedNotification( const baseUrl = 'https://support.signal.org/hc/LOCALE/requests/new?desktop&chat_refreshed'; - const locale = window.getLocale(); + const locale = window.getResolvedMessagesLocale(); const supportLocale = mapToSupportLocale(locale); const url = baseUrl.replace('LOCALE', supportLocale); diff --git a/ts/models/messages.ts b/ts/models/messages.ts index 897070951741..bc91817e4321 100644 --- a/ts/models/messages.ts +++ b/ts/models/messages.ts @@ -2961,8 +2961,8 @@ export class MessageModel extends window.Backbone.Model { 'getProfile: expected updatesUrl to be a defined string' ); const userLanguages = getUserLanguages( - navigator.languages, - window.getLocale() + window.getPreferredSystemLocales(), + window.getResolvedMessagesLocale() ); const { messaging } = window.textsecure; if (!messaging) { diff --git a/ts/services/profiles.ts b/ts/services/profiles.ts index 1cf5adf58940..d5be677a0b30 100644 --- a/ts/services/profiles.ts +++ b/ts/services/profiles.ts @@ -223,8 +223,8 @@ async function doGetProfile(c: ConversationModel): Promise { ); const userLanguages = getUserLanguages( - navigator.languages, - window.getLocale() + window.getPreferredSystemLocales(), + window.getResolvedMessagesLocale() ); let profile; diff --git a/ts/test-node/app/menu_test.ts b/ts/test-node/app/menu_test.ts index d2243786f30e..16210e887273 100644 --- a/ts/test-node/app/menu_test.ts +++ b/ts/test-node/app/menu_test.ts @@ -197,8 +197,11 @@ const PLATFORMS = [ describe('createTemplate', () => { const { i18n } = loadLocale({ - appLocale: 'en', + preferredSystemLocales: ['en'], logger: { + info(_arg: unknown) { + // noop + }, error(arg: unknown) { throw new Error(String(arg)); }, diff --git a/ts/types/RendererConfig.ts b/ts/types/RendererConfig.ts index 10565a6cd6db..ba90a81b83bd 100644 --- a/ts/types/RendererConfig.ts +++ b/ts/types/RendererConfig.ts @@ -37,7 +37,8 @@ export const rendererConfigSchema = z.object({ environment: environmentSchema, homePath: configRequiredStringSchema, hostname: configRequiredStringSchema, - locale: configRequiredStringSchema, + resolvedTranslationsLocale: configRequiredStringSchema, + preferredSystemLocales: z.array(configRequiredStringSchema), name: configRequiredStringSchema, nodeVersion: configRequiredStringSchema, proxyUrl: configOptionalStringSchema, diff --git a/ts/window.d.ts b/ts/window.d.ts index 0c1a3b634254..41a29a59367f 100644 --- a/ts/window.d.ts +++ b/ts/window.d.ts @@ -160,7 +160,8 @@ declare global { getEnvironment: typeof getEnvironment; getHostName: () => string; getInteractionMode: () => 'mouse' | 'keyboard'; - getLocale: () => string; + getResolvedMessagesLocale: () => string; + getPreferredSystemLocales: () => Array; getServerPublicParams: () => string; getSfuUrl: () => string; getSocketStatus: () => SocketStatus; diff --git a/ts/windows/context.ts b/ts/windows/context.ts index 20cc6f730474..f1fea2b1ef81 100644 --- a/ts/windows/context.ts +++ b/ts/windows/context.ts @@ -37,11 +37,17 @@ activeWindowService.initialize(window.document, ipcRenderer); const params = new URLSearchParams(document.location.search); const configParam = params.get('config'); strictAssert(typeof configParam === 'string', 'config is not a string'); -const config = JSON.parse(configParam); +const config: RendererConfigType = JSON.parse(configParam); -const { locale } = config; -strictAssert(locale, 'locale could not be parsed from config'); -strictAssert(typeof locale === 'string', 'locale is not a string'); +const { resolvedTranslationsLocale } = config; +strictAssert( + resolvedTranslationsLocale, + 'locale could not be parsed from config' +); +strictAssert( + typeof resolvedTranslationsLocale === 'string', + 'locale is not a string' +); const localeMessages = ipcRenderer.sendSync('locale-data'); setEnvironment(parseEnvironment(config.environment)); @@ -114,7 +120,7 @@ export const SignalContext: SignalContextType = { getPath: (name: 'userData' | 'home'): string => { return String(config[`${name}Path`]); }, - i18n: setupI18n(locale, localeMessages), + i18n: setupI18n(resolvedTranslationsLocale, localeMessages), localeMessages, log: window.SignalContext.log, nativeThemeListener: createNativeThemeListener(ipcRenderer, window), diff --git a/ts/windows/main/phase1-ipc.ts b/ts/windows/main/phase1-ipc.ts index f424b6b82bc1..50e5a9d6aea4 100644 --- a/ts/windows/main/phase1-ipc.ts +++ b/ts/windows/main/phase1-ipc.ts @@ -38,7 +38,8 @@ window.RETRY_DELAY = false; window.platform = process.platform; window.getTitle = () => title; -window.getLocale = () => config.locale; +window.getResolvedMessagesLocale = () => config.resolvedTranslationsLocale; +window.getPreferredSystemLocales = () => config.preferredSystemLocales; window.getEnvironment = getEnvironment; window.getAppInstance = () => config.appInstance; window.getVersion = () => config.version; diff --git a/ts/windows/main/phase2-dependencies.ts b/ts/windows/main/phase2-dependencies.ts index e6b7afe9575e..9fb3b7724568 100644 --- a/ts/windows/main/phase2-dependencies.ts +++ b/ts/windows/main/phase2-dependencies.ts @@ -43,15 +43,15 @@ window.libphonenumberFormat = PhoneNumberFormat; window.React = React; window.ReactDOM = ReactDOM; -const { locale } = config; -moment.updateLocale(locale, { +const { resolvedTranslationsLocale, preferredSystemLocales } = config; +moment.updateLocale(resolvedTranslationsLocale, { relativeTime: { s: window.i18n('timestamp_s'), m: window.i18n('timestamp_m'), h: window.i18n('timestamp_h'), }, }); -moment.locale(locale); +moment.locale(preferredSystemLocales); const userDataPath = SignalContext.getPath('userData'); window.BasePaths = {