aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSebastian <sebasjm@gmail.com>2021-06-08 15:56:27 -0300
committerSebastian <sebasjm@gmail.com>2021-06-08 17:18:23 -0300
commit2c5612fd63f766ce19a9f885e5142b04bcf11604 (patch)
treef3287775a47bd7d5ad3a3edbbdd341fb9ab3d1e7
parentb9b6ac0cdac6796139e604f9300879a2cb57ec44 (diff)
moving i18n into taler util
-rw-r--r--packages/taler-util/package.json1
-rw-r--r--packages/taler-util/src/i18n.ts147
-rw-r--r--packages/taler-util/src/index.ts4
-rw-r--r--packages/taler-util/src/logging.ts100
-rw-r--r--pnpm-lock.yaml3
5 files changed, 253 insertions, 2 deletions
diff --git a/packages/taler-util/package.json b/packages/taler-util/package.json
index a1c769e8f..977a35c6f 100644
--- a/packages/taler-util/package.json
+++ b/packages/taler-util/package.json
@@ -36,6 +36,7 @@
"typescript": "^4.2.3"
},
"dependencies": {
+ "jed": "^1.1.1",
"tslib": "^2.1.0"
},
"ava": {
diff --git a/packages/taler-util/src/i18n.ts b/packages/taler-util/src/i18n.ts
new file mode 100644
index 000000000..4253eb227
--- /dev/null
+++ b/packages/taler-util/src/i18n.ts
@@ -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
+}
+
diff --git a/packages/taler-util/src/index.ts b/packages/taler-util/src/index.ts
index 3416c7d12..25a24fa18 100644
--- a/packages/taler-util/src/index.ts
+++ b/packages/taler-util/src/index.ts
@@ -16,4 +16,6 @@ export * from "./talerTypes.js";
export * from "./taleruri.js";
export * from "./time.js";
export * from "./transactionsTypes.js";
-export * from "./walletTypes.js"; \ No newline at end of file
+export * from "./walletTypes.js";
+export * from "./i18n.js";
+export * from "./logging.js"; \ No newline at end of file
diff --git a/packages/taler-util/src/logging.ts b/packages/taler-util/src/logging.ts
new file mode 100644
index 000000000..4f48e24da
--- /dev/null
+++ b/packages/taler-util/src/logging.ts
@@ -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,
+ );
+ }
+ }
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 1cf013b82..10198d6af 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -46,11 +46,13 @@ importers:
'@types/node': ^14.14.22
ava: ^3.15.0
esbuild: ^0.9.2
+ jed: ^1.1.1
prettier: ^2.2.1
rimraf: ^3.0.2
tslib: ^2.1.0
typescript: ^4.2.3
dependencies:
+ jed: 1.1.1
tslib: 2.1.0
devDependencies:
'@types/node': 14.14.34
@@ -11583,7 +11585,6 @@ packages:
/jed/1.1.1:
resolution: {integrity: sha1-elSbvZ/+FYWwzQoZHiAwVb7ldLQ=}
- dev: true
/jest-changed-files/26.6.2:
resolution: {integrity: sha512-fDS7szLcY9sCtIip8Fjry9oGf3I2ht/QT21bAHm5Dmf0mD4X3ReNUf17y+bO6fR8WgbIZTlbyG1ak/53cbRzKQ==}