aboutsummaryrefslogtreecommitdiff
path: root/src/webex
diff options
context:
space:
mode:
authorFlorian Dold <florian.dold@gmail.com>2019-12-12 20:53:15 +0100
committerFlorian Dold <florian.dold@gmail.com>2019-12-12 20:53:15 +0100
commit74433c3e05734aa1194049fcbcaa92c70ce61c74 (patch)
treed30e79c9ac3fd5720de628f6a9764354ec69c648 /src/webex
parentcc137c87394ec34d2f54d69fe896dfdf3feec5ea (diff)
refactor: re-structure type definitions
Diffstat (limited to 'src/webex')
-rw-r--r--src/webex/i18n.tsx267
-rw-r--r--src/webex/messages.ts9
-rw-r--r--src/webex/pages/add-auditor.tsx2
-rw-r--r--src/webex/pages/auditors.tsx2
-rw-r--r--src/webex/pages/benchmark.tsx4
-rw-r--r--src/webex/pages/pay.tsx4
-rw-r--r--src/webex/pages/payback.tsx2
-rw-r--r--src/webex/pages/popup.tsx6
-rw-r--r--src/webex/pages/refund.tsx2
-rw-r--r--src/webex/pages/return-coins.tsx4
-rw-r--r--src/webex/pages/tip.tsx4
-rw-r--r--src/webex/pages/welcome.tsx2
-rw-r--r--src/webex/pages/withdraw.tsx4
-rw-r--r--src/webex/renderHtml.tsx6
-rw-r--r--src/webex/wxApi.ts4
-rw-r--r--src/webex/wxBackend.ts14
16 files changed, 302 insertions, 34 deletions
diff --git a/src/webex/i18n.tsx b/src/webex/i18n.tsx
new file mode 100644
index 000000000..3923654e7
--- /dev/null
+++ b/src/webex/i18n.tsx
@@ -0,0 +1,267 @@
+/*
+ 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 "../i18n/strings";
+
+// @ts-ignore: no type decl for this library
+import * as jedLib from "jed";
+
+import * as React from "react";
+
+
+const jed = setupJed();
+
+let enableTracing = false;
+
+
+/**
+ * Set up jed library for internationalization,
+ * based on browser language settings.
+ */
+function setupJed(): any {
+ let lang: string;
+ try {
+ lang = chrome.i18n.getUILanguage();
+ // Chrome gives e.g. "en-US", but Firefox gives us "en_US"
+ lang = lang.replace("_", "-");
+ } catch (e) {
+ lang = "en";
+ console.warn("i18n default language not available");
+ }
+
+ if (!strings[lang]) {
+ lang = "en-US";
+ console.log(`language ${lang} not found, defaulting to english`);
+ }
+ return new jedLib.Jed(strings[lang]);
+}
+
+
+/**
+ * Convert template strings to a msgid
+ */
+function toI18nString(stringSeq: ReadonlyArray<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[]) {
+ const s = toI18nString(stringSeq);
+ const tr = jed.translate(s).ifPlural(1, s).fetch(...values);
+ return tr;
+}
+
+
+interface TranslateSwitchProps {
+ target: number;
+}
+
+
+function stringifyChildren(children: any): string {
+ let n = 1;
+ const ss = React.Children.map(children, (c) => {
+ if (typeof c === "string") {
+ return c;
+ }
+ return `%${n++}$s`;
+ });
+ const s = ss.join("").replace(/ +/g, " ").trim();
+ enableTracing && 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;
+}
+
+
+/**
+ * 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 class Translate extends React.Component<TranslateProps, {}> {
+ render(): JSX.Element {
+ const s = stringifyChildren(this.props.children);
+ const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 === 0);
+ const childArray = React.Children.toArray(this.props.children!);
+ for (let i = 0; i < childArray.length - 1; ++i) {
+ if ((typeof childArray[i]) === "string" && (typeof childArray[i + 1]) === "string") {
+ childArray[i + 1] = (childArray[i] as string).concat(childArray[i + 1] as string);
+ childArray.splice(i, 1);
+ }
+ }
+ const result = [];
+ while (childArray.length > 0) {
+ const x = childArray.shift();
+ if (x === undefined) {
+ continue;
+ }
+ if (typeof x === "string") {
+ const t = tr.shift();
+ result.push(t);
+ } else {
+ result.push(x);
+ }
+ }
+ if (!this.props.wrap) {
+ return <div>{result}</div>;
+ }
+ return React.createElement(this.props.wrap, this.props.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 React.Component<TranslateSwitchProps, void> {
+ render(): JSX.Element {
+ let singular: React.ReactElement<TranslationPluralProps> | undefined;
+ let plural: React.ReactElement<TranslationPluralProps> | undefined;
+ const children = this.props.children;
+ if (children) {
+ React.Children.forEach(children, (child: any) => {
+ if (child.type === TranslatePlural) {
+ plural = child;
+ }
+ if (child.type === TranslateSingular) {
+ singular = child;
+ }
+ });
+ }
+ if ((!singular) || (!plural)) {
+ console.error("translation not found");
+ return React.createElement("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 React.Component<TranslationPluralProps, void> {
+ render(): JSX.Element {
+ const s = stringifyChildren(this.props.children);
+ const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 === 0);
+ const childArray = React.Children.toArray(this.props.children!);
+ for (let i = 0; i < childArray.length - 1; ++i) {
+ if ((typeof childArray[i]) === "string" && (typeof childArray[i + 1]) === "string") {
+ childArray[i + i] = childArray[i] as string + childArray[i + 1] as string;
+ childArray.splice(i, 1);
+ }
+ }
+ const result = [];
+ while (childArray.length > 0) {
+ const x = childArray.shift();
+ if (x === undefined) {
+ continue;
+ }
+ if (typeof x === "string") {
+ const t = tr.shift();
+ result.push(t);
+ } else {
+ result.push(x);
+ }
+ }
+ return <div>{result}</div>;
+ }
+}
+
+
+/**
+ * See [[TranslateSwitch]].
+ */
+export class TranslateSingular extends React.Component<TranslationPluralProps, void> {
+ render(): JSX.Element {
+ const s = stringifyChildren(this.props.children);
+ const tr = jed.ngettext(s, s, 1).split(/%(\d+)\$s/).filter((e: any, i: number) => i % 2 === 0);
+ const childArray = React.Children.toArray(this.props.children!);
+ for (let i = 0; i < childArray.length - 1; ++i) {
+ if ((typeof childArray[i]) === "string" && (typeof childArray[i + 1]) === "string") {
+ childArray[i + i] = childArray[i] as string + childArray[i + 1] as string;
+ childArray.splice(i, 1);
+ }
+ }
+ const result = [];
+ while (childArray.length > 0) {
+ const x = childArray.shift();
+ if (x === undefined) {
+ continue;
+ }
+ if (typeof x === "string") {
+ const t = tr.shift();
+ result.push(t);
+ } else {
+ result.push(x);
+ }
+ }
+ return <div>{result}</div>;
+ }
+}
diff --git a/src/webex/messages.ts b/src/webex/messages.ts
index 4aaf75b2b..579dd4347 100644
--- a/src/webex/messages.ts
+++ b/src/webex/messages.ts
@@ -22,11 +22,12 @@
/* tslint:disable:completed-docs */
import { AmountJson } from "../util/amounts";
-import * as dbTypes from "../dbTypes";
-import * as talerTypes from "../talerTypes";
-import * as walletTypes from "../walletTypes";
+import * as dbTypes from "../types/dbTypes";
+import * as talerTypes from "../types/talerTypes";
+import * as walletTypes from "../types/walletTypes";
import { UpgradeResponse } from "./wxApi";
+import { HistoryEvent } from "../types/history";
/**
* Message type information.
@@ -79,7 +80,7 @@ export interface MessageMap {
};
"get-history": {
request: {};
- response: walletTypes.HistoryEvent[];
+ response: HistoryEvent[];
};
"get-coins": {
request: { exchangeBaseUrl: string };
diff --git a/src/webex/pages/add-auditor.tsx b/src/webex/pages/add-auditor.tsx
index 766db9c5d..0f681aae4 100644
--- a/src/webex/pages/add-auditor.tsx
+++ b/src/webex/pages/add-auditor.tsx
@@ -20,7 +20,7 @@
* @author Florian Dold
*/
-import { CurrencyRecord } from "../../dbTypes";
+import { CurrencyRecord } from "../../types/dbTypes";
import { getCurrencies, updateCurrency } from "../wxApi";
import React, { useState } from "react";
import { registerMountPage } from "../renderHtml";
diff --git a/src/webex/pages/auditors.tsx b/src/webex/pages/auditors.tsx
index 276a7e8e1..876cf326b 100644
--- a/src/webex/pages/auditors.tsx
+++ b/src/webex/pages/auditors.tsx
@@ -25,7 +25,7 @@ import {
AuditorRecord,
CurrencyRecord,
ExchangeForCurrencyRecord,
-} from "../../dbTypes";
+} from "../../types/dbTypes";
import {
getCurrencies,
diff --git a/src/webex/pages/benchmark.tsx b/src/webex/pages/benchmark.tsx
index b250bc20a..fe874f2b7 100644
--- a/src/webex/pages/benchmark.tsx
+++ b/src/webex/pages/benchmark.tsx
@@ -21,9 +21,9 @@
* @author Florian Dold
*/
-import * as i18n from "../../i18n";
+import * as i18n from "../i18n";
-import { BenchmarkResult } from "../../walletTypes";
+import { BenchmarkResult } from "../../types/walletTypes";
import * as wxApi from "../wxApi";
diff --git a/src/webex/pages/pay.tsx b/src/webex/pages/pay.tsx
index cff2f9461..eca115e78 100644
--- a/src/webex/pages/pay.tsx
+++ b/src/webex/pages/pay.tsx
@@ -22,9 +22,9 @@
/**
* Imports.
*/
-import * as i18n from "../../i18n";
+import * as i18n from "../i18n";
-import { PreparePayResult } from "../../walletTypes";
+import { PreparePayResult } from "../../types/walletTypes";
import { renderAmount, ProgressButton, registerMountPage } from "../renderHtml";
import * as wxApi from "../wxApi";
diff --git a/src/webex/pages/payback.tsx b/src/webex/pages/payback.tsx
index 806bef17c..a25b5c6b2 100644
--- a/src/webex/pages/payback.tsx
+++ b/src/webex/pages/payback.tsx
@@ -23,7 +23,7 @@
/**
* Imports.
*/
-import { ReserveRecord } from "../../dbTypes";
+import { ReserveRecord } from "../../types/dbTypes";
import { renderAmount, registerMountPage } from "../renderHtml";
import { getPaybackReserves, withdrawPaybackReserve } from "../wxApi";
import * as React from "react";
diff --git a/src/webex/pages/popup.tsx b/src/webex/pages/popup.tsx
index 27d5dddba..3a2856d64 100644
--- a/src/webex/pages/popup.tsx
+++ b/src/webex/pages/popup.tsx
@@ -24,16 +24,15 @@
/**
* Imports.
*/
-import * as i18n from "../../i18n";
+import * as i18n from "../i18n";
import { AmountJson } from "../../util/amounts";
import * as Amounts from "../../util/amounts";
import {
- HistoryEvent,
WalletBalance,
WalletBalanceEntry,
-} from "../../walletTypes";
+} from "../../types/walletTypes";
import {
abbrev,
@@ -44,6 +43,7 @@ import {
import * as wxApi from "../wxApi";
import * as React from "react";
+import { HistoryEvent } from "../../types/history";
function onUpdateNotification(f: () => void): () => void {
const port = chrome.runtime.connect({ name: "notifications" });
diff --git a/src/webex/pages/refund.tsx b/src/webex/pages/refund.tsx
index 5196c9ea6..2a3f65d21 100644
--- a/src/webex/pages/refund.tsx
+++ b/src/webex/pages/refund.tsx
@@ -24,7 +24,7 @@ import React, { useEffect, useState } from "react";
import ReactDOM from "react-dom";
import * as wxApi from "../wxApi";
-import { PurchaseDetails } from "../../walletTypes";
+import { PurchaseDetails } from "../../types/walletTypes";
import { AmountView } from "../renderHtml";
function RefundStatusView(props: { talerRefundUri: string }) {
diff --git a/src/webex/pages/return-coins.tsx b/src/webex/pages/return-coins.tsx
index be65b4121..7c835da0a 100644
--- a/src/webex/pages/return-coins.tsx
+++ b/src/webex/pages/return-coins.tsx
@@ -31,9 +31,9 @@ import * as Amounts from "../../util/amounts";
import {
SenderWireInfos,
WalletBalance,
-} from "../../walletTypes";
+} from "../../types/walletTypes";
-import * as i18n from "../../i18n";
+import * as i18n from "../i18n";
import * as wire from "../../util/wire";
diff --git a/src/webex/pages/tip.tsx b/src/webex/pages/tip.tsx
index ac904cf0d..c44b343a4 100644
--- a/src/webex/pages/tip.tsx
+++ b/src/webex/pages/tip.tsx
@@ -24,7 +24,7 @@
import * as React from "react";
import * as ReactDOM from "react-dom";
-import * as i18n from "../../i18n";
+import * as i18n from "../i18n";
import { acceptTip, getReserveCreationInfo, getTipStatus } from "../wxApi";
@@ -32,7 +32,7 @@ import { WithdrawDetailView, renderAmount, ProgressButton } from "../renderHtml"
import * as Amounts from "../../util/amounts";
import { useState, useEffect } from "react";
-import { TipStatus } from "../../walletTypes";
+import { TipStatus } from "../../types/walletTypes";
function TipDisplay(props: { talerTipUri: string }) {
diff --git a/src/webex/pages/welcome.tsx b/src/webex/pages/welcome.tsx
index 1026e6e6e..e8f7028ed 100644
--- a/src/webex/pages/welcome.tsx
+++ b/src/webex/pages/welcome.tsx
@@ -23,7 +23,7 @@
import React, { useState, useEffect } from "react";
import { getDiagnostics } from "../wxApi";
import { registerMountPage, PageLink } from "../renderHtml";
-import { WalletDiagnostics } from "../../walletTypes";
+import { WalletDiagnostics } from "../../types/walletTypes";
function Diagnostics() {
const [timedOut, setTimedOut] = useState(false);
diff --git a/src/webex/pages/withdraw.tsx b/src/webex/pages/withdraw.tsx
index 3ee0f768a..9d84ff3a6 100644
--- a/src/webex/pages/withdraw.tsx
+++ b/src/webex/pages/withdraw.tsx
@@ -22,11 +22,11 @@
*/
-import * as i18n from "../../i18n";
+import * as i18n from "../i18n";
import {
WithdrawDetails,
-} from "../../walletTypes";
+} from "../../types/walletTypes";
import { WithdrawDetailView, renderAmount } from "../renderHtml";
diff --git a/src/webex/renderHtml.tsx b/src/webex/renderHtml.tsx
index bf9cdc76f..767058ebf 100644
--- a/src/webex/renderHtml.tsx
+++ b/src/webex/renderHtml.tsx
@@ -25,10 +25,10 @@
*/
import { AmountJson } from "../util/amounts";
import * as Amounts from "../util/amounts";
-import { DenominationRecord } from "../dbTypes";
-import { ExchangeWithdrawDetails } from "../walletTypes";
+import { DenominationRecord } from "../types/dbTypes";
+import { ExchangeWithdrawDetails } from "../types/walletTypes";
import * as moment from "moment";
-import * as i18n from "../i18n";
+import * as i18n from "./i18n";
import React from "react";
import ReactDOM from "react-dom";
diff --git a/src/webex/wxApi.ts b/src/webex/wxApi.ts
index b0af7ac29..1383ffbc3 100644
--- a/src/webex/wxApi.ts
+++ b/src/webex/wxApi.ts
@@ -30,7 +30,7 @@ import {
ExchangeRecord,
PlanchetRecord,
ReserveRecord,
-} from "../dbTypes";
+} from "../types/dbTypes";
import {
BenchmarkResult,
ConfirmPayResult,
@@ -40,7 +40,7 @@ import {
WalletBalance,
PurchaseDetails,
WalletDiagnostics,
-} from "../walletTypes";
+} from "../types/walletTypes";
import { MessageMap, MessageType } from "./messages";
diff --git a/src/webex/wxBackend.ts b/src/webex/wxBackend.ts
index 27141247e..f3f4d80eb 100644
--- a/src/webex/wxBackend.ts
+++ b/src/webex/wxBackend.ts
@@ -30,11 +30,11 @@ import {
CreateReserveRequest,
ReturnCoinsRequest,
WalletDiagnostics,
-} from "../walletTypes";
+} from "../types/walletTypes";
import { Wallet } from "../wallet";
import { isFirefox } from "./compat";
-import { WALLET_DB_VERSION } from "../dbTypes";
-import { openTalerDb, exportDb, importDb, deleteDb } from "../db";
+import { WALLET_DB_VERSION } from "../types/dbTypes";
+import { openDatabase, exportDatabase, importDatabase, deleteDatabase } from "../db";
import { ChromeBadge } from "./chromeBadge";
import { MessageType } from "./messages";
import * as wxApi from "./wxApi";
@@ -73,11 +73,11 @@ async function handleMessage(
}
case "dump-db": {
const db = needsWallet().db;
- return exportDb(db);
+ return exportDatabase(db);
}
case "import-db": {
const db = needsWallet().db;
- return importDb(db, detail.dump);
+ return importDatabase(db, detail.dump);
}
case "ping": {
return Promise.resolve();
@@ -91,7 +91,7 @@ async function handleMessage(
tx.objectStore(db.objectStoreNames[i]).clear();
}
}
- deleteDb(indexedDB);
+ deleteDatabase(indexedDB);
setBadgeText({ text: "" });
console.log("reset done");
if (!currentWallet) {
@@ -423,7 +423,7 @@ async function reinitWallet() {
setBadgeText({ text: "" });
const badge = new ChromeBadge();
try {
- currentDatabase = await openTalerDb(
+ currentDatabase = await openDatabase(
indexedDB,
reinitWallet,
handleUpgradeUnsupported,