subcommands for i18n tooling, unique message IDs

This commit is contained in:
Florian Dold 2022-02-15 17:45:26 +01:00
parent e6c1294c91
commit 465ccdaa06
No known key found for this signature in database
GPG Key ID: D2E4F00F29D02A4B
5 changed files with 317 additions and 251 deletions

View File

@ -23,38 +23,44 @@ import * as po2json from "po2json";
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
const files = fs export function po2ts(): void {
.readdirSync("./src/i18n") const files = fs
.filter((x) => x.endsWith(".po")) .readdirSync("./src/i18n")
.map((x) => path.join("./src/i18n/", x)); .filter((x) => x.endsWith(".po"))
.map((x) => path.join("./src/i18n/", x));
if (files.length === 0) { if (files.length === 0) {
console.error("no .po files found in src/i18n/"); console.error("no .po files found in src/i18n/");
process.exit(1);
}
console.log(files);
const chunks: string[] = [];
for (const filename of files) {
const m = filename.match(/([a-zA-Z0-9-_]+).po/);
if (!m) {
console.error("error: unexpected filename (expected <lang>.po)");
process.exit(1); process.exit(1);
} }
const lang = m[1]; console.log(files);
const pojson = po2json.parseFileSync(filename, {
format: "jed1.x", const chunks: string[] = [];
fuzzy: true,
}); for (const filename of files) {
const s = const m = filename.match(/([a-zA-Z0-9-_]+).po/);
"strings['" + lang + "'] = " + JSON.stringify(pojson, null, " ") + ";\n\n";
chunks.push(s); if (!m) {
console.error("error: unexpected filename (expected <lang>.po)");
process.exit(1);
}
const lang = m[1];
const pojson = po2json.parseFileSync(filename, {
format: "jed1.x",
fuzzy: true,
});
const s =
"strings['" +
lang +
"'] = " +
JSON.stringify(pojson, null, " ") +
";\n\n";
chunks.push(s);
}
const tsContents = chunks.join("");
fs.writeFileSync("src/i18n/strings.ts", tsContents);
} }
const tsContents = chunks.join("");
fs.writeFileSync("src/i18n/strings.ts", tsContents);

View File

@ -0,0 +1,21 @@
import { potextract } from "./potextract.js";
function usage(): never {
console.log("usage: pogen <extract|merge|emit>");
process.exit(1);
}
export function main() {
const subcommand = process.argv[2];
if (process.argv.includes("--help") || !subcommand) {
usage();
}
switch (subcommand) {
case "extract":
potextract();
break;
default:
console.error(`unknown subcommand '${subcommand}'`);
usage();
}
}

View File

@ -14,21 +14,27 @@
GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/>
*/ */
/** /**
* Imports. * Imports.
*/ */
import * as ts from "typescript"; import * as ts from "typescript";
import * as fs from "fs";
import * as os from "os";
import path = require("path/posix");
function wordwrap(str: string, width: number = 80): string[] { function wordwrap(str: string, width: number = 80): string[] {
var regex = ".{1," + width + "}(\\s|$)|\\S+(\\s|$)"; var regex = ".{1," + width + "}(\\s|$)|\\S+(\\s|$)";
return str.match(RegExp(regex, "g")); return str.match(RegExp(regex, "g"));
} }
export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) { function processFile(
processNode(sourceFile); sourceFile: ts.SourceFile,
outChunks: string[],
knownMessageIds: Set<string>,
) {
let lastTokLine = 0; let lastTokLine = 0;
let preLastTokLine = 0; let preLastTokLine = 0;
processNode(sourceFile);
function getTemplate(node: ts.Node): string { function getTemplate(node: ts.Node): string {
switch (node.kind) { switch (node.kind) {
@ -140,7 +146,8 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
outChunks.push(`#. ${cl}\n`); outChunks.push(`#. ${cl}\n`);
} }
} }
outChunks.push(`#: ${sourceFile.fileName}:${line + 1}\n`); const fn = path.relative(process.cwd(), sourceFile.fileName);
outChunks.push(`#: ${fn}:${line + 1}\n`);
outChunks.push(`#, c-format\n`); outChunks.push(`#, c-format\n`);
} }
@ -148,7 +155,7 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
// Do escaping, wrap break at newlines // Do escaping, wrap break at newlines
let parts = msg let parts = msg
.match(/(.*\n|.+$)/g) .match(/(.*\n|.+$)/g)
.map((x) => x.replace(/\n/g, "\\n")) .map((x) => x.replace(/\n/g, "\\n").replace(/"/g, '\\"'))
.map((p) => wordwrap(p)) .map((p) => wordwrap(p))
.reduce((a, b) => a.concat(b)); .reduce((a, b) => a.concat(b));
if (parts.length == 1) { if (parts.length == 1) {
@ -188,7 +195,7 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
} }
} }
function trim(s) { function trim(s: string) {
return s.replace(/^[ \n\t]*/, "").replace(/[ \n\t]*$/, ""); return s.replace(/^[ \n\t]*/, "").replace(/[ \n\t]*$/, "");
} }
@ -284,10 +291,13 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
let content = getJsxContent(node); let content = getJsxContent(node);
let { line } = ts.getLineAndCharacterOfPosition(sourceFile, node.pos); let { line } = ts.getLineAndCharacterOfPosition(sourceFile, node.pos);
let comment = getComment(node); let comment = getComment(node);
formatMsgComment(line, comment); if (!knownMessageIds.has(content)) {
formatMsgLine("msgid", content); knownMessageIds.add(content);
outChunks.push(`msgstr ""\n`); formatMsgComment(line, comment);
outChunks.push("\n"); formatMsgLine("msgid", content);
outChunks.push(`msgstr ""\n`);
outChunks.push("\n");
}
return; return;
} }
if (arrayEq(path, ["i18n", "TranslateSwitch"])) { if (arrayEq(path, ["i18n", "TranslateSwitch"])) {
@ -304,11 +314,14 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
console.error("plural form missing"); console.error("plural form missing");
process.exit(1); process.exit(1);
} }
formatMsgLine("msgid", singularForm); if (!knownMessageIds.has(singularForm)) {
formatMsgLine("msgid_plural", pluralForm); knownMessageIds.add(singularForm);
outChunks.push(`msgstr[0] ""\n`); formatMsgLine("msgid", singularForm);
outChunks.push(`msgstr[1] ""\n`); formatMsgLine("msgid_plural", pluralForm);
outChunks.push(`\n`); outChunks.push(`msgstr[0] ""\n`);
outChunks.push(`msgstr[1] ""\n`);
outChunks.push(`\n`);
}
return; return;
} }
break; break;
@ -333,13 +346,16 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
<ts.TaggedTemplateExpression>ce.arguments[1], <ts.TaggedTemplateExpression>ce.arguments[1],
); );
let comment = getComment(ce); let comment = getComment(ce);
const msgid = t1.template;
formatMsgComment(line, comment); if (!knownMessageIds.has(msgid)) {
formatMsgLine("msgid", t1.template); knownMessageIds.add(msgid);
formatMsgLine("msgid_plural", t2.template); formatMsgComment(line, comment);
outChunks.push(`msgstr[0] ""\n`); formatMsgLine("msgid", t1.template);
outChunks.push(`msgstr[1] ""\n`); formatMsgLine("msgid_plural", t2.template);
outChunks.push("\n"); outChunks.push(`msgstr[0] ""\n`);
outChunks.push(`msgstr[1] ""\n`);
outChunks.push("\n");
}
// Important: no processing for child i18n expressions here // Important: no processing for child i18n expressions here
return; return;
@ -351,10 +367,14 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
if (path[0] != "i18n") { if (path[0] != "i18n") {
break; break;
} }
formatMsgComment(line, comment); const msgid = template;
formatMsgLine("msgid", template); if (!knownMessageIds.has(msgid)) {
outChunks.push(`msgstr ""\n`); knownMessageIds.add(msgid);
outChunks.push("\n"); formatMsgComment(line, comment);
formatMsgLine("msgid", template);
outChunks.push(`msgstr ""\n`);
outChunks.push("\n");
}
break; break;
} }
} }
@ -363,51 +383,48 @@ export function processFile(sourceFile: ts.SourceFile, outChunks: string[]) {
} }
} }
const configPath = ts.findConfigFile( export function potextract() {
/*searchPath*/ "./", const configPath = ts.findConfigFile(
ts.sys.fileExists, /*searchPath*/ "./",
"tsconfig.json", ts.sys.fileExists,
); "tsconfig.json",
if (!configPath) { );
throw new Error("Could not find a valid 'tsconfig.json'."); if (!configPath) {
} throw new Error("Could not find a valid 'tsconfig.json'.");
}
console.log(configPath); const cmdline = ts.getParsedCommandLineOfConfigFile(
configPath,
{},
{
fileExists: ts.sys.fileExists,
getCurrentDirectory: ts.sys.getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic: (e) => console.log(e),
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile,
useCaseSensitiveFileNames: true,
},
);
const cmdline = ts.getParsedCommandLineOfConfigFile( const prog = ts.createProgram({
configPath, options: cmdline.options,
{}, rootNames: cmdline.fileNames,
{ });
fileExists: ts.sys.fileExists,
getCurrentDirectory: ts.sys.getCurrentDirectory,
onUnRecoverableConfigFileDiagnostic: (e) => console.log(e),
readDirectory: ts.sys.readDirectory,
readFile: ts.sys.readFile,
useCaseSensitiveFileNames: true,
},
);
console.log(cmdline); const allFiles = prog.getSourceFiles();
const prog = ts.createProgram({ const ownFiles = allFiles.filter(
options: cmdline.options, (x) =>
rootNames: cmdline.fileNames, !x.isDeclarationFile &&
}); !prog.isSourceFileFromExternalLibrary(x) &&
!prog.isSourceFileDefaultLibrary(x),
);
const allFiles = prog.getSourceFiles(); //console.log(ownFiles.map((x) => x.fileName));
const ownFiles = allFiles.filter( const chunks = [];
(x) =>
!x.isDeclarationFile &&
!prog.isSourceFileFromExternalLibrary(x) &&
!prog.isSourceFileDefaultLibrary(x),
);
console.log(ownFiles.map((x) => x.fileName)); chunks.push(`# SOME DESCRIPTIVE TITLE.
const chunks = [];
chunks.push(`# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package. # This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
@ -424,10 +441,26 @@ msgstr ""
"Language: \\n" "Language: \\n"
"MIME-Version: 1.0\\n" "MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n" "Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"`); "Content-Transfer-Encoding: 8bit\\n"\n\n`);
for (const f of ownFiles) { const knownMessageIds = new Set<string>();
processFile(f, chunks);
for (const f of ownFiles) {
processFile(f, chunks, knownMessageIds);
}
const pot = chunks.join("");
//console.log(pot);
const packageJson = JSON.parse(
fs.readFileSync("./package.json", { encoding: "utf-8" }),
);
const poDomain = packageJson.pogen?.domain;
if (!poDomain) {
console.error("missing 'pogen.domain' field in package.json");
process.exit(1);
}
fs.writeFileSync(`./src/i18n/${poDomain}.pot`, pot);
} }
console.log(chunks.join(""));

View File

@ -15,7 +15,9 @@
"build-storybook": "build-storybook", "build-storybook": "build-storybook",
"storybook": "start-storybook -s . -p 6006", "storybook": "start-storybook -s . -p 6006",
"pretty": "prettier --write src", "pretty": "prettier --write src",
"watch": "tsc --watch & rollup -w -c" "watch": "tsc --watch & rollup -w -c",
"i18n:extract": "pogen extract",
"i18n:msgmerge": "pogen msgmerge"
}, },
"dependencies": { "dependencies": {
"@gnu-taler/taler-util": "workspace:*", "@gnu-taler/taler-util": "workspace:*",
@ -71,5 +73,8 @@
"**" "**"
], ],
"exclude": [] "exclude": []
},
"pogen": {
"domain": "taler-wallet-webex"
} }
} }

View File

@ -1,21 +1,12 @@
# This file is part of TALER # SOME DESCRIPTIVE TITLE.
# (C) 2016 GNUnet e.V. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# # This file is distributed under the same license as the PACKAGE package.
# TALER is free software; you can redistribute it and/or modify it under the # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
# 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/>
# #
#, fuzzy #, fuzzy
msgid "" msgid ""
msgstr "" msgstr ""
"Project-Id-Version: Taler Wallet\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2016-11-23 00:00+0100\n" "POT-Creation-Date: 2016-11-23 00:00+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
@ -25,266 +16,276 @@ msgstr ""
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
#: src/util/wire.ts:37 #: src/NavigationBar.tsx:86
#, c-format
msgid "Invalid Wire"
msgstr ""
#: src/util/wire.ts:42 src/util/wire.ts:45
#, c-format
msgid "Invalid Test Wire Detail"
msgstr ""
#: src/util/wire.ts:47
#, c-format
msgid "Test Wire Acct #%1$s on %2$s"
msgstr ""
#: src/util/wire.ts:49
#, c-format
msgid "Unknown Wire Detail"
msgstr ""
#: src/webex/pages/benchmark.tsx:52
#, c-format
msgid "Operation"
msgstr ""
#: src/webex/pages/benchmark.tsx:53
#, c-format
msgid "time (ms/op)"
msgstr ""
#: src/webex/pages/pay.tsx:130
#, c-format
msgid "The merchant %1$s offers you to purchase:"
msgstr ""
#: src/webex/pages/pay.tsx:136
#, c-format
msgid "The total price is %1$s (plus %2$s fees)."
msgstr ""
#: src/webex/pages/pay.tsx:141
#, c-format
msgid "The total price is %1$s."
msgstr ""
#: src/webex/pages/pay.tsx:163
#, c-format
msgid "Retry"
msgstr ""
#: src/webex/pages/pay.tsx:173
#, c-format
msgid "Confirm payment"
msgstr ""
#: src/webex/pages/popup.tsx:153
#, c-format #, c-format
msgid "Balance" msgid "Balance"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:154 #: src/NavigationBar.tsx:87
#, c-format #, c-format
msgid "History" msgid "Pending"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:155 #: src/NavigationBar.tsx:88
#, c-format #, c-format
msgid "Debug" msgid "Backup"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:175 #: src/NavigationBar.tsx:89
#, c-format
msgid "Settings"
msgstr ""
#: src/NavigationBar.tsx:90
#, c-format
msgid "Dev"
msgstr ""
#: src/wallet/BackupPage.tsx:127
#, c-format
msgid "Add provider"
msgstr ""
#: src/wallet/BackupPage.tsx:137
#, c-format
msgid "Sync all backups"
msgstr ""
#: src/wallet/BackupPage.tsx:139
#, c-format
msgid "Sync now"
msgstr ""
#: src/popup/BalancePage.tsx:79
#, c-format #, c-format
msgid "You have no balance to show. Need some %1$s getting started?" msgid "You have no balance to show. Need some %1$s getting started?"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:238 #: src/wallet/ProviderAddPage.tsx:145
#, c-format #, c-format
msgid "%1$s incoming" msgid "&lt; Back"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:250 #: src/wallet/ProviderAddPage.tsx:156
#, c-format #, c-format
msgid "%1$s being spent" msgid "Next"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:281 #: src/wallet/ProviderDetailPage.tsx:57
#, c-format #, c-format
msgid "Error: could not retrieve balance information." msgid "Loading..."
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:390 #: src/wallet/ProviderDetailPage.tsx:64
#, c-format #, c-format
msgid "Invalid " msgid "There was an error loading the provider detail for \"%1$s\""
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:396 #: src/wallet/ProviderDetailPage.tsx:75
#, c-format #, c-format
msgid "Fees " msgid "There is not known provider with url \"%1$s\". Redirecting back..."
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:434 #: src/wallet/ProviderDetailPage.tsx:131
#, c-format #, c-format
msgid "Refresh sessions has completed" msgid "Back up"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:451 #: src/wallet/ProviderDetailPage.tsx:142
#, c-format #, c-format
msgid "Order Refused" msgid "Extend"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:465 #: src/wallet/ProviderDetailPage.tsx:148
#, c-format #, c-format
msgid "Order redirected" msgid ""
"terms has changed, extending the service will imply accepting the new terms of "
"service"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:482 #: src/wallet/ProviderDetailPage.tsx:158
#, c-format #, c-format
msgid "Payment aborted" msgid "old"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:512 #: src/wallet/ProviderDetailPage.tsx:162
#, c-format #, c-format
msgid "Payment Sent" msgid "new"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:536 #: src/wallet/ProviderDetailPage.tsx:169
#, c-format #, c-format
msgid "Order accepted" msgid "fee"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:547 #: src/wallet/ProviderDetailPage.tsx:177
#, c-format #, c-format
msgid "Reserve balance updated" msgid "storage"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:559 #: src/wallet/ProviderDetailPage.tsx:190
#, c-format #, c-format
msgid "Payment refund" msgid "&lt; back"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:584 #: src/wallet/ProviderDetailPage.tsx:194
#, c-format #, c-format
msgid "Withdrawn" msgid "remove provider"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:596 #: src/wallet/ProviderDetailPage.tsx:213
#, c-format #, c-format
msgid "Tip Accepted" msgid "There is conflict with another backup from %1$s"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:606 #: src/wallet/ProviderDetailPage.tsx:228
#, c-format #, c-format
msgid "Tip Declined" msgid "Unknown backup problem: %1$s"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:615 #: src/wallet/ProviderDetailPage.tsx:247
#, c-format #, c-format
msgid "%1$s" msgid "service paid"
msgstr "" msgstr ""
#: src/webex/pages/popup.tsx:707 #: src/popup/Settings.tsx:46
#, c-format #, c-format
msgid "Your wallet has no events recorded." msgid "Permissions"
msgstr "" msgstr ""
#: src/webex/pages/return-coins.tsx:124 #: src/cta/TermsOfServiceSection.tsx:37
#, c-format #, c-format
msgid "Wire to bank account" msgid "Exchange doesn't have terms of service"
msgstr "" msgstr ""
#: src/webex/pages/return-coins.tsx:206 #: src/cta/TermsOfServiceSection.tsx:56
#, c-format #, c-format
msgid "Confirm" msgid "Review exchange terms of service"
msgstr "" msgstr ""
#: src/webex/pages/return-coins.tsx:209 #: src/cta/TermsOfServiceSection.tsx:63
#, c-format
msgid "Review new version of terms of service"
msgstr ""
#: src/cta/TermsOfServiceSection.tsx:75
#, c-format
msgid "Show terms of service"
msgstr ""
#: src/cta/TermsOfServiceSection.tsx:83
#, c-format
msgid "I accept the exchange terms of service"
msgstr ""
#: src/cta/TermsOfServiceSection.tsx:127
#, c-format
msgid "Hide terms of service"
msgstr ""
#: src/wallet/ExchangeAddConfirm.tsx:110
#, c-format #, c-format
msgid "Cancel" msgid "Cancel"
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:73 #: src/wallet/ExchangeAddConfirm.tsx:114
#, c-format #, c-format
msgid "Could not get details for withdraw operation:" msgid "Loading terms.."
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:89 src/webex/pages/withdraw.tsx:183 #: src/wallet/ExchangeAddConfirm.tsx:121
#, c-format #, c-format
msgid "Chose different exchange provider" msgid "Add exchange"
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:109 #: src/wallet/ExchangeAddConfirm.tsx:131
#, c-format #, c-format
msgid "" msgid "Add exchange anyway"
"Please select an exchange. You can review the details before after your "
"selection."
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:121 #: src/wallet/Settings.tsx:95
#, c-format #, c-format
msgid "Select %1$s" msgid "Known exchanges"
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:143 #: src/wallet/Transaction.tsx:159
#, c-format #, c-format
msgid "Select custom exchange" msgid "retry"
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:163 #: src/wallet/Transaction.tsx:163
#, c-format #, c-format
msgid "You are about to withdraw %1$s from your bank account into your wallet." msgid "Forget"
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:174 #: src/wallet/Transaction.tsx:198
#, c-format #, c-format
msgid "Accept fees and withdraw" msgid "Confirm"
msgstr "" msgstr ""
#: src/webex/pages/withdraw.tsx:192 #: src/cta/Pay.tsx:211
#, c-format #, c-format
msgid "Cancel withdraw operation" msgid "Pay with a mobile phone"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:249 #: src/cta/Pay.tsx:211
#, c-format #, c-format
msgid "Withdrawal fees:" msgid "Hide QR"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:252 #: src/cta/Pay.tsx:241
#, c-format #, c-format
msgid "Rounding loss:" msgid "Pay"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:254 #: src/cta/Pay.tsx:265
#, c-format #, c-format
msgid "Earliest expiration (for deposit): %1$s" msgid "Withdraw digital cash"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:262 #: src/cta/Pay.tsx:295
#, c-format #, c-format
msgid "# Coins" msgid "Digital cash payment"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:263 #: src/cta/Withdraw.tsx:101
#, c-format #, c-format
msgid "Value" msgid "Digital cash withdrawal"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:264 #: src/cta/Withdraw.tsx:149
#, c-format #, c-format
msgid "Withdraw Fee" msgid "Cancel exchange selection"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:265 #: src/cta/Withdraw.tsx:150
#, c-format #, c-format
msgid "Refresh Fee" msgid "Confirm exchange selection"
msgstr "" msgstr ""
#: src/webex/renderHtml.tsx:266 #: src/cta/Withdraw.tsx:155
#, c-format #, c-format
msgid "Deposit Fee" msgid "Switch exchange"
msgstr "" msgstr ""
#: src/cta/Withdraw.tsx:174
#, c-format
msgid "Confirm withdrawal"
msgstr ""
#: src/cta/Withdraw.tsx:183
#, c-format
msgid "Withdraw anyway"
msgstr ""
#: src/cta/Withdraw.tsx:310
#, c-format
msgid "missing withdraw uri"
msgstr ""
#: src/cta/Deposit.tsx:186
#, c-format
msgid "Digital cash deposit"
msgstr ""