moving i18n into taler util
This commit is contained in:
parent
b9b6ac0cda
commit
2c5612fd63
@ -36,6 +36,7 @@
|
|||||||
"typescript": "^4.2.3"
|
"typescript": "^4.2.3"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"jed": "^1.1.1",
|
||||||
"tslib": "^2.1.0"
|
"tslib": "^2.1.0"
|
||||||
},
|
},
|
||||||
"ava": {
|
"ava": {
|
||||||
|
147
packages/taler-util/src/i18n.ts
Normal file
147
packages/taler-util/src/i18n.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
// @ts-ignore: no type decl for this library
|
||||||
|
import * as jedLib from "jed";
|
||||||
|
import { Logger } from "./logging";
|
||||||
|
|
||||||
|
const logger = new Logger("i18n/index.ts");
|
||||||
|
|
||||||
|
export let jed: any = undefined;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set up jed library for internationalization,
|
||||||
|
* based on browser language settings.
|
||||||
|
*/
|
||||||
|
export function setupI18n(lang: string, strings: { [s: string]: any }): any {
|
||||||
|
lang = lang.replace("_", "-");
|
||||||
|
|
||||||
|
if (!strings[lang]) {
|
||||||
|
lang = "en-US";
|
||||||
|
logger.warn(`language ${lang} not found, defaulting to english`);
|
||||||
|
}
|
||||||
|
debugger
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert template strings to a msgid
|
||||||
|
*/
|
||||||
|
function toI18nString(stringSeq: ReadonlyArray<string>): string {
|
||||||
|
let s = "";
|
||||||
|
for (let i = 0; i < stringSeq.length; i++) {
|
||||||
|
s += stringSeq[i];
|
||||||
|
if (i < stringSeq.length - 1) {
|
||||||
|
s += `%${i + 1}$s`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internationalize a string template with arbitrary serialized values.
|
||||||
|
*/
|
||||||
|
export function str(stringSeq: TemplateStringsArray, ...values: any[]): string {
|
||||||
|
const s = toI18nString(stringSeq);
|
||||||
|
const tr = jed
|
||||||
|
.translate(s)
|
||||||
|
.ifPlural(1, s)
|
||||||
|
.fetch(...values);
|
||||||
|
return tr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internationalize a string template without serializing
|
||||||
|
*/
|
||||||
|
export function translate(stringSeq: TemplateStringsArray, ...values: any[]): any[] {
|
||||||
|
const s = toI18nString(stringSeq);
|
||||||
|
if (!s) return []
|
||||||
|
const translation: string = jed.ngettext(s, s, 1);
|
||||||
|
return replacePlaceholderWithValues(translation, values)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Internationalize a string template without serializing
|
||||||
|
*/
|
||||||
|
export function Translate({ children, ...rest }: { children: any }): any {
|
||||||
|
const c = [].concat(children);
|
||||||
|
const s = stringifyArray(c);
|
||||||
|
if (!s) return []
|
||||||
|
const translation: string = jed.ngettext(s, s, 1);
|
||||||
|
return replacePlaceholderWithValues(translation, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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);
|
||||||
|
const translation: string = jed.ngettext(s, s, 1);
|
||||||
|
return replacePlaceholderWithValues(translation, array);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function replacePlaceholderWithValues(
|
||||||
|
translation: string,
|
||||||
|
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();
|
||||||
|
console.log("translation lookup", JSON.stringify(s));
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const i18n = {
|
||||||
|
str,
|
||||||
|
Translate,
|
||||||
|
translate
|
||||||
|
}
|
||||||
|
|
@ -17,3 +17,5 @@ export * from "./taleruri.js";
|
|||||||
export * from "./time.js";
|
export * from "./time.js";
|
||||||
export * from "./transactionsTypes.js";
|
export * from "./transactionsTypes.js";
|
||||||
export * from "./walletTypes.js";
|
export * from "./walletTypes.js";
|
||||||
|
export * from "./i18n.js";
|
||||||
|
export * from "./logging.js";
|
100
packages/taler-util/src/logging.ts
Normal file
100
packages/taler-util/src/logging.ts
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
/*
|
||||||
|
This file is part of TALER
|
||||||
|
(C) 2019 GNUnet e.V.
|
||||||
|
|
||||||
|
TALER is free software; you can redistribute it and/or modify it under the
|
||||||
|
terms of the GNU General Public License as published by the Free Software
|
||||||
|
Foundation; either version 3, or (at your option) any later version.
|
||||||
|
|
||||||
|
TALER is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||||
|
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE. See the GNU General Public License for more details.
|
||||||
|
|
||||||
|
You should have received a copy of the GNU General Public License along with
|
||||||
|
TALER; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if we are running under nodejs.
|
||||||
|
*/
|
||||||
|
|
||||||
|
const isNode =
|
||||||
|
typeof process !== "undefined" && typeof process.release !== "undefined" && process.release.name === "node";
|
||||||
|
|
||||||
|
function writeNodeLog(
|
||||||
|
message: any,
|
||||||
|
tag: string,
|
||||||
|
level: string,
|
||||||
|
args: any[],
|
||||||
|
): void {
|
||||||
|
try {
|
||||||
|
process.stderr.write(`${new Date().toISOString()} ${tag} ${level} `);
|
||||||
|
process.stderr.write(`${message}`);
|
||||||
|
if (args.length != 0) {
|
||||||
|
process.stderr.write(" ");
|
||||||
|
process.stderr.write(JSON.stringify(args, undefined, 2));
|
||||||
|
}
|
||||||
|
process.stderr.write("\n");
|
||||||
|
} catch (e) {
|
||||||
|
// This can happen when we're trying to log something that doesn't want to be
|
||||||
|
// converted to a string.
|
||||||
|
process.stderr.write(`${new Date().toISOString()} (logger) FATAL `);
|
||||||
|
if (e instanceof Error) {
|
||||||
|
process.stderr.write("failed to write log: ");
|
||||||
|
process.stderr.write(e.message);
|
||||||
|
}
|
||||||
|
process.stderr.write("\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logger that writes to stderr when running under node,
|
||||||
|
* and uses the corresponding console.* method to log in the browser.
|
||||||
|
*/
|
||||||
|
export class Logger {
|
||||||
|
constructor(private tag: string) {}
|
||||||
|
|
||||||
|
info(message: string, ...args: any[]): void {
|
||||||
|
if (isNode) {
|
||||||
|
writeNodeLog(message, this.tag, "INFO", args);
|
||||||
|
} else {
|
||||||
|
console.info(
|
||||||
|
`${new Date().toISOString()} ${this.tag} INFO ` + message,
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
warn(message: string, ...args: any[]): void {
|
||||||
|
if (isNode) {
|
||||||
|
writeNodeLog(message, this.tag, "WARN", args);
|
||||||
|
} else {
|
||||||
|
console.warn(
|
||||||
|
`${new Date().toISOString()} ${this.tag} INFO ` + message,
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
error(message: string, ...args: any[]): void {
|
||||||
|
if (isNode) {
|
||||||
|
writeNodeLog(message, this.tag, "ERROR", args);
|
||||||
|
} else {
|
||||||
|
console.info(
|
||||||
|
`${new Date().toISOString()} ${this.tag} ERROR ` + message,
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace(message: any, ...args: any[]): void {
|
||||||
|
if (isNode) {
|
||||||
|
writeNodeLog(message, this.tag, "TRACE", args);
|
||||||
|
} else {
|
||||||
|
console.info(
|
||||||
|
`${new Date().toISOString()} ${this.tag} TRACE ` + message,
|
||||||
|
...args,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -46,11 +46,13 @@ importers:
|
|||||||
'@types/node': ^14.14.22
|
'@types/node': ^14.14.22
|
||||||
ava: ^3.15.0
|
ava: ^3.15.0
|
||||||
esbuild: ^0.9.2
|
esbuild: ^0.9.2
|
||||||
|
jed: ^1.1.1
|
||||||
prettier: ^2.2.1
|
prettier: ^2.2.1
|
||||||
rimraf: ^3.0.2
|
rimraf: ^3.0.2
|
||||||
tslib: ^2.1.0
|
tslib: ^2.1.0
|
||||||
typescript: ^4.2.3
|
typescript: ^4.2.3
|
||||||
dependencies:
|
dependencies:
|
||||||
|
jed: 1.1.1
|
||||||
tslib: 2.1.0
|
tslib: 2.1.0
|
||||||
devDependencies:
|
devDependencies:
|
||||||
'@types/node': 14.14.34
|
'@types/node': 14.14.34
|
||||||
@ -11583,7 +11585,6 @@ packages:
|
|||||||
|
|
||||||
/jed/1.1.1:
|
/jed/1.1.1:
|
||||||
resolution: {integrity: sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=}
|
resolution: {integrity: sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=}
|
||||||
dev: true
|
|
||||||
|
|
||||||
/jest-changed-files/26.6.2:
|
/jest-changed-files/26.6.2:
|
||||||
resolution: {integrity: sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==}
|
resolution: {integrity: sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==}
|
||||||
|
Loading…
Reference in New Issue
Block a user