From 3ebb09fa8dcdfaba57e3576bdb790431e2b0a71d Mon Sep 17 00:00:00 2001 From: Kyle Spearrin Date: Wed, 11 Apr 2018 14:52:49 -0400 Subject: [PATCH] converted i18nservice properly --- src/background/main.background.ts | 30 +++--- src/background/runtime.background.ts | 18 ++-- src/browser/browserApi.ts | 10 +- src/popup/services/services.module.ts | 4 +- src/services/i18n.service.ts | 146 +++++++++++++++++--------- src/services/i18n2.service.ts | 29 ----- 6 files changed, 133 insertions(+), 104 deletions(-) delete mode 100644 src/services/i18n2.service.ts diff --git a/src/background/main.background.ts b/src/background/main.background.ts index f4865c12a1..7fd8333922 100644 --- a/src/background/main.background.ts +++ b/src/background/main.background.ts @@ -60,8 +60,7 @@ import AutofillService from '../services/autofill.service'; import BrowserMessagingService from '../services/browserMessaging.service'; import BrowserPlatformUtilsService from '../services/browserPlatformUtils.service'; import BrowserStorageService from '../services/browserStorage.service'; -import i18nService from '../services/i18n.service'; -import I18n2Service from '../services/i18n2.service'; +import I18nService from '../services/i18n.service'; import { AutofillService as AutofillServiceAbstraction } from '../services/abstractions/autofill.service'; @@ -69,8 +68,7 @@ export default class MainBackground { messagingService: MessagingServiceAbstraction; storageService: StorageServiceAbstraction; secureStorageService: StorageServiceAbstraction; - i18nService: any; - i18n2Service: I18nServiceAbstraction; + i18nService: I18nServiceAbstraction; platformUtilsService: PlatformUtilsServiceAbstraction; utilsService: UtilsServiceAbstraction; constantsService: ConstantsService; @@ -117,12 +115,10 @@ export default class MainBackground { this.utilsService = new UtilsService(); this.messagingService = new BrowserMessagingService(); this.platformUtilsService = new BrowserPlatformUtilsService(this.messagingService); - const delayi18nLoad = this.platformUtilsService.isEdge() || this.platformUtilsService.isSafari() ? 1000 : 0; this.storageService = new BrowserStorageService(this.platformUtilsService, false); this.secureStorageService = new BrowserStorageService(this.platformUtilsService, true); - this.i18nService = i18nService(this.platformUtilsService); - this.i18n2Service = new I18n2Service(window.navigator.language, this.i18nService); - this.constantsService = new ConstantsService(this.i18nService, delayi18nLoad); + this.i18nService = new I18nService(BrowserApi.getUILanguage(window), + BrowserApi.isSafariApi ? './_locales/' : null); this.cryptoService = new CryptoService(this.storageService, this.secureStorageService); this.tokenService = new TokenService(this.storageService); this.appIdService = new AppIdService(this.storageService); @@ -132,11 +128,11 @@ export default class MainBackground { this.userService = new UserService(this.tokenService, this.storageService); this.settingsService = new SettingsService(this.userService, this.storageService); this.cipherService = new CipherService(this.cryptoService, this.userService, this.settingsService, - this.apiService, this.storageService, this.i18n2Service, this.platformUtilsService, this.utilsService); + this.apiService, this.storageService, this.i18nService, this.platformUtilsService, this.utilsService); this.folderService = new FolderService(this.cryptoService, this.userService, - () => this.i18nService.noneFolder, this.apiService, this.storageService, this.i18n2Service); + () => this.i18nService.t('noneFolder'), this.apiService, this.storageService, this.i18nService); this.collectionService = new CollectionService(this.cryptoService, this.userService, this.storageService, - this.i18n2Service); + this.i18nService); this.lockService = new LockService(this.cipherService, this.folderService, this.collectionService, this.cryptoService, this.platformUtilsService, this.storageService, this.messagingService, async () => { await this.setIcon(); @@ -342,7 +338,7 @@ export default class MainBackground { id: 'autofill', parentId: 'root', contexts: ['all'], - title: this.i18nService.autoFill, + title: this.i18nService.t('autoFill'), }); // Firefox & Edge do not support writing to the clipboard from background @@ -352,7 +348,7 @@ export default class MainBackground { id: 'copy-username', parentId: 'root', contexts: ['all'], - title: this.i18nService.copyUsername, + title: this.i18nService.t('copyUsername'), }); await this.contextMenusCreate({ @@ -360,7 +356,7 @@ export default class MainBackground { id: 'copy-password', parentId: 'root', contexts: ['all'], - title: this.i18nService.copyPassword, + title: this.i18nService.t('copyPassword'), }); await this.contextMenusCreate({ @@ -373,7 +369,7 @@ export default class MainBackground { id: 'generate-password', parentId: 'root', contexts: ['all'], - title: this.i18nService.generatePasswordCopied, + title: this.i18nService.t('generatePasswordCopied'), }); } @@ -411,7 +407,7 @@ export default class MainBackground { theText = '9+'; } else { if (contextMenuEnabled) { - await this.loadNoLoginsContextMenuOptions(this.i18nService.noMatchingLogins); + await this.loadNoLoginsContextMenuOptions(this.i18nService.t('noMatchingLogins')); } } @@ -424,7 +420,7 @@ export default class MainBackground { private async loadMenuAndUpdateBadgeForLockedState(contextMenuEnabled: boolean) { if (contextMenuEnabled) { - await this.loadNoLoginsContextMenuOptions(this.i18nService.vaultLocked); + await this.loadNoLoginsContextMenuOptions(this.i18nService.t('vaultLocked')); } const tabs = await BrowserApi.getActiveTabs(); diff --git a/src/background/runtime.background.ts b/src/background/runtime.background.ts index 1af8b549cb..30c5426b45 100644 --- a/src/background/runtime.background.ts +++ b/src/background/runtime.background.ts @@ -7,6 +7,8 @@ import { LoginView } from 'jslib/models/view/loginView'; import { ConstantsService } from 'jslib/services/constants.service'; import { UtilsService } from 'jslib/services/utils.service'; +import { I18nService } from 'jslib/abstractions/i18n.service'; + import { Analytics } from 'jslib/misc'; import { @@ -30,7 +32,7 @@ export default class RuntimeBackground { constructor(private main: MainBackground, private autofillService: AutofillService, private cipherService: CipherService, private platformUtilsService: BrowserPlatformUtilsService, - private storageService: StorageService, private i18nService: any, private analytics: Analytics) { + private storageService: StorageService, private i18nService: I18nService, private analytics: Analytics) { this.isSafari = this.platformUtilsService.isSafari(); this.runtime = this.isSafari ? safari.application : chrome.runtime; @@ -357,13 +359,13 @@ export default class RuntimeBackground { ConstantsService.enableAutoFillOnPageLoadKey); } else if (responseCommand === 'notificationBarFrameDataResponse') { responseData.i18n = { - appName: this.i18nService.appName, - close: this.i18nService.close, - yes: this.i18nService.yes, - never: this.i18nService.never, - notificationAddSave: this.i18nService.notificationAddSave, - notificationNeverSave: this.i18nService.notificationNeverSave, - notificationAddDesc: this.i18nService.notificationAddDesc, + appName: this.i18nService.t('appName'), + close: this.i18nService.t('close'), + yes: this.i18nService.t('yes'), + never: this.i18nService.t('never'), + notificationAddSave: this.i18nService.t('notificationAddSave'), + notificationNeverSave: this.i18nService.t('notificationNeverSave'), + notificationAddDesc: this.i18nService.t('notificationAddDesc'), }; } diff --git a/src/browser/browserApi.ts b/src/browser/browserApi.ts index 4fcd259957..8a6b34dad5 100644 --- a/src/browser/browserApi.ts +++ b/src/browser/browserApi.ts @@ -1,6 +1,6 @@ export class BrowserApi { static isSafariApi: boolean = (typeof safari !== 'undefined') && - navigator.userAgent.indexOf(' Safari/') !== -1 && navigator.userAgent.indexOf('Chrome') === -1; + navigator.userAgent.indexOf(' Safari/') !== -1 && navigator.userAgent.indexOf('Chrome') === -1; static isChromeApi: boolean = !BrowserApi.isSafariApi && (typeof chrome !== 'undefined'); static async getTabFromCurrentWindowId(): Promise { @@ -264,4 +264,12 @@ export class BrowserApi { static gaFilter() { return BrowserApi.isSafariApi && safari.application.activeBrowserWindow.activeTab.private; } + + static getUILanguage(win: Window) { + if (BrowserApi.isSafariApi) { + return win.navigator.language; + } else { + return chrome.i18n.getUILanguage(); + } + } } diff --git a/src/popup/services/services.module.ts b/src/popup/services/services.module.ts index d5a7057111..613fee51e6 100644 --- a/src/popup/services/services.module.ts +++ b/src/popup/services/services.module.ts @@ -57,7 +57,7 @@ export const messagingService = new BrowserMessagingService(); export const authService = new AuthService(getBgService('cryptoService')(), getBgService('apiService')(), getBgService('userService')(), getBgService('tokenService')(), getBgService('appIdService')(), - getBgService('i18n2Service')(), getBgService('platformUtilsService')(), + getBgService('i18nService')(), getBgService('platformUtilsService')(), getBgService('constantsService')(), messagingService); export function initFactory(i18nService: I18nService, storageService: StorageService, @@ -102,7 +102,7 @@ export function initFactory(i18nService: I18nService, storageService: StorageSer { provide: EnvironmentService, useFactory: getBgService('environmentService'), deps: [] }, { provide: TotpService, useFactory: getBgService('totpService'), deps: [] }, { provide: TokenService, useFactory: getBgService('tokenService'), deps: [] }, - { provide: I18nService, useFactory: getBgService('i18n2Service'), deps: [] }, + { provide: I18nService, useFactory: getBgService('i18nService'), deps: [] }, { provide: UtilsService, useFactory: getBgService('utilsService'), deps: [] }, { provide: CryptoService, useFactory: getBgService('cryptoService'), deps: [] }, { diff --git a/src/services/i18n.service.ts b/src/services/i18n.service.ts index b27ffb76b8..37570c9648 100644 --- a/src/services/i18n.service.ts +++ b/src/services/i18n.service.ts @@ -1,69 +1,121 @@ -import { PlatformUtilsService } from 'jslib/abstractions'; +import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service'; // First locale is the default (English) -const SupportedLocales = [ +const SupportedTranslationLocales = [ 'en', 'cs', 'da', 'de', 'es', 'et', 'fi', 'fr', 'hr', 'hu', 'id', 'it', 'ja', 'nb', 'nl', 'pl', 'pt-BR', 'pt-PT', 'ro', 'ru', 'sk', 'sv', 'tr', 'uk', 'vi', 'zh-CN', 'zh-TW', ]; -export default function i18nService(platformUtilsService: PlatformUtilsService) { - const defaultMessages: any = {}; - const localeMessages: any = {}; +export default class I18nService implements I18nServiceAbstraction { + defaultMessages: any = {}; + localeMessages: any = {}; + locale: string; + translationLocale: string; + collator: Intl.Collator; + inited: boolean; - if (platformUtilsService.isEdge()) { - loadMessages('../_locales/', 'en', localeMessages, - (prop: string, message: string) => chrome.i18n.getMessage(prop)); - return localeMessages; - } + constructor(private systemLanguage: string, private localesDirectory: string) { } - if (platformUtilsService.isSafari()) { - let lang = navigator.language; - if (SupportedLocales.indexOf(lang) === -1) { - lang = lang.slice(0, 2); + async init(locale?: string) { + if (this.inited) { + throw new Error('i18n already initialized.'); + } - if (SupportedLocales.indexOf(lang) === -1) { - lang = SupportedLocales[0]; + this.inited = true; + this.locale = this.translationLocale = locale != null ? locale : this.systemLanguage; + this.collator = new Intl.Collator(this.locale); + + if (SupportedTranslationLocales.indexOf(this.translationLocale) === -1) { + this.translationLocale = this.translationLocale.slice(0, 2); + + if (SupportedTranslationLocales.indexOf(this.translationLocale) === -1) { + this.translationLocale = SupportedTranslationLocales[0]; } } - const dir = './_locales/'; - loadMessages(dir, lang, localeMessages, (prop: string, message: string) => message); - if (lang !== SupportedLocales[0]) { - loadMessages(dir, SupportedLocales[0], defaultMessages, - (prop: string, message: string) => message); + if (this.localesDirectory != null) { + await this.loadMessages(this.localesDirectory, this.translationLocale, this.localeMessages); + if (this.translationLocale !== SupportedTranslationLocales[0]) { + await this.loadMessages(this.localesDirectory, SupportedTranslationLocales[0], this.defaultMessages); + } } } - return new Proxy({}, { - get: (target, name) => { - const id = name.toString(); + t(id: string, p1?: string, p2?: string, p3?: string): string { + return this.translate(id, p1, p2, p3); + } - if (platformUtilsService.isSafari()) { - if (localeMessages.hasOwnProperty(id) && localeMessages[id]) { - return localeMessages[id]; - } else if (defaultMessages.hasOwnProperty(id) && defaultMessages[id]) { - return defaultMessages[id]; + translate(id: string, p1?: string, p2?: string, p3?: string): string { + if (this.localesDirectory == null) { + const placeholders: string[] = []; + if (p1 != null) { + placeholders.push(p1); + } + if (p2 != null) { + placeholders.push(p2); + } + if (p3 != null) { + placeholders.push(p3); + } + + if (placeholders.length) { + return chrome.i18n.getMessage(id, placeholders); + } else { + return chrome.i18n.getMessage(id); + } + } + + let result: string; + if (this.localeMessages.hasOwnProperty(id) && this.localeMessages[id]) { + result = this.localeMessages[id]; + } else if (this.defaultMessages.hasOwnProperty(id) && this.defaultMessages[id]) { + result = this.defaultMessages[id]; + } else { + result = ''; + } + + if (result !== '') { + if (p1 != null) { + result = result.split('__$1__').join(p1); + } + if (p2 != null) { + result = result.split('__$2__').join(p2); + } + if (p3 != null) { + result = result.split('__$3__').join(p3); + } + } + + return result; + } + + private async loadMessages(localesDir: string, locale: string, messagesObj: any): Promise { + const formattedLocale = locale.replace('-', '_'); + const file = await fetch(localesDir + formattedLocale + '/messages.json'); + const locales = await file.json(); + for (const prop in locales) { + if (!locales.hasOwnProperty(prop)) { + continue; + } + messagesObj[prop] = locales[prop].message; + + if (locales[prop].placeholders) { + for (const placeProp in locales[prop].placeholders) { + if (!locales[prop].placeholders.hasOwnProperty(placeProp) || + !locales[prop].placeholders[placeProp].content) { + continue; + } + + const replaceToken = '\\$' + placeProp.toUpperCase() + '\\$'; + let replaceContent = locales[prop].placeholders[placeProp].content; + if (replaceContent === '$1' || replaceContent === '$2' || replaceContent === '$3') { + replaceContent = '__' + replaceContent + '__'; + } + messagesObj[prop] = messagesObj[prop].replace(new RegExp(replaceToken, 'g'), replaceContent); } - return ''; } - - return chrome.i18n.getMessage(id); - }, - set: (target, name, value) => { - return false; - }, - }); -} - -async function loadMessages(localesDir: string, locale: string, messagesObj: any, - messageCallback: (prop: string, message: string) => string): Promise { - const formattedLocale = locale.replace('-', '_'); - const file = await fetch(localesDir + formattedLocale + '/messages.json'); - const locales = await file.json(); - for (const prop in locales) { - if (locales.hasOwnProperty(prop)) { - messagesObj[prop] = messageCallback(prop, locales[prop].message); } } + } diff --git a/src/services/i18n2.service.ts b/src/services/i18n2.service.ts deleted file mode 100644 index 0f9bc077dd..0000000000 --- a/src/services/i18n2.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { I18nService as I18nServiceAbstraction } from 'jslib/abstractions/i18n.service'; - -export default class I18n2Service implements I18nServiceAbstraction { - locale: string; - translationLocale: string; - collator: Intl.Collator; - inited: boolean; - - constructor(private systemLanguage: string, private i18nService: any) { - } - - async init(locale?: string) { - if (this.inited) { - throw new Error('i18n already initialized.'); - } - - this.inited = true; - this.locale = this.translationLocale = locale != null ? locale : this.systemLanguage; - this.collator = new Intl.Collator(this.locale); - } - - t(id: string, p1?: string, p2?: string, p3?: string): string { - return this.translate(id); - } - - translate(id: string, p1?: string, p2?: string, p3?: string): string { - return this.i18nService[id]; - } -}