2021-06-08 20:56:27 +02:00
|
|
|
// @ts-ignore: no type decl for this library
|
|
|
|
import * as jedLib from "jed";
|
2021-06-14 16:08:58 +02:00
|
|
|
import { Logger } from "./logging.js";
|
2021-06-08 20:56:27 +02:00
|
|
|
|
|
|
|
const logger = new Logger("i18n/index.ts");
|
|
|
|
|
|
|
|
export let jed: any = undefined;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set up jed library for internationalization,
|
|
|
|
* based on browser language settings.
|
|
|
|
*/
|
2023-01-04 19:45:38 +01:00
|
|
|
export function setupI18n(lang: string, strings: { [s: string]: any }): void {
|
2021-06-08 20:56:27 +02:00
|
|
|
lang = lang.replace("_", "-");
|
|
|
|
|
|
|
|
if (!strings[lang]) {
|
2022-09-21 20:43:35 +02:00
|
|
|
strings[lang] = {};
|
2022-04-26 04:07:46 +02:00
|
|
|
// logger.warn(`language ${lang} not found, defaulting to source strings`);
|
2021-06-08 20:56:27 +02:00
|
|
|
}
|
|
|
|
jed = new jedLib.Jed(strings[lang]);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Use different translations for testing. Should not be used outside
|
|
|
|
* of test cases.
|
|
|
|
*/
|
|
|
|
export function internalSetStrings(langStrings: any): void {
|
|
|
|
jed = new jedLib.Jed(langStrings);
|
|
|
|
}
|
|
|
|
|
2023-01-04 19:45:38 +01:00
|
|
|
declare const __translated: unique symbol;
|
|
|
|
export type TranslatedString = string & { [__translated]: true };
|
|
|
|
|
2021-06-08 20:56:27 +02:00
|
|
|
/**
|
|
|
|
* Convert template strings to a msgid
|
|
|
|
*/
|
2023-01-04 19:45:38 +01:00
|
|
|
function toI18nString(stringSeq: ReadonlyArray<string>): TranslatedString {
|
2021-06-08 20:56:27 +02:00
|
|
|
let s = "";
|
|
|
|
for (let i = 0; i < stringSeq.length; i++) {
|
|
|
|
s += stringSeq[i];
|
|
|
|
if (i < stringSeq.length - 1) {
|
|
|
|
s += `%${i + 1}$s`;
|
|
|
|
}
|
|
|
|
}
|
2023-01-04 19:45:38 +01:00
|
|
|
return s as TranslatedString;
|
2021-06-08 20:56:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internationalize a string template with arbitrary serialized values.
|
|
|
|
*/
|
2022-09-21 20:43:35 +02:00
|
|
|
export function singular(
|
|
|
|
stringSeq: TemplateStringsArray,
|
|
|
|
...values: any[]
|
2023-01-04 19:45:38 +01:00
|
|
|
): TranslatedString {
|
2021-06-08 20:56:27 +02:00
|
|
|
const s = toI18nString(stringSeq);
|
|
|
|
const tr = jed
|
|
|
|
.translate(s)
|
|
|
|
.ifPlural(1, s)
|
|
|
|
.fetch(...values);
|
|
|
|
return tr;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internationalize a string template without serializing
|
|
|
|
*/
|
2021-06-09 15:26:18 +02:00
|
|
|
export function translate(
|
|
|
|
stringSeq: TemplateStringsArray,
|
|
|
|
...values: any[]
|
2023-01-04 19:45:38 +01:00
|
|
|
): TranslatedString[] {
|
2021-06-08 20:56:27 +02:00
|
|
|
const s = toI18nString(stringSeq);
|
2021-06-09 15:26:18 +02:00
|
|
|
if (!s) return [];
|
2023-01-04 19:45:38 +01:00
|
|
|
const translation: TranslatedString = jed.ngettext(s, s, 1);
|
2021-06-09 15:26:18 +02:00
|
|
|
return replacePlaceholderWithValues(translation, values);
|
2021-06-08 20:56:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Internationalize a string template without serializing
|
|
|
|
*/
|
2022-09-21 20:43:35 +02:00
|
|
|
export function Translate({
|
|
|
|
children,
|
|
|
|
debug,
|
|
|
|
}: {
|
|
|
|
children: any;
|
|
|
|
debug?: boolean;
|
|
|
|
}): any {
|
2021-06-08 20:56:27 +02:00
|
|
|
const c = [].concat(children);
|
|
|
|
const s = stringifyArray(c);
|
2021-06-09 15:26:18 +02:00
|
|
|
if (!s) return [];
|
2023-01-04 19:45:38 +01:00
|
|
|
const translation: TranslatedString = jed.ngettext(s, s, 1);
|
2022-02-24 16:41:47 +01:00
|
|
|
if (debug) {
|
2022-09-21 20:43:35 +02:00
|
|
|
console.log("looking for ", s, "got", translation);
|
2022-02-24 16:41:47 +01:00
|
|
|
}
|
2021-06-09 15:26:18 +02:00
|
|
|
return replacePlaceholderWithValues(translation, c);
|
2021-06-08 20:56:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get an internationalized string (based on the globally set, current language)
|
|
|
|
* from a JSON object. Fall back to the default language of the JSON object
|
|
|
|
* if no match exists.
|
|
|
|
*/
|
|
|
|
export function getJsonI18n<K extends string>(
|
|
|
|
obj: Record<K, string>,
|
|
|
|
key: K,
|
|
|
|
): string {
|
|
|
|
return obj[key];
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getTranslatedArray(array: Array<any>) {
|
|
|
|
const s = stringifyArray(array);
|
2023-01-04 19:45:38 +01:00
|
|
|
const translation: TranslatedString = jed.ngettext(s, s, 1);
|
2021-06-08 20:56:27 +02:00
|
|
|
return replacePlaceholderWithValues(translation, array);
|
|
|
|
}
|
|
|
|
|
|
|
|
function replacePlaceholderWithValues(
|
2023-01-04 19:45:38 +01:00
|
|
|
translation: TranslatedString,
|
2021-06-08 20:56:27 +02:00
|
|
|
childArray: Array<any>,
|
|
|
|
): Array<any> {
|
|
|
|
const tr = translation.split(/%(\d+)\$s/);
|
|
|
|
// const childArray = toChildArray(children);
|
|
|
|
// Merge consecutive string children.
|
|
|
|
const placeholderChildren = [];
|
|
|
|
for (let i = 0; i < childArray.length; i++) {
|
|
|
|
const x = childArray[i];
|
|
|
|
if (x === undefined) {
|
|
|
|
continue;
|
|
|
|
} else if (typeof x === "string") {
|
|
|
|
continue;
|
|
|
|
} else {
|
|
|
|
placeholderChildren.push(x);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
const result = [];
|
|
|
|
for (let i = 0; i < tr.length; i++) {
|
|
|
|
if (i % 2 == 0) {
|
|
|
|
// Text
|
|
|
|
result.push(tr[i]);
|
|
|
|
} else {
|
|
|
|
const childIdx = Number.parseInt(tr[i]) - 1;
|
|
|
|
result.push(placeholderChildren[childIdx]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
function stringifyArray(children: Array<any>): string {
|
|
|
|
let n = 1;
|
|
|
|
const ss = children.map((c) => {
|
|
|
|
if (typeof c === "string") {
|
|
|
|
return c;
|
|
|
|
}
|
|
|
|
return `%${n++}$s`;
|
|
|
|
});
|
|
|
|
const s = ss.join("").replace(/ +/g, " ").trim();
|
|
|
|
return s;
|
|
|
|
}
|
|
|
|
|
|
|
|
export const i18n = {
|
2021-07-26 16:27:56 +02:00
|
|
|
str: singular,
|
|
|
|
singular,
|
2021-06-08 20:56:27 +02:00
|
|
|
Translate,
|
2021-06-09 15:26:18 +02:00
|
|
|
translate,
|
|
|
|
};
|