Use preferred system locales and add Farsi font stack

This commit is contained in:
Jamie Kyle 2023-01-24 16:54:46 -08:00 committed by GitHub
parent fbdfaf3962
commit da0a741a36
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 124 additions and 72 deletions

View file

@ -49,13 +49,13 @@ function finalize(
} }
export function load({ export function load({
appLocale, preferredSystemLocales,
logger, logger,
}: { }: {
appLocale: string; preferredSystemLocales: Array<string>;
logger: Pick<LoggerType, 'error' | 'warn'>; logger: Pick<LoggerType, 'error' | 'warn' | 'info'>;
}): LocaleType { }): LocaleType {
if (!appLocale) { if (preferredSystemLocales.length === 0) {
throw new TypeError('`appLocale` is required'); throw new TypeError('`appLocale` is required');
} }
@ -68,27 +68,28 @@ export function load({
const english = getLocaleMessages('en'); const english = getLocaleMessages('en');
// Load locale - if we can't load messages for the current locale, we for (const locale of preferredSystemLocales) {
// default to 'en' try {
// logger.info(`Loading preferred system locale: '${locale}'`);
// possible locales: return finalize(getLocaleMessages(locale), english, locale);
// https://source.chromium.org/chromium/chromium/src/+/main:ui/base/l10n/l10n_util.cc } catch (e) {
const languageOnly = removeRegion(appLocale); logger.warn(
`Problem loading messages for locale '${locale}', ${e.toString()}`
);
}
try { const languageOnly = removeRegion(locale);
return finalize(getLocaleMessages(appLocale), english, appLocale); try {
} catch (e) { logger.warn(`Falling back to parent language: '${languageOnly}'`);
logger.warn(`Problem loading messages for locale ${appLocale}`); // 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 'en' locale");
logger.warn(`Falling back to parent language: '${languageOnly}'`); return finalize(english, english, 'en');
// 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');
}
} }

View file

@ -352,7 +352,8 @@ let menuOptions: CreateTemplateOptionsType | undefined;
// These will be set after app fires the 'ready' event // These will be set after app fires the 'ready' event
let logger: LoggerType | undefined; let logger: LoggerType | undefined;
let locale: LocaleType | undefined; let preferredSystemLocales: Array<string> | undefined;
let resolvedTranslationsLocale: LocaleType | undefined;
let settingsChannel: SettingsChannel | undefined; let settingsChannel: SettingsChannel | undefined;
function getLogger(): LoggerType { function getLogger(): LoggerType {
@ -364,12 +365,19 @@ function getLogger(): LoggerType {
return logger; return logger;
} }
function getLocale(): LocaleType { function getPreferredSystemLocales(): Array<string> {
if (!locale) { if (!preferredSystemLocales) {
throw new Error('getLocale: Locale not yet initialized!'); 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 }; type PrepareUrlOptions = { forCalling?: boolean; forCamera?: boolean };
@ -404,7 +412,8 @@ async function prepareUrl(
const urlParams: RendererConfigType = { const urlParams: RendererConfigType = {
name: packageJson.productName, name: packageJson.productName,
locale: getLocale().name, resolvedTranslationsLocale: getResolvedMessagesLocale().name,
preferredSystemLocales: getPreferredSystemLocales(),
version: app.getVersion(), version: app.getVersion(),
buildCreation: config.get<number>('buildCreation'), buildCreation: config.get<number>('buildCreation'),
buildExpiration: config.get<number>('buildExpiration'), buildExpiration: config.get<number>('buildExpiration'),
@ -750,7 +759,7 @@ async function createWindow() {
} }
mainWindowCreated = true; mainWindowCreated = true;
setupSpellChecker(mainWindow, getLocale()); setupSpellChecker(mainWindow, getResolvedMessagesLocale());
if (!startInTray && windowConfig && windowConfig.maximized) { if (!startInTray && windowConfig && windowConfig.maximized) {
mainWindow.maximize(); mainWindow.maximize();
} }
@ -879,8 +888,12 @@ async function createWindow() {
getLogger().info('close: showing tray notice'); getLogger().info('close: showing tray notice');
const n = new Notification({ const n = new Notification({
title: getLocale().i18n('minimizeToTrayNotification--title'), title: getResolvedMessagesLocale().i18n(
body: getLocale().i18n('minimizeToTrayNotification--body'), 'minimizeToTrayNotification--title'
),
body: getResolvedMessagesLocale().i18n(
'minimizeToTrayNotification--body'
),
}); });
n.show(); n.show();
@ -1143,7 +1156,7 @@ async function showScreenShareWindow(sourceName: string) {
minimizable: false, minimizable: false,
resizable: false, resizable: false,
show: false, show: false,
title: getLocale().i18n('screenShareWindow'), title: getResolvedMessagesLocale().i18n('screenShareWindow'),
titleBarStyle: nonMainTitleBarStyle, titleBarStyle: nonMainTitleBarStyle,
width, width,
webPreferences: { webPreferences: {
@ -1194,7 +1207,7 @@ async function showAbout() {
width: 500, width: 500,
height: 500, height: 500,
resizable: false, resizable: false,
title: getLocale().i18n('aboutSignalDesktop'), title: getResolvedMessagesLocale().i18n('aboutSignalDesktop'),
titleBarStyle: nonMainTitleBarStyle, titleBarStyle: nonMainTitleBarStyle,
titleBarOverlay, titleBarOverlay,
autoHideMenuBar: true, autoHideMenuBar: true,
@ -1242,7 +1255,7 @@ async function showSettingsWindow() {
height: 700, height: 700,
frame: true, frame: true,
resizable: false, resizable: false,
title: getLocale().i18n('signalDesktopPreferences'), title: getResolvedMessagesLocale().i18n('signalDesktopPreferences'),
titleBarStyle: nonMainTitleBarStyle, titleBarStyle: nonMainTitleBarStyle,
titleBarOverlay, titleBarOverlay,
autoHideMenuBar: true, autoHideMenuBar: true,
@ -1294,7 +1307,9 @@ async function getIsLinked() {
let stickerCreatorWindow: BrowserWindow | undefined; let stickerCreatorWindow: BrowserWindow | undefined;
async function showStickerCreator() { async function showStickerCreator() {
if (!(await getIsLinked())) { if (!(await getIsLinked())) {
const message = getLocale().i18n('StickerCreator--Authentication--error'); const message = getResolvedMessagesLocale().i18n(
'StickerCreator--Authentication--error'
);
await dialog.showMessageBox({ await dialog.showMessageBox({
type: 'warning', type: 'warning',
@ -1319,7 +1334,7 @@ async function showStickerCreator() {
width: 800, width: 800,
minWidth: 800, minWidth: 800,
height: 650, height: 650,
title: getLocale().i18n('signalDesktopStickerCreator'), title: getResolvedMessagesLocale().i18n('signalDesktopStickerCreator'),
titleBarStyle: nonMainTitleBarStyle, titleBarStyle: nonMainTitleBarStyle,
titleBarOverlay, titleBarOverlay,
autoHideMenuBar: true, autoHideMenuBar: true,
@ -1338,7 +1353,7 @@ async function showStickerCreator() {
}; };
stickerCreatorWindow = new BrowserWindow(options); stickerCreatorWindow = new BrowserWindow(options);
setupSpellChecker(stickerCreatorWindow, getLocale()); setupSpellChecker(stickerCreatorWindow, getResolvedMessagesLocale());
handleCommonWindowEvents(stickerCreatorWindow, titleBarOverlay); handleCommonWindowEvents(stickerCreatorWindow, titleBarOverlay);
@ -1381,7 +1396,7 @@ async function showDebugLogWindow() {
width: 700, width: 700,
height: 500, height: 500,
resizable: false, resizable: false,
title: getLocale().i18n('debugLog'), title: getResolvedMessagesLocale().i18n('debugLog'),
titleBarStyle: nonMainTitleBarStyle, titleBarStyle: nonMainTitleBarStyle,
titleBarOverlay, titleBarOverlay,
autoHideMenuBar: true, autoHideMenuBar: true,
@ -1445,7 +1460,7 @@ function showPermissionsPopupWindow(forCalling: boolean, forCamera: boolean) {
width: Math.min(400, size[0]), width: Math.min(400, size[0]),
height: Math.min(150, size[1]), height: Math.min(150, size[1]),
resizable: false, resizable: false,
title: getLocale().i18n('allowAccess'), title: getResolvedMessagesLocale().i18n('allowAccess'),
titleBarStyle: nonMainTitleBarStyle, titleBarStyle: nonMainTitleBarStyle,
autoHideMenuBar: true, autoHideMenuBar: true,
backgroundColor: await getBackgroundColor(), backgroundColor: await getBackgroundColor(),
@ -1567,13 +1582,13 @@ const onDatabaseError = async (error: string) => {
const buttonIndex = dialog.showMessageBoxSync({ const buttonIndex = dialog.showMessageBoxSync({
buttons: [ buttons: [
getLocale().i18n('deleteAndRestart'), getResolvedMessagesLocale().i18n('deleteAndRestart'),
getLocale().i18n('copyErrorAndQuit'), getResolvedMessagesLocale().i18n('copyErrorAndQuit'),
], ],
defaultId: 1, defaultId: 1,
cancelId: 1, cancelId: 1,
detail: redactAll(error), detail: redactAll(error),
message: getLocale().i18n('databaseError'), message: getResolvedMessagesLocale().i18n('databaseError'),
noLink: true, noLink: true,
type: 'error', type: 'error',
}); });
@ -1601,8 +1616,10 @@ ipc.on('database-error', (_event: Electron.Event, error: string) => {
drop(onDatabaseError(error)); drop(onDatabaseError(error));
}); });
function getAppLocale(): string { function loadPreferredSystemLocales(): Array<string> {
return getEnvironment() === Environment.Test ? 'en' : app.getLocale(); return getEnvironment() === Environment.Test
? ['en']
: app.getPreferredSystemLanguages();
} }
async function getDefaultLoginItemSettings(): Promise<LoginItemSettingsOptions> { async function getDefaultLoginItemSettings(): Promise<LoginItemSettingsOptions> {
@ -1654,9 +1671,17 @@ app.on('ready', async () => {
await setupCrashReports(getLogger); await setupCrashReports(getLogger);
if (!locale) { if (!resolvedTranslationsLocale) {
const appLocale = getAppLocale(); preferredSystemLocales = loadPreferredSystemLocales();
locale = loadLocale({ appLocale, logger: getLogger() }); logger.info(
`app.ready: preferred system locales: ${preferredSystemLocales.join(
', '
)}}`
);
resolvedTranslationsLocale = loadLocale({
preferredSystemLocales,
logger: getLogger(),
});
} }
sqlInitPromise = initializeSQL(userDataPath); 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 // If the sql initialization takes more than three seconds to complete, we
// want to notify the user that things are happening // want to notify the user that things are happening
@ -1877,7 +1902,9 @@ app.on('ready', async () => {
setupMenu(); setupMenu();
systemTrayService = new SystemTrayService({ i18n: locale.i18n }); systemTrayService = new SystemTrayService({
i18n: resolvedTranslationsLocale.i18n,
});
systemTrayService.setMainWindow(mainWindow); systemTrayService.setMainWindow(mainWindow);
systemTrayService.setEnabled( systemTrayService.setEnabled(
shouldMinimizeToSystemTray(await systemTraySettingCache.get()) shouldMinimizeToSystemTray(await systemTraySettingCache.get())
@ -1920,7 +1947,10 @@ function setupMenu(options?: Partial<CreateTemplateOptionsType>) {
// overrides // overrides
...options, ...options,
}; };
const template = createTemplate(menuOptions, getLocale().i18n); const template = createTemplate(
menuOptions,
getResolvedMessagesLocale().i18n
);
const menu = Menu.buildFromTemplate(template); const menu = Menu.buildFromTemplate(template);
Menu.setApplicationMenu(menu); Menu.setApplicationMenu(menu);
@ -2235,7 +2265,7 @@ ipc.on('get-built-in-images', async () => {
// Ingested in preload.js via a sendSync call // Ingested in preload.js via a sendSync call
ipc.on('locale-data', event => { ipc.on('locale-data', event => {
// eslint-disable-next-line no-param-reassign // eslint-disable-next-line no-param-reassign
event.returnValue = getLocale().messages; event.returnValue = getResolvedMessagesLocale().messages;
}); });
ipc.on('user-config-key', event => { ipc.on('user-config-key', event => {

View file

@ -16,7 +16,10 @@ function ColdRoot() {
return ( return (
<ReduxProvider store={store}> <ReduxProvider store={store}>
<Router history={history}> <Router history={history}>
<I18n messages={localeMessages} locale={SignalContext.config.locale}> <I18n
messages={localeMessages}
locale={SignalContext.config.resolvedTranslationsLocale}
>
<App <App
executeMenuRole={SignalContext.executeMenuRole} executeMenuRole={SignalContext.executeMenuRole}
hasCustomTitleBar={SignalContext.OS.hasCustomTitleBar()} hasCustomTitleBar={SignalContext.OS.hasCustomTitleBar()}

View file

@ -5,11 +5,17 @@
@mixin font-family { @mixin font-family {
font-family: $inter; font-family: $inter;
/* Japanese */
&:lang(ja) { &:lang(ja) {
font-family: 'SF Pro JP', 'Hiragino Kaku Gothic Pro', 'ヒラギノ角ゴ Pro W3', font-family: 'SF Pro JP', 'Hiragino Kaku Gothic Pro', 'ヒラギノ角ゴ Pro W3',
メイリオ, Meiryo, ' Pゴシック', 'Helvetica Neue', Helvetica, Arial, メイリオ, Meiryo, ' Pゴシック', 'Helvetica Neue', Helvetica, Arial,
sans-serif; sans-serif;
} }
/* Farsi (Persian) */
&:lang(fa) {
font-family: -apple-system, system-ui, BlinkMacSystemFont, 'Segoe UI',
Tahoma, 'Noto Sans Arabic', Helvetica, Arial, sans-serif;
}
} }
@mixin font-title-1 { @mixin font-title-1 {

View file

@ -552,7 +552,7 @@ export async function startApp(): Promise<void> {
document.documentElement.setAttribute( document.documentElement.setAttribute(
'lang', 'lang',
window.getLocale().substring(0, 2) window.getResolvedMessagesLocale().split(/[-_]/)[0]
); );
KeyChangeListener.init(window.textsecure.storage.protocol); KeyChangeListener.init(window.textsecure.storage.protocol);

View file

@ -36,7 +36,7 @@ export function ChatSessionRefreshedNotification(
const baseUrl = const baseUrl =
'https://support.signal.org/hc/LOCALE/requests/new?desktop&chat_refreshed'; 'https://support.signal.org/hc/LOCALE/requests/new?desktop&chat_refreshed';
const locale = window.getLocale(); const locale = window.getResolvedMessagesLocale();
const supportLocale = mapToSupportLocale(locale); const supportLocale = mapToSupportLocale(locale);
const url = baseUrl.replace('LOCALE', supportLocale); const url = baseUrl.replace('LOCALE', supportLocale);

View file

@ -2961,8 +2961,8 @@ export class MessageModel extends window.Backbone.Model<MessageAttributesType> {
'getProfile: expected updatesUrl to be a defined string' 'getProfile: expected updatesUrl to be a defined string'
); );
const userLanguages = getUserLanguages( const userLanguages = getUserLanguages(
navigator.languages, window.getPreferredSystemLocales(),
window.getLocale() window.getResolvedMessagesLocale()
); );
const { messaging } = window.textsecure; const { messaging } = window.textsecure;
if (!messaging) { if (!messaging) {

View file

@ -223,8 +223,8 @@ async function doGetProfile(c: ConversationModel): Promise<void> {
); );
const userLanguages = getUserLanguages( const userLanguages = getUserLanguages(
navigator.languages, window.getPreferredSystemLocales(),
window.getLocale() window.getResolvedMessagesLocale()
); );
let profile; let profile;

View file

@ -197,8 +197,11 @@ const PLATFORMS = [
describe('createTemplate', () => { describe('createTemplate', () => {
const { i18n } = loadLocale({ const { i18n } = loadLocale({
appLocale: 'en', preferredSystemLocales: ['en'],
logger: { logger: {
info(_arg: unknown) {
// noop
},
error(arg: unknown) { error(arg: unknown) {
throw new Error(String(arg)); throw new Error(String(arg));
}, },

View file

@ -37,7 +37,8 @@ export const rendererConfigSchema = z.object({
environment: environmentSchema, environment: environmentSchema,
homePath: configRequiredStringSchema, homePath: configRequiredStringSchema,
hostname: configRequiredStringSchema, hostname: configRequiredStringSchema,
locale: configRequiredStringSchema, resolvedTranslationsLocale: configRequiredStringSchema,
preferredSystemLocales: z.array(configRequiredStringSchema),
name: configRequiredStringSchema, name: configRequiredStringSchema,
nodeVersion: configRequiredStringSchema, nodeVersion: configRequiredStringSchema,
proxyUrl: configOptionalStringSchema, proxyUrl: configOptionalStringSchema,

3
ts/window.d.ts vendored
View file

@ -160,7 +160,8 @@ declare global {
getEnvironment: typeof getEnvironment; getEnvironment: typeof getEnvironment;
getHostName: () => string; getHostName: () => string;
getInteractionMode: () => 'mouse' | 'keyboard'; getInteractionMode: () => 'mouse' | 'keyboard';
getLocale: () => string; getResolvedMessagesLocale: () => string;
getPreferredSystemLocales: () => Array<string>;
getServerPublicParams: () => string; getServerPublicParams: () => string;
getSfuUrl: () => string; getSfuUrl: () => string;
getSocketStatus: () => SocketStatus; getSocketStatus: () => SocketStatus;

View file

@ -37,11 +37,17 @@ activeWindowService.initialize(window.document, ipcRenderer);
const params = new URLSearchParams(document.location.search); const params = new URLSearchParams(document.location.search);
const configParam = params.get('config'); const configParam = params.get('config');
strictAssert(typeof configParam === 'string', 'config is not a string'); strictAssert(typeof configParam === 'string', 'config is not a string');
const config = JSON.parse(configParam); const config: RendererConfigType = JSON.parse(configParam);
const { locale } = config; const { resolvedTranslationsLocale } = config;
strictAssert(locale, 'locale could not be parsed from config'); strictAssert(
strictAssert(typeof locale === 'string', 'locale is not a string'); resolvedTranslationsLocale,
'locale could not be parsed from config'
);
strictAssert(
typeof resolvedTranslationsLocale === 'string',
'locale is not a string'
);
const localeMessages = ipcRenderer.sendSync('locale-data'); const localeMessages = ipcRenderer.sendSync('locale-data');
setEnvironment(parseEnvironment(config.environment)); setEnvironment(parseEnvironment(config.environment));
@ -114,7 +120,7 @@ export const SignalContext: SignalContextType = {
getPath: (name: 'userData' | 'home'): string => { getPath: (name: 'userData' | 'home'): string => {
return String(config[`${name}Path`]); return String(config[`${name}Path`]);
}, },
i18n: setupI18n(locale, localeMessages), i18n: setupI18n(resolvedTranslationsLocale, localeMessages),
localeMessages, localeMessages,
log: window.SignalContext.log, log: window.SignalContext.log,
nativeThemeListener: createNativeThemeListener(ipcRenderer, window), nativeThemeListener: createNativeThemeListener(ipcRenderer, window),

View file

@ -38,7 +38,8 @@ window.RETRY_DELAY = false;
window.platform = process.platform; window.platform = process.platform;
window.getTitle = () => title; window.getTitle = () => title;
window.getLocale = () => config.locale; window.getResolvedMessagesLocale = () => config.resolvedTranslationsLocale;
window.getPreferredSystemLocales = () => config.preferredSystemLocales;
window.getEnvironment = getEnvironment; window.getEnvironment = getEnvironment;
window.getAppInstance = () => config.appInstance; window.getAppInstance = () => config.appInstance;
window.getVersion = () => config.version; window.getVersion = () => config.version;

View file

@ -43,15 +43,15 @@ window.libphonenumberFormat = PhoneNumberFormat;
window.React = React; window.React = React;
window.ReactDOM = ReactDOM; window.ReactDOM = ReactDOM;
const { locale } = config; const { resolvedTranslationsLocale, preferredSystemLocales } = config;
moment.updateLocale(locale, { moment.updateLocale(resolvedTranslationsLocale, {
relativeTime: { relativeTime: {
s: window.i18n('timestamp_s'), s: window.i18n('timestamp_s'),
m: window.i18n('timestamp_m'), m: window.i18n('timestamp_m'),
h: window.i18n('timestamp_h'), h: window.i18n('timestamp_h'),
}, },
}); });
moment.locale(locale); moment.locale(preferredSystemLocales);
const userDataPath = SignalContext.getPath('userData'); const userDataPath = SignalContext.getPath('userData');
window.BasePaths = { window.BasePaths = {