mirror of
https://github.com/bitwarden/browser
synced 2025-12-11 13:53:34 +00:00
[CSA-27] Use new dependency-free locale service for WebAuthN translations (#4557)
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
import { I18nService as BaseI18nService } from "@bitwarden/common/services/i18n.service";
|
import { I18nService as BaseI18nService } from "@bitwarden/common/services/i18n.service";
|
||||||
|
|
||||||
|
import { SupportedTranslationLocales } from "../../translation-constants";
|
||||||
|
|
||||||
export class I18nService extends BaseI18nService {
|
export class I18nService extends BaseI18nService {
|
||||||
constructor(systemLanguage: string, localesDirectory: string) {
|
constructor(systemLanguage: string, localesDirectory: string) {
|
||||||
super(systemLanguage || "en-US", localesDirectory, async (formattedLocale: string) => {
|
super(systemLanguage || "en-US", localesDirectory, async (formattedLocale: string) => {
|
||||||
@@ -14,61 +16,6 @@ export class I18nService extends BaseI18nService {
|
|||||||
return locales;
|
return locales;
|
||||||
});
|
});
|
||||||
|
|
||||||
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
this.supportedTranslationLocales = SupportedTranslationLocales;
|
||||||
this.supportedTranslationLocales = [
|
|
||||||
"en",
|
|
||||||
"af",
|
|
||||||
"ar",
|
|
||||||
"az",
|
|
||||||
"be",
|
|
||||||
"bg",
|
|
||||||
"bn",
|
|
||||||
"bs",
|
|
||||||
"ca",
|
|
||||||
"cs",
|
|
||||||
"da",
|
|
||||||
"de",
|
|
||||||
"el",
|
|
||||||
"en-GB",
|
|
||||||
"en-IN",
|
|
||||||
"eo",
|
|
||||||
"es",
|
|
||||||
"et",
|
|
||||||
"eu",
|
|
||||||
"fi",
|
|
||||||
"fil",
|
|
||||||
"fr",
|
|
||||||
"he",
|
|
||||||
"hi",
|
|
||||||
"hr",
|
|
||||||
"hu",
|
|
||||||
"id",
|
|
||||||
"it",
|
|
||||||
"ja",
|
|
||||||
"ka",
|
|
||||||
"km",
|
|
||||||
"kn",
|
|
||||||
"ko",
|
|
||||||
"lv",
|
|
||||||
"ml",
|
|
||||||
"nb",
|
|
||||||
"nl",
|
|
||||||
"nn",
|
|
||||||
"pl",
|
|
||||||
"pt-PT",
|
|
||||||
"pt-BR",
|
|
||||||
"ro",
|
|
||||||
"ru",
|
|
||||||
"si",
|
|
||||||
"sk",
|
|
||||||
"sl",
|
|
||||||
"sr",
|
|
||||||
"sv",
|
|
||||||
"tr",
|
|
||||||
"uk",
|
|
||||||
"vi",
|
|
||||||
"zh-CN",
|
|
||||||
"zh-TW",
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
31
apps/web/src/connectors/translation.service.ts
Normal file
31
apps/web/src/connectors/translation.service.ts
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
import { TranslationService as BaseTranslationService } from "@bitwarden/common/services/translation.service";
|
||||||
|
|
||||||
|
import { SupportedTranslationLocales } from "../translation-constants";
|
||||||
|
|
||||||
|
export class TranslationService extends BaseTranslationService {
|
||||||
|
private _translationLocale: string;
|
||||||
|
|
||||||
|
constructor(systemLanguage: string, localesDirectory: string) {
|
||||||
|
super(systemLanguage || "en-US", localesDirectory, async (formattedLocale: string) => {
|
||||||
|
const filePath =
|
||||||
|
this.localesDirectory +
|
||||||
|
"/" +
|
||||||
|
formattedLocale +
|
||||||
|
"/messages.json?cache=" +
|
||||||
|
process.env.CACHE_TAG;
|
||||||
|
const localesResult = await fetch(filePath);
|
||||||
|
const locales = await localesResult.json();
|
||||||
|
return locales;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.supportedTranslationLocales = SupportedTranslationLocales;
|
||||||
|
}
|
||||||
|
|
||||||
|
get translationLocale(): string {
|
||||||
|
return this._translationLocale;
|
||||||
|
}
|
||||||
|
|
||||||
|
set translationLocale(locale: string) {
|
||||||
|
this._translationLocale = locale;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { b64Decode, getQsParam } from "./common";
|
import { b64Decode, getQsParam } from "./common";
|
||||||
import { buildDataString, parseWebauthnJson } from "./common-webauthn";
|
import { buildDataString, parseWebauthnJson } from "./common-webauthn";
|
||||||
|
import { TranslationService } from "./translation.service";
|
||||||
|
|
||||||
require("./webauthn.scss");
|
require("./webauthn.scss");
|
||||||
|
|
||||||
@@ -7,9 +8,8 @@ let parsed = false;
|
|||||||
let webauthnJson: any;
|
let webauthnJson: any;
|
||||||
let parentUrl: string = null;
|
let parentUrl: string = null;
|
||||||
let sentSuccess = false;
|
let sentSuccess = false;
|
||||||
let locale = "en";
|
let locale: string = null;
|
||||||
|
let localeService: TranslationService = null;
|
||||||
let locales: any = {};
|
|
||||||
|
|
||||||
function parseParameters() {
|
function parseParameters() {
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
@@ -24,7 +24,7 @@ function parseParameters() {
|
|||||||
parentUrl = decodeURIComponent(parentUrl);
|
parentUrl = decodeURIComponent(parentUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
locale = getQsParam("locale").replace("-", "_");
|
locale = getQsParam("locale") ?? "en";
|
||||||
|
|
||||||
const version = getQsParam("v");
|
const version = getQsParam("v");
|
||||||
|
|
||||||
@@ -61,18 +61,19 @@ function parseParametersV2() {
|
|||||||
document.addEventListener("DOMContentLoaded", async () => {
|
document.addEventListener("DOMContentLoaded", async () => {
|
||||||
parseParameters();
|
parseParameters();
|
||||||
try {
|
try {
|
||||||
locales = await loadLocales(locale);
|
localeService = new TranslationService(locale, "locales");
|
||||||
} catch {
|
} catch {
|
||||||
// eslint-disable-next-line
|
error("Failed to load the provided locale " + locale);
|
||||||
console.error("Failed to load the locale", locale);
|
localeService = new TranslationService("en", "locales");
|
||||||
locales = await loadLocales("en");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
document.getElementById("msg").innerText = translate("webAuthnFallbackMsg");
|
await localeService.init();
|
||||||
document.getElementById("remember-label").innerText = translate("rememberMe");
|
|
||||||
|
document.getElementById("msg").innerText = localeService.t("webAuthnFallbackMsg");
|
||||||
|
document.getElementById("remember-label").innerText = localeService.t("rememberMe");
|
||||||
|
|
||||||
const button = document.getElementById("webauthn-button");
|
const button = document.getElementById("webauthn-button");
|
||||||
button.innerText = translate("webAuthnAuthenticate");
|
button.innerText = localeService.t("webAuthnAuthenticate");
|
||||||
button.onclick = start;
|
button.onclick = start;
|
||||||
|
|
||||||
document.getElementById("spinner").classList.add("d-none");
|
document.getElementById("spinner").classList.add("d-none");
|
||||||
@@ -81,23 +82,13 @@ document.addEventListener("DOMContentLoaded", async () => {
|
|||||||
content.classList.remove("d-none");
|
content.classList.remove("d-none");
|
||||||
});
|
});
|
||||||
|
|
||||||
async function loadLocales(newLocale: string) {
|
|
||||||
const filePath = `locales/${newLocale}/messages.json?cache=${process.env.CACHE_TAG}`;
|
|
||||||
const localesResult = await fetch(filePath);
|
|
||||||
return await localesResult.json();
|
|
||||||
}
|
|
||||||
|
|
||||||
function translate(id: string) {
|
|
||||||
return locales[id]?.message || "";
|
|
||||||
}
|
|
||||||
|
|
||||||
function start() {
|
function start() {
|
||||||
if (sentSuccess) {
|
if (sentSuccess) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!("credentials" in navigator)) {
|
if (!("credentials" in navigator)) {
|
||||||
error(translate("webAuthnNotSupported"));
|
error(localeService.t("webAuthnNotSupported"));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,7 +124,7 @@ async function initWebAuthn(obj: any) {
|
|||||||
window.postMessage({ command: "webAuthnResult", data: dataString, remember: remember }, "*");
|
window.postMessage({ command: "webAuthnResult", data: dataString, remember: remember }, "*");
|
||||||
|
|
||||||
sentSuccess = true;
|
sentSuccess = true;
|
||||||
success(translate("webAuthnSuccess"));
|
success(localeService.t("webAuthnSuccess"));
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
error(err);
|
error(err);
|
||||||
}
|
}
|
||||||
|
|||||||
56
apps/web/src/translation-constants.ts
Normal file
56
apps/web/src/translation-constants.ts
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
// Please leave 'en' where it is, as it's our fallback language in case no translation can be found
|
||||||
|
export const SupportedTranslationLocales: string[] = [
|
||||||
|
"en",
|
||||||
|
"af",
|
||||||
|
"ar",
|
||||||
|
"az",
|
||||||
|
"be",
|
||||||
|
"bg",
|
||||||
|
"bn",
|
||||||
|
"bs",
|
||||||
|
"ca",
|
||||||
|
"cs",
|
||||||
|
"da",
|
||||||
|
"de",
|
||||||
|
"el",
|
||||||
|
"en-GB",
|
||||||
|
"en-IN",
|
||||||
|
"eo",
|
||||||
|
"es",
|
||||||
|
"et",
|
||||||
|
"eu",
|
||||||
|
"fi",
|
||||||
|
"fil",
|
||||||
|
"fr",
|
||||||
|
"he",
|
||||||
|
"hi",
|
||||||
|
"hr",
|
||||||
|
"hu",
|
||||||
|
"id",
|
||||||
|
"it",
|
||||||
|
"ja",
|
||||||
|
"ka",
|
||||||
|
"km",
|
||||||
|
"kn",
|
||||||
|
"ko",
|
||||||
|
"lv",
|
||||||
|
"ml",
|
||||||
|
"nb",
|
||||||
|
"nl",
|
||||||
|
"nn",
|
||||||
|
"pl",
|
||||||
|
"pt-PT",
|
||||||
|
"pt-BR",
|
||||||
|
"ro",
|
||||||
|
"ru",
|
||||||
|
"si",
|
||||||
|
"sk",
|
||||||
|
"sl",
|
||||||
|
"sr",
|
||||||
|
"sv",
|
||||||
|
"tr",
|
||||||
|
"uk",
|
||||||
|
"vi",
|
||||||
|
"zh-CN",
|
||||||
|
"zh-TW",
|
||||||
|
];
|
||||||
@@ -1,11 +1,7 @@
|
|||||||
import { Observable } from "rxjs";
|
import { Observable } from "rxjs";
|
||||||
|
|
||||||
export abstract class I18nService {
|
import { TranslationService } from "./translation.service";
|
||||||
|
|
||||||
|
export abstract class I18nService extends TranslationService {
|
||||||
locale$: Observable<string>;
|
locale$: Observable<string>;
|
||||||
supportedTranslationLocales: string[];
|
|
||||||
translationLocale: string;
|
|
||||||
collator: Intl.Collator;
|
|
||||||
localeNames: Map<string, string>;
|
|
||||||
t: (id: string, p1?: string | number, p2?: string | number, p3?: string | number) => string;
|
|
||||||
translate: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
|
||||||
}
|
}
|
||||||
|
|||||||
8
libs/common/src/abstractions/translation.service.ts
Normal file
8
libs/common/src/abstractions/translation.service.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export abstract class TranslationService {
|
||||||
|
supportedTranslationLocales: string[];
|
||||||
|
translationLocale: string;
|
||||||
|
collator: Intl.Collator;
|
||||||
|
localeNames: Map<string, string>;
|
||||||
|
t: (id: string, p1?: string | number, p2?: string | number, p3?: string | number) => string;
|
||||||
|
translate: (id: string, p1?: string, p2?: string, p3?: string) => string;
|
||||||
|
}
|
||||||
@@ -2,184 +2,27 @@ import { Observable, ReplaySubject } from "rxjs";
|
|||||||
|
|
||||||
import { I18nService as I18nServiceAbstraction } from "../abstractions/i18n.service";
|
import { I18nService as I18nServiceAbstraction } from "../abstractions/i18n.service";
|
||||||
|
|
||||||
export class I18nService implements I18nServiceAbstraction {
|
import { TranslationService } from "./translation.service";
|
||||||
protected _locale = new ReplaySubject<string>(1);
|
|
||||||
locale$: Observable<string> = this._locale.asObservable();
|
|
||||||
// First locale is the default (English)
|
|
||||||
supportedTranslationLocales: string[] = ["en"];
|
|
||||||
defaultLocale = "en";
|
|
||||||
translationLocale: string;
|
|
||||||
collator: Intl.Collator;
|
|
||||||
localeNames = new Map<string, string>([
|
|
||||||
["af", "Afrikaans"],
|
|
||||||
["ar", "العربية الفصحى"],
|
|
||||||
["az", "Azərbaycanca"],
|
|
||||||
["be", "Беларуская"],
|
|
||||||
["bg", "български"],
|
|
||||||
["bn", "বাংলা"],
|
|
||||||
["bs", "bosanski jezik"],
|
|
||||||
["ca", "català"],
|
|
||||||
["cs", "čeština"],
|
|
||||||
["da", "dansk"],
|
|
||||||
["de", "Deutsch"],
|
|
||||||
["el", "Ελληνικά"],
|
|
||||||
["en", "English"],
|
|
||||||
["en-GB", "English (British)"],
|
|
||||||
["en-IN", "English (India)"],
|
|
||||||
["eo", "Esperanto"],
|
|
||||||
["es", "español"],
|
|
||||||
["et", "eesti"],
|
|
||||||
["eu", "euskara"],
|
|
||||||
["fa", "فارسی"],
|
|
||||||
["fi", "suomi"],
|
|
||||||
["fil", "Wikang Filipino"],
|
|
||||||
["fr", "français"],
|
|
||||||
["he", "עברית"],
|
|
||||||
["hi", "हिन्दी"],
|
|
||||||
["hr", "hrvatski"],
|
|
||||||
["hu", "magyar"],
|
|
||||||
["id", "Bahasa Indonesia"],
|
|
||||||
["it", "italiano"],
|
|
||||||
["ja", "日本語"],
|
|
||||||
["ka", "ქართული"],
|
|
||||||
["km", "ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ"],
|
|
||||||
["kn", "ಕನ್ನಡ"],
|
|
||||||
["ko", "한국어"],
|
|
||||||
["lt", "lietuvių kalba"],
|
|
||||||
["lv", "Latvietis"],
|
|
||||||
["me", "црногорски"],
|
|
||||||
["ml", "മലയാളം"],
|
|
||||||
["nb", "norsk (bokmål)"],
|
|
||||||
["nl", "Nederlands"],
|
|
||||||
["nn", "Norsk Nynorsk"],
|
|
||||||
["pl", "polski"],
|
|
||||||
["pt-BR", "português do Brasil"],
|
|
||||||
["pt-PT", "português"],
|
|
||||||
["ro", "română"],
|
|
||||||
["ru", "русский"],
|
|
||||||
["si", "සිංහල"],
|
|
||||||
["sk", "slovenčina"],
|
|
||||||
["sl", "Slovenski jezik, Slovenščina"],
|
|
||||||
["sr", "Српски"],
|
|
||||||
["sv", "svenska"],
|
|
||||||
["th", "ไทย"],
|
|
||||||
["tr", "Türkçe"],
|
|
||||||
["uk", "українська"],
|
|
||||||
["vi", "Tiếng Việt"],
|
|
||||||
["zh-CN", "中文(中国大陆)"],
|
|
||||||
["zh-TW", "中文(台灣)"],
|
|
||||||
]);
|
|
||||||
|
|
||||||
protected inited: boolean;
|
export class I18nService extends TranslationService implements I18nServiceAbstraction {
|
||||||
protected defaultMessages: any = {};
|
protected _locale = new ReplaySubject<string>(1);
|
||||||
protected localeMessages: any = {};
|
private _translationLocale: string;
|
||||||
|
locale$: Observable<string> = this._locale.asObservable();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
protected systemLanguage: string,
|
protected systemLanguage: string,
|
||||||
protected localesDirectory: string,
|
protected localesDirectory: string,
|
||||||
protected getLocalesJson: (formattedLocale: string) => Promise<any>
|
protected getLocalesJson: (formattedLocale: string) => Promise<any>
|
||||||
) {
|
) {
|
||||||
this.systemLanguage = systemLanguage.replace("_", "-");
|
super(systemLanguage, localesDirectory, getLocalesJson);
|
||||||
}
|
}
|
||||||
|
|
||||||
async init(locale?: string) {
|
get translationLocale(): string {
|
||||||
if (this.inited) {
|
return this._translationLocale;
|
||||||
throw new Error("i18n already initialized.");
|
|
||||||
}
|
|
||||||
if (this.supportedTranslationLocales == null || this.supportedTranslationLocales.length === 0) {
|
|
||||||
throw new Error("supportedTranslationLocales not set.");
|
|
||||||
}
|
|
||||||
|
|
||||||
this.inited = true;
|
|
||||||
this.translationLocale = locale != null ? locale : this.systemLanguage;
|
|
||||||
this._locale.next(this.translationLocale);
|
|
||||||
|
|
||||||
try {
|
|
||||||
this.collator = new Intl.Collator(this.translationLocale, {
|
|
||||||
numeric: true,
|
|
||||||
sensitivity: "base",
|
|
||||||
});
|
|
||||||
} catch {
|
|
||||||
this.collator = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) {
|
|
||||||
this.translationLocale = this.translationLocale.slice(0, 2);
|
|
||||||
|
|
||||||
if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) {
|
|
||||||
this.translationLocale = this.defaultLocale;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (this.localesDirectory != null) {
|
|
||||||
await this.loadMessages(this.translationLocale, this.localeMessages);
|
|
||||||
if (this.translationLocale !== this.defaultLocale) {
|
|
||||||
await this.loadMessages(this.defaultLocale, this.defaultMessages);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
t(id: string, p1?: string, p2?: string, p3?: string): string {
|
set translationLocale(locale: string) {
|
||||||
return this.translate(id, p1, p2, p3);
|
this._translationLocale = locale;
|
||||||
}
|
this._locale.next(locale);
|
||||||
|
|
||||||
translate(id: string, p1?: string | number, p2?: string | number, p3?: string | number): string {
|
|
||||||
let result: string;
|
|
||||||
// eslint-disable-next-line
|
|
||||||
if (this.localeMessages.hasOwnProperty(id) && this.localeMessages[id]) {
|
|
||||||
result = this.localeMessages[id];
|
|
||||||
// eslint-disable-next-line
|
|
||||||
} 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.toString());
|
|
||||||
}
|
|
||||||
if (p2 != null) {
|
|
||||||
result = result.split("__$2__").join(p2.toString());
|
|
||||||
}
|
|
||||||
if (p3 != null) {
|
|
||||||
result = result.split("__$3__").join(p3.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
private async loadMessages(locale: string, messagesObj: any): Promise<any> {
|
|
||||||
const formattedLocale = locale.replace("-", "_");
|
|
||||||
const locales = await this.getLocalesJson(formattedLocale);
|
|
||||||
for (const prop in locales) {
|
|
||||||
// eslint-disable-next-line
|
|
||||||
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) || // eslint-disable-line
|
|
||||||
!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
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
180
libs/common/src/services/translation.service.ts
Normal file
180
libs/common/src/services/translation.service.ts
Normal file
@@ -0,0 +1,180 @@
|
|||||||
|
import { TranslationService as TranslationServiceAbstraction } from "../abstractions/translation.service";
|
||||||
|
|
||||||
|
export abstract class TranslationService implements TranslationServiceAbstraction {
|
||||||
|
// First locale is the default (English)
|
||||||
|
supportedTranslationLocales: string[] = ["en"];
|
||||||
|
defaultLocale = "en";
|
||||||
|
abstract translationLocale: string;
|
||||||
|
collator: Intl.Collator;
|
||||||
|
localeNames = new Map<string, string>([
|
||||||
|
["af", "Afrikaans"],
|
||||||
|
["ar", "العربية الفصحى"],
|
||||||
|
["az", "Azərbaycanca"],
|
||||||
|
["be", "Беларуская"],
|
||||||
|
["bg", "български"],
|
||||||
|
["bn", "বাংলা"],
|
||||||
|
["bs", "bosanski jezik"],
|
||||||
|
["ca", "català"],
|
||||||
|
["cs", "čeština"],
|
||||||
|
["da", "dansk"],
|
||||||
|
["de", "Deutsch"],
|
||||||
|
["el", "Ελληνικά"],
|
||||||
|
["en", "English"],
|
||||||
|
["en-GB", "English (British)"],
|
||||||
|
["en-IN", "English (India)"],
|
||||||
|
["eo", "Esperanto"],
|
||||||
|
["es", "español"],
|
||||||
|
["et", "eesti"],
|
||||||
|
["eu", "euskara"],
|
||||||
|
["fa", "فارسی"],
|
||||||
|
["fi", "suomi"],
|
||||||
|
["fil", "Wikang Filipino"],
|
||||||
|
["fr", "français"],
|
||||||
|
["he", "עברית"],
|
||||||
|
["hi", "हिन्दी"],
|
||||||
|
["hr", "hrvatski"],
|
||||||
|
["hu", "magyar"],
|
||||||
|
["id", "Bahasa Indonesia"],
|
||||||
|
["it", "italiano"],
|
||||||
|
["ja", "日本語"],
|
||||||
|
["ka", "ქართული"],
|
||||||
|
["km", "ខ្មែរ, ខេមរភាសា, ភាសាខ្មែរ"],
|
||||||
|
["kn", "ಕನ್ನಡ"],
|
||||||
|
["ko", "한국어"],
|
||||||
|
["lt", "lietuvių kalba"],
|
||||||
|
["lv", "Latvietis"],
|
||||||
|
["me", "црногорски"],
|
||||||
|
["ml", "മലയാളം"],
|
||||||
|
["nb", "norsk (bokmål)"],
|
||||||
|
["nl", "Nederlands"],
|
||||||
|
["nn", "Norsk Nynorsk"],
|
||||||
|
["pl", "polski"],
|
||||||
|
["pt-BR", "português do Brasil"],
|
||||||
|
["pt-PT", "português"],
|
||||||
|
["ro", "română"],
|
||||||
|
["ru", "русский"],
|
||||||
|
["si", "සිංහල"],
|
||||||
|
["sk", "slovenčina"],
|
||||||
|
["sl", "Slovenski jezik, Slovenščina"],
|
||||||
|
["sr", "Српски"],
|
||||||
|
["sv", "svenska"],
|
||||||
|
["th", "ไทย"],
|
||||||
|
["tr", "Türkçe"],
|
||||||
|
["uk", "українська"],
|
||||||
|
["vi", "Tiếng Việt"],
|
||||||
|
["zh-CN", "中文(中国大陆)"],
|
||||||
|
["zh-TW", "中文(台灣)"],
|
||||||
|
]);
|
||||||
|
|
||||||
|
protected inited: boolean;
|
||||||
|
protected defaultMessages: any = {};
|
||||||
|
protected localeMessages: any = {};
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
protected systemLanguage: string,
|
||||||
|
protected localesDirectory: string,
|
||||||
|
protected getLocalesJson: (formattedLocale: string) => Promise<any>
|
||||||
|
) {
|
||||||
|
this.systemLanguage = systemLanguage.replace("_", "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
async init(locale?: string) {
|
||||||
|
if (this.inited) {
|
||||||
|
throw new Error("i18n already initialized.");
|
||||||
|
}
|
||||||
|
if (this.supportedTranslationLocales == null || this.supportedTranslationLocales.length === 0) {
|
||||||
|
throw new Error("supportedTranslationLocales not set.");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.inited = true;
|
||||||
|
this.translationLocale = locale != null ? locale : this.systemLanguage;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.collator = new Intl.Collator(this.translationLocale, {
|
||||||
|
numeric: true,
|
||||||
|
sensitivity: "base",
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
this.collator = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) {
|
||||||
|
this.translationLocale = this.translationLocale.slice(0, 2);
|
||||||
|
|
||||||
|
if (this.supportedTranslationLocales.indexOf(this.translationLocale) === -1) {
|
||||||
|
this.translationLocale = this.defaultLocale;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.localesDirectory != null) {
|
||||||
|
await this.loadMessages(this.translationLocale, this.localeMessages);
|
||||||
|
if (this.translationLocale !== this.defaultLocale) {
|
||||||
|
await this.loadMessages(this.defaultLocale, this.defaultMessages);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
t(id: string, p1?: string, p2?: string, p3?: string): string {
|
||||||
|
return this.translate(id, p1, p2, p3);
|
||||||
|
}
|
||||||
|
|
||||||
|
translate(id: string, p1?: string | number, p2?: string | number, p3?: string | number): string {
|
||||||
|
let result: string;
|
||||||
|
// eslint-disable-next-line
|
||||||
|
if (this.localeMessages.hasOwnProperty(id) && this.localeMessages[id]) {
|
||||||
|
result = this.localeMessages[id];
|
||||||
|
// eslint-disable-next-line
|
||||||
|
} 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.toString());
|
||||||
|
}
|
||||||
|
if (p2 != null) {
|
||||||
|
result = result.split("__$2__").join(p2.toString());
|
||||||
|
}
|
||||||
|
if (p3 != null) {
|
||||||
|
result = result.split("__$3__").join(p3.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async loadMessages(locale: string, messagesObj: any): Promise<any> {
|
||||||
|
const formattedLocale = locale.replace("-", "_");
|
||||||
|
const locales = await this.getLocalesJson(formattedLocale);
|
||||||
|
for (const prop in locales) {
|
||||||
|
// eslint-disable-next-line
|
||||||
|
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) || // eslint-disable-line
|
||||||
|
!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
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user