diff --git a/app/locale.js b/app/locale.js index ddf80159e8e..028dec12bdf 100644 --- a/app/locale.js +++ b/app/locale.js @@ -1,6 +1,7 @@ const path = require('path'); const fs = require('fs'); const _ = require('lodash'); +const { setup } = require('../js/modules/i18n'); function normalizeLocaleName(locale) { if (/^en-/.test(locale)) { @@ -58,7 +59,10 @@ function load({ appLocale, logger } = {}) { messages = english; } + const i18n = setup(appLocale, messages); + return { + i18n, name: localeName, messages, }; diff --git a/main.js b/main.js index 3d6e0a19d75..1d969c4848b 100644 --- a/main.js +++ b/main.js @@ -421,7 +421,7 @@ async function readyForUpdates() { // Second, start checking for app updates try { - await updater.start(getMainWindow, locale.messages, logger); + await updater.start(getMainWindow, locale, logger); } catch (error) { logger.error( 'Error starting update checks:', diff --git a/test/i18n_test.js b/test/i18n_test.js index c351887e786..3f5442ba2b7 100644 --- a/test/i18n_test.js +++ b/test/i18n_test.js @@ -8,6 +8,13 @@ describe('i18n', () => { it('returns message for given string', () => { assert.equal(i18n('reportIssue'), 'Report an issue'); }); + it('returns message with single substitution', () => { + const actual = i18n('cannotUpdateDetail', 'https://signal.org/download'); + assert.equal( + actual, + 'Signal Desktop failed to update, but there is a new version available. Please go to https://signal.org/download and install the new version manually, then either contact support or file a bug about this problem.' + ); + }); it('returns message with multiple substitutions', () => { const actual = i18n('theyChangedTheTimer', ['Someone', '5 minutes']); assert.equal( diff --git a/ts/updater/common.ts b/ts/updater/common.ts index bb5881d5f80..d4c462d1692 100644 --- a/ts/updater/common.ts +++ b/ts/updater/common.ts @@ -27,10 +27,13 @@ import { Dialogs } from '../types/Dialogs'; import * as packageJson from '../../package.json'; import { getSignatureFileName } from './signature'; -export type MessagesType = { - [key: string]: { - message: string; - description?: string; +export type LocaleType = { + i18n: (key: string, placeholders: Array) => string; + messages: { + [key: string]: { + message: string; + description?: string; + }; }; }; @@ -146,19 +149,19 @@ export async function downloadUpdate( async function showFallbackUpdateDialog( mainWindow: BrowserWindow, - messages: MessagesType + locale: LocaleType ) { const RESTART_BUTTON = 0; const LATER_BUTTON = 1; const options = { type: 'info', buttons: [ - messages.autoUpdateRestartButtonLabel.message, - messages.autoUpdateLaterButtonLabel.message, + locale.messages.autoUpdateRestartButtonLabel.message, + locale.messages.autoUpdateLaterButtonLabel.message, ], - title: messages.autoUpdateNewVersionTitle.message, - message: messages.autoUpdateNewVersionMessage.message, - detail: messages.autoUpdateNewVersionInstructions.message, + title: locale.messages.autoUpdateNewVersionTitle.message, + message: locale.messages.autoUpdateNewVersionMessage.message, + detail: locale.messages.autoUpdateNewVersionInstructions.message, defaultId: LATER_BUTTON, cancelId: LATER_BUTTON, }; @@ -170,7 +173,7 @@ async function showFallbackUpdateDialog( export function showUpdateDialog( mainWindow: BrowserWindow, - messages: MessagesType, + locale: LocaleType, performUpdateCallback: () => void ): void { let ack = false; @@ -185,20 +188,20 @@ export function showUpdateDialog( setTimeout(async () => { if (!ack) { - await showFallbackUpdateDialog(mainWindow, messages); + await showFallbackUpdateDialog(mainWindow, locale); } }, ACK_RENDER_TIMEOUT); } async function showFallbackCannotUpdateDialog( mainWindow: BrowserWindow, - messages: MessagesType + locale: LocaleType ) { const options = { type: 'error', - buttons: [messages.ok.message], - title: messages.cannotUpdate.message, - message: messages.cannotUpdateDetail.message, + buttons: [locale.messages.ok.message], + title: locale.messages.cannotUpdate.message, + message: locale.i18n('cannotUpdateDetail', ['https://signal.org/download']), }; await dialog.showMessageBox(mainWindow, options); @@ -206,7 +209,7 @@ async function showFallbackCannotUpdateDialog( export function showCannotUpdateDialog( mainWindow: BrowserWindow, - messages: MessagesType + locale: LocaleType ): void { let ack = false; @@ -218,7 +221,7 @@ export function showCannotUpdateDialog( setTimeout(async () => { if (!ack) { - await showFallbackCannotUpdateDialog(mainWindow, messages); + await showFallbackCannotUpdateDialog(mainWindow, locale); } }, ACK_RENDER_TIMEOUT); } diff --git a/ts/updater/index.ts b/ts/updater/index.ts index 0724319bb53..17276260e78 100644 --- a/ts/updater/index.ts +++ b/ts/updater/index.ts @@ -3,13 +3,13 @@ import { BrowserWindow } from 'electron'; import { start as startMacOS } from './macos'; import { start as startWindows } from './windows'; -import { LoggerType, MessagesType } from './common'; +import { LocaleType, LoggerType } from './common'; let initialized = false; export async function start( getMainWindow: () => BrowserWindow, - messages?: MessagesType, + locale?: LocaleType, logger?: LoggerType ) { const { platform } = process; @@ -19,8 +19,8 @@ export async function start( } initialized = true; - if (!messages) { - throw new Error('updater/start: Must provide messages!'); + if (!locale) { + throw new Error('updater/start: Must provide locale!'); } if (!logger) { throw new Error('updater/start: Must provide logger!'); @@ -35,9 +35,9 @@ export async function start( } if (platform === 'win32') { - await startWindows(getMainWindow, messages, logger); + await startWindows(getMainWindow, locale, logger); } else if (platform === 'darwin') { - await startMacOS(getMainWindow, messages, logger); + await startMacOS(getMainWindow, locale, logger); } else { throw new Error('updater/start: Unsupported platform'); } diff --git a/ts/updater/macos.ts b/ts/updater/macos.ts index cfd697d3560..c5d7e15c35a 100644 --- a/ts/updater/macos.ts +++ b/ts/updater/macos.ts @@ -15,8 +15,8 @@ import { deleteTempDir, downloadUpdate, getPrintableError, + LocaleType, LoggerType, - MessagesType, showCannotUpdateDialog, showUpdateDialog, } from './common'; @@ -31,7 +31,7 @@ const INTERVAL = MINUTE * 30; export async function start( getMainWindow: () => BrowserWindow, - messages: MessagesType, + locale: LocaleType, logger: LoggerType ) { logger.info('macos/start: starting checks...'); @@ -41,13 +41,13 @@ export async function start( setInterval(async () => { try { - await checkDownloadAndInstall(getMainWindow, messages, logger); + await checkDownloadAndInstall(getMainWindow, locale, logger); } catch (error) { logger.error('macos/start: error:', getPrintableError(error)); } }, INTERVAL); - await checkDownloadAndInstall(getMainWindow, messages, logger); + await checkDownloadAndInstall(getMainWindow, locale, logger); } let fileName: string; @@ -57,7 +57,7 @@ let loggerForQuitHandler: LoggerType; async function checkDownloadAndInstall( getMainWindow: () => BrowserWindow, - messages: MessagesType, + locale: LocaleType, logger: LoggerType ) { if (isChecking) { @@ -98,12 +98,12 @@ async function checkDownloadAndInstall( const message: string = error.message || ''; if (message.includes(readOnly)) { logger.info('checkDownloadAndInstall: showing read-only dialog...'); - showReadOnlyDialog(getMainWindow(), messages); + showReadOnlyDialog(getMainWindow(), locale); } else { logger.info( 'checkDownloadAndInstall: showing general update failure dialog...' ); - showCannotUpdateDialog(getMainWindow(), messages); + showCannotUpdateDialog(getMainWindow(), locale); } throw error; @@ -114,7 +114,7 @@ async function checkDownloadAndInstall( logger.info('checkDownloadAndInstall: showing update dialog...'); - showUpdateDialog(getMainWindow(), messages, () => { + showUpdateDialog(getMainWindow(), locale, () => { logger.info('checkDownloadAndInstall: calling quitAndInstall...'); markShouldQuit(); autoUpdater.quitAndInstall(); @@ -341,7 +341,7 @@ function shutdown( export function showReadOnlyDialog( mainWindow: BrowserWindow, - messages: MessagesType + locale: LocaleType ): void { let ack = false; @@ -353,20 +353,20 @@ export function showReadOnlyDialog( setTimeout(async () => { if (!ack) { - await showFallbackReadOnlyDialog(mainWindow, messages); + await showFallbackReadOnlyDialog(mainWindow, locale); } }, ACK_RENDER_TIMEOUT); } async function showFallbackReadOnlyDialog( mainWindow: BrowserWindow, - messages: MessagesType + locale: LocaleType ) { const options = { type: 'warning', - buttons: [messages.ok.message], - title: messages.cannotUpdate.message, - message: messages.readOnlyVolume.message, + buttons: [locale.messages.ok.message], + title: locale.messages.cannotUpdate.message, + message: locale.i18n('readOnlyVolume', ['Signal.app', '/Applications']), }; await dialog.showMessageBox(mainWindow, options); diff --git a/ts/updater/windows.ts b/ts/updater/windows.ts index e1295b85a83..586d1e933ba 100644 --- a/ts/updater/windows.ts +++ b/ts/updater/windows.ts @@ -12,8 +12,8 @@ import { deleteTempDir, downloadUpdate, getPrintableError, + LocaleType, LoggerType, - MessagesType, showCannotUpdateDialog, showUpdateDialog, } from './common'; @@ -30,7 +30,7 @@ const INTERVAL = MINUTE * 30; export async function start( getMainWindow: () => BrowserWindow, - messages: MessagesType, + locale: LocaleType, logger: LoggerType ) { logger.info('windows/start: starting checks...'); @@ -40,14 +40,14 @@ export async function start( setInterval(async () => { try { - await checkDownloadAndInstall(getMainWindow, messages, logger); + await checkDownloadAndInstall(getMainWindow, locale, logger); } catch (error) { logger.error('windows/start: error:', getPrintableError(error)); } }, INTERVAL); await deletePreviousInstallers(logger); - await checkDownloadAndInstall(getMainWindow, messages, logger); + await checkDownloadAndInstall(getMainWindow, locale, logger); } let fileName: string; @@ -58,7 +58,7 @@ let loggerForQuitHandler: LoggerType; async function checkDownloadAndInstall( getMainWindow: () => BrowserWindow, - messages: MessagesType, + locale: LocaleType, logger: LoggerType ) { if (isChecking) { @@ -93,7 +93,7 @@ async function checkDownloadAndInstall( } logger.info('checkDownloadAndInstall: showing dialog...'); - showUpdateDialog(getMainWindow(), messages, async () => { + showUpdateDialog(getMainWindow(), locale, async () => { try { await verifyAndInstall(updateFilePath, version, logger); installing = true; @@ -101,7 +101,7 @@ async function checkDownloadAndInstall( logger.info( 'checkDownloadAndInstall: showing general update failure dialog...' ); - showCannotUpdateDialog(getMainWindow(), messages); + showCannotUpdateDialog(getMainWindow(), locale); throw error; } diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index 06282612f68..67b352ac325 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -3,7 +3,7 @@ "rule": "jQuery-load(", "path": "app/locale.js", "line": "function load({ appLocale, logger } = {}) {", - "lineNumber": 27, + "lineNumber": 28, "reasonCategory": "falseMatch", "updated": "2018-09-13T21:20:44.234Z" },