moving strings from wallet-core to web-extension

This commit is contained in:
Sebastian 2021-06-08 16:01:41 -03:00
parent 2e1438eb04
commit 1d55c551bb
No known key found for this signature in database
GPG Key ID: BE4FF68352439FC1
18 changed files with 53 additions and 326 deletions

View File

@ -1,93 +0,0 @@
/*
This file is part of TALER
(C) 2016 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/>
*/
/**
* Translation helpers for React components and template literals.
*/
/**
* Imports.
*/
import { strings } from "./strings";
export { strings } from "./strings";
// @ts-ignore: no type decl for this library
import * as jedLib from "jed";
import { Logger } from "../util/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): any {
lang = lang.replace("_", "-");
if (!strings[lang]) {
lang = "en-US";
logger.warn(`language ${lang} not found, defaulting to english`);
}
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;
}
/**
* 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];
}

View File

@ -14,7 +14,8 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/
import * as core from "@gnu-taler/taler-wallet-core";
import { setupI18n } from "@gnu-taler/taler-util"
import { strings } from '../src/i18n'
const mockConfig = {
backendURL: 'http://demo.taler.net',
@ -45,7 +46,7 @@ export const globalTypes = {
export const decorators = [
(Story, { globals }) => {
core.setupI18n(globals.locale);
setupI18n(globals.locale, strings);
return <Story />
},
// (Story) => <ConfigContextProvider value={mockConfig}> <Story /> </ConfigContextProvider>

View File

@ -1,209 +0,0 @@
/*
This file is part of TALER
(C) 2016 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/>
*/
/**
* Translation helpers for React components and template literals.
*/
/**
* Imports
*/
import * as i18nCore from "@gnu-taler/taler-wallet-core";
import { Component, ComponentChildren, h, JSX, toChildArray, VNode } from "preact";
/**
* 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;
}
export const str = i18nCore.str;
export const internalSetStrings = i18nCore.internalSetStrings;
export const strings = i18nCore.strings;
interface TranslateSwitchProps {
target: number;
}
function stringifyChildren(children: any): string {
let n = 1;
const ss = toChildArray(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;
}
interface TranslateProps {
/**
* Component that the translated element should be wrapped in.
* Defaults to "div".
*/
wrap?: any;
/**
* Props to give to the wrapped component.
*/
wrapProps?: any;
/**
* Translated elements
*/
children: ComponentChildren;
}
function getTranslatedChildren(
translation: string,
children: ComponentChildren,
): ComponentChildren {
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;
}
/**
* Translate text node children of this component.
* If a child component might produce a text node, it must be wrapped
* in a another non-text element.
*
* Example:
* ```
* <Translate>
* Hello. Your score is <span><PlayerScore player={player} /></span>
* </Translate>
* ```
*/
export function Translate({children, wrap, wrapProps}: TranslateProps): VNode {
const s = stringifyChildren(children);
const translation: string = i18nCore.jed.ngettext(s, s, 1);
const result = getTranslatedChildren(translation, children);
if (!wrap) {
return <div>{result}</div>;
}
return h(wrap, wrapProps, result);
}
/**
* Switch translation based on singular or plural based on the target prop.
* Should only contain TranslateSingular and TransplatePlural as children.
*
* Example:
* ```
* <TranslateSwitch target={n}>
* <TranslateSingular>I have {n} apple.</TranslateSingular>
* <TranslatePlural>I have {n} apples.</TranslatePlural>
* </TranslateSwitch>
* ```
*/
export class TranslateSwitch extends Component<
TranslateSwitchProps,
void
> {
render(): JSX.Element {
let singular: VNode<TranslationPluralProps> | undefined;
let plural: VNode<TranslationPluralProps> | undefined;
const children = this.props.children;
if (children) {
toChildArray(children).forEach((child: any) => {
if (child.type === TranslatePlural) {
plural = child;
}
if (child.type === TranslateSingular) {
singular = child;
}
});
}
if (!singular || !plural) {
console.error("translation not found");
return h("span", {}, ["translation not found"]);
}
singular.props.target = this.props.target;
plural.props.target = this.props.target;
// We're looking up the translation based on the
// singular, even if we must use the plural form.
return singular;
}
}
interface TranslationPluralProps {
target: number;
}
/**
* See [[TranslateSwitch]].
*/
export class TranslatePlural extends Component<
TranslationPluralProps,
void
> {
render(): JSX.Element {
const s = stringifyChildren(this.props.children);
const translation = i18nCore.jed.ngettext(s, s, 1);
const result = getTranslatedChildren(translation, this.props.children);
return <div>{result}</div>;
}
}
/**
* See [[TranslateSwitch]].
*/
export class TranslateSingular extends Component<
TranslationPluralProps,
void
> {
render(): JSX.Element {
const s = stringifyChildren(this.props.children);
const translation = i18nCore.jed.ngettext(s, s, this.props.target);
const result = getTranslatedChildren(translation, this.props.children);
return <div>{result}</div>;
}
}

View File

@ -0,0 +1,24 @@
/*
This file is part of TALER
(C) 2016 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/>
*/
/**
* Translation helpers for React components and template literals.
*/
/**
* Imports.
*/
export { strings } from "./strings";

View File

@ -22,14 +22,14 @@
/**
* Imports.
*/
import * as i18n from "../i18n";
// import * as i18n from "../i18n";
import { renderAmount, ProgressButton } from "../renderHtml";
import * as wxApi from "../wxApi";
import { useState, useEffect } from "preact/hooks";
import { getJsonI18n } from "@gnu-taler/taler-wallet-core";
import { getJsonI18n, i18n } from "@gnu-taler/taler-util";
import {
PreparePayResult,
ConfirmPayResult,
@ -171,19 +171,19 @@ export function TalerPayDialog({ talerPayUri }: Props): JSX.Element {
return (
<div>
<p>
<i18n.Translate wrap="p">
<i18n.Translate>
The merchant <span>{merchantName}</span> offers you to purchase:
</i18n.Translate>
<div style={{ textAlign: "center" }}>
<strong>{contractTerms.summary}</strong>
</div>
{totalFees ? (
<i18n.Translate wrap="p">
<i18n.Translate>
The total price is <span>{amount} </span>
(plus <span>{renderAmount(totalFees)}</span> fees).
</i18n.Translate>
) : (
<i18n.Translate wrap="p">
<i18n.Translate>
The total price is <span>{amount}</span>.
</i18n.Translate>
)}

View File

@ -19,8 +19,13 @@
* @author Sebastian Javier Marchano (sebasjm)
*/
import { PaymentStatus, TransactionPayment, TransactionType, TransactionWithdrawal, TransactionDeposit, TransactionRefresh, TransactionTip, TransactionRefund, WithdrawalType, TransactionCommon } from '@gnu-taler/taler-util';
import { Fragment, h } from 'preact';
import {
PaymentStatus,
TransactionCommon, TransactionDeposit, TransactionPayment,
TransactionRefresh, TransactionRefund, TransactionTip, TransactionType,
TransactionWithdrawal,
WithdrawalType
} from '@gnu-taler/taler-util';
import { WalletTransactionView as Component } from './popup';
export default {

View File

@ -37,13 +37,13 @@ import {
AmountString,
Timestamp,
amountFractionalBase,
i18n,
} from "@gnu-taler/taler-util";
import { format } from "date-fns";
import { Component, ComponentChildren, Fragment, JSX } from "preact";
import { route, Route, Router } from 'preact-router';
import { Match } from 'preact-router/match';
import { useEffect, useState } from "preact/hooks";
import * as i18n from "../i18n";
import { PageLink, renderAmount } from "../renderHtml";
import * as wxApi from "../wxApi";
import { PermissionsCheckbox, useExtendedPermissions, Diagnostics } from "./welcome";
@ -92,10 +92,10 @@ function bigAmount(amount: AmountJson): JSX.Element {
function EmptyBalanceView(): JSX.Element {
return (
<i18n.Translate wrap="p">
<p><i18n.Translate>
You have no balance to show. Need some{" "}
<PageLink pageName="/welcome">help</PageLink> getting started?
</i18n.Translate>
</i18n.Translate></p>
);
}
@ -166,13 +166,13 @@ class WalletBalanceView extends Component<any, any> {
if (!Amounts.isZero(pendingIncoming)) {
incoming = (
<i18n.Translate wrap="span">
<span><i18n.Translate>
<span style={{ color: "darkgreen" }}>
{"+"}
{renderAmount(entry.pendingIncoming)}
</span>{" "}
incoming
</i18n.Translate>
</i18n.Translate></span>
);
}
@ -436,14 +436,14 @@ interface WalletTransactionProps {
export function WalletTransactionView({ transaction, onDelete, onBack }: WalletTransactionProps) {
if (!transaction) {
return <div>Loading ...</div>;
return <div><i18n.Translate>Loading ...</i18n.Translate></div>;
}
function Footer() {
return <footer style={{ marginTop: 'auto', display: 'flex' }}>
<button onClick={onBack}>back</button>
<button onClick={onBack}><i18n.Translate>back</i18n.Translate></button>
<div style={{ width: '100%', flexDirection: 'row', justifyContent: 'flex-end', display: 'flex' }}>
<button onClick={onDelete}>remove</button>
<button onClick={onDelete}><i18n.Translate>remove</i18n.Translate></button>
</div>

View File

@ -21,8 +21,7 @@
* @author Florian Dold
*/
import * as i18n from "../i18n";
import { i18n } from '@gnu-taler/taler-util'
import { renderAmount } from "../renderHtml";
import { useState, useEffect } from "preact/hooks";
@ -64,11 +63,11 @@ export function View({ talerWithdrawUri, details, cancelled, selectedExchange, a
return (
<div>
<h1>Digital Cash Withdrawal</h1>
<i18n.Translate wrap="p">
<p><i18n.Translate>
You are about to withdraw{" "}
<strong>{renderAmount(details.amount)}</strong> from your bank account
into your wallet.
</i18n.Translate>
</i18n.Translate></p>
{selectedExchange ? (
<p>
The exchange <strong>{selectedExchange}</strong> will be used as the

View File

@ -15,7 +15,7 @@
*/
// import * as test from "ava";
import { internalSetStrings, str, Translate } from "../src/i18n";
import { internalSetStrings, str, Translate } from "@gnu-taler/taler-util";
import { render, configure } from "enzyme";
import Adapter from 'enzyme-adapter-preact-pure';