[MM-14058] Add support for i18n (#2190)

* Add language files

* Add react-intl, mmjstool, setup for adding translations

* Translated main module

* Translations for renderer

* A few minor fixes

* More fixes

* Add CI, add missing menu translations, other cleanup

* Added setting to manually select the language of the app

* Force English for E2e

* Unit tests

* Fix mmjstool

* Move set language to before update menu

* PR feedback
This commit is contained in:
Devin Binnie
2022-07-14 11:04:18 -04:00
committed by GitHub
parent 22c97591d5
commit 59e4e7e516
92 changed files with 3554 additions and 2375 deletions

View File

@@ -33,6 +33,9 @@ jest.mock('main/certificateStore', () => ({
add: jest.fn(),
save: jest.fn(),
}));
jest.mock('main/i18nManager', () => ({
localizeMessage: jest.fn(),
}));
jest.mock('main/tray/tray', () => ({}));
jest.mock('main/windows/windowManager', () => ({
getMainWindow: jest.fn(),

View File

@@ -8,6 +8,7 @@ import urlUtils from 'common/utils/url';
import updateManager from 'main/autoUpdater';
import CertificateStore from 'main/certificateStore';
import {localizeMessage} from 'main/i18nManager';
import {destroyTray} from 'main/tray/tray';
import WindowManager from 'main/windows/windowManager';
@@ -96,8 +97,8 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo
certificateErrorCallbacks.set(errorID, callback);
return;
}
const extraDetail = CertificateStore.isExisting(origin) ? 'Certificate is different from previous one.\n\n' : '';
const detail = `${extraDetail}origin: ${origin}\nError: ${error}`;
const extraDetail = CertificateStore.isExisting(origin) ? localizeMessage('main.app.app.handleAppCertificateError.dialog.extraDetail', 'Certificate is different from previous one.\n\n') : '';
const detail = localizeMessage('main.app.app.handleAppCertificateError.certError.dialog.detail', '{extraDetail}origin: {origin}\nError: {error}', {extraDetail, origin, error});
certificateErrorCallbacks.set(errorID, callback);
@@ -109,21 +110,27 @@ export async function handleAppCertificateError(event: Event, webContents: WebCo
try {
let result = await dialog.showMessageBox(mainWindow, {
title: 'Certificate Error',
message: 'There is a configuration issue with this Mattermost server, or someone is trying to intercept your connection. You also may need to sign into the Wi-Fi you are connected to using your web browser.',
title: localizeMessage('main.app.app.handleAppCertificateError.certError.dialog.title', 'Certificate Error'),
message: localizeMessage('main.app.app.handleAppCertificateError.certError.dialog.message', 'There is a configuration issue with this Mattermost server, or someone is trying to intercept your connection. You also may need to sign into the Wi-Fi you are connected to using your web browser.'),
type: 'error',
detail,
buttons: ['More Details', 'Cancel Connection'],
buttons: [
localizeMessage('main.app.app.handleAppCertificateError.certError.button.moreDetails', 'More Details'),
localizeMessage('main.app.app.handleAppCertificateError.certError.button.cancelConnection', 'Cancel Connection'),
],
cancelId: 1,
});
if (result.response === 0) {
result = await dialog.showMessageBox(mainWindow, {
title: 'Certificate Not Trusted',
message: `Certificate from "${certificate.issuerName}" is not trusted.`,
title: localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.dialog.title', 'Certificate Not Trusted'),
message: localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.dialog.message', 'Certificate from "{issuerName}" is not trusted.', {issuerName: certificate.issuerName}),
detail: extraDetail,
type: 'error',
buttons: ['Trust Insecure Certificate', 'Cancel Connection'],
buttons: [
localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.button.trustInsecureCertificate', 'Trust Insecure Certificate'),
localizeMessage('main.app.app.handleAppCertificateError.certNotTrusted.button.cancelConnection', 'Cancel Connection'),
],
cancelId: 1,
checkboxChecked: false,
checkboxLabel: "Don't ask again",

View File

@@ -39,6 +39,8 @@ jest.mock('electron', () => ({
setAppUserModelId: jest.fn(),
getVersion: jest.fn(),
whenReady: jest.fn(),
getLocale: jest.fn(),
getLocaleCountryCode: jest.fn(),
},
ipcMain: {
on: jest.fn(),
@@ -54,6 +56,11 @@ jest.mock('electron', () => ({
},
}));
jest.mock('main/i18nManager', () => ({
localizeMessage: jest.fn(),
setLocale: jest.fn(),
}));
jest.mock('electron-devtools-installer', () => {
return () => ({
REACT_DEVELOPER_TOOLS: 'react-developer-tools',

View File

@@ -47,6 +47,7 @@ import {setupBadge} from 'main/badge';
import CertificateManager from 'main/certificateManager';
import {updatePaths} from 'main/constants';
import CriticalErrorHandler from 'main/CriticalErrorHandler';
import i18nManager, {localizeMessage} from 'main/i18nManager';
import {displayDownloadCompleted} from 'main/notifications';
import parseArgs from 'main/ParseArgs';
import TrustedOriginsStore from 'main/trustedOrigins';
@@ -359,7 +360,7 @@ function initializeAfterAppReady() {
const filters = [];
if (fileElements.length > 1) {
filters.push({
name: 'All files',
name: localizeMessage('main.app.initialize.downloadBox.allFiles', 'All files'),
extensions: ['*'],
});
}
@@ -376,6 +377,14 @@ function initializeAfterAppReady() {
});
});
// needs to be done after app ready
// must be done before update menu
if (Config.appLanguage) {
i18nManager.setLocale(Config.appLanguage);
} else if (!i18nManager.setLocale(app.getLocale())) {
i18nManager.setLocale(app.getLocaleCountryCode());
}
handleUpdateMenuEvent();
ipcMain.emit('update-dict');

View File

@@ -49,6 +49,9 @@ jest.mock('main/autoUpdater', () => ({}));
jest.mock('main/constants', () => ({
updatePaths: jest.fn(),
}));
jest.mock('main/i18nManager', () => ({
localizeMessage: jest.fn(),
}));
jest.mock('main/menus/app', () => ({}));
jest.mock('main/menus/tray', () => ({}));
jest.mock('main/server/serverInfo', () => ({

View File

@@ -21,6 +21,7 @@ import Utils from 'common/utils/util';
import updateManager from 'main/autoUpdater';
import {migrationInfoPath, updatePaths} from 'main/constants';
import {localizeMessage} from 'main/i18nManager';
import {createMenu as createAppMenu} from 'main/menus/app';
import {createMenu as createTrayMenu} from 'main/menus/tray';
import {ServerInfo} from 'main/server/serverInfo';
@@ -224,11 +225,14 @@ export function migrateMacAppStore() {
}
const cancelImport = dialog.showMessageBoxSync({
title: 'Mattermost',
message: 'Import Existing Configuration',
detail: 'It appears that an existing Mattermost configuration exists, would you like to import it? You will be asked to pick the correct configuration directory.',
title: app.name,
message: localizeMessage('main.app.utils.migrateMacAppStore.dialog.message', 'Import Existing Configuration'),
detail: localizeMessage('main.app.utils.migrateMacAppStore.dialog.detail', 'It appears that an existing {appName} configuration exists, would you like to import it? You will be asked to pick the correct configuration directory.', {appName: app.name}),
icon: appIcon,
buttons: ['Select Directory and Import', 'Don\'t Import'],
buttons: [
localizeMessage('main.app.utils.migrateMacAppStore.button.selectAndImport', 'Select Directory and Import'),
localizeMessage('main.app.utils.migrateMacAppStore.button.dontImport', 'Don\'t Import'),
],
type: 'info',
defaultId: 0,
cancelId: 1,