diff options
author | Sebastian <sebasjm@gmail.com> | 2021-07-26 11:27:56 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2021-07-26 11:33:49 -0300 |
commit | 44551245dab36570d937affdb89735c937b4ae55 (patch) | |
tree | fca0e201ce8157122af15abb9aebd1061a773b92 /packages | |
parent | e70e664da982baf1b74e6210adaa870c298281d4 (diff) |
i18n
Diffstat (limited to 'packages')
-rw-r--r-- | packages/taler-util/src/i18n.ts | 6 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/.storybook/preview.js | 20 | ||||
-rwxr-xr-x | packages/taler-wallet-webextension/clean_and_build.sh | 1 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/components/SelectList.tsx | 40 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/context/devContext.ts (renamed from packages/taler-wallet-webextension/src/context/useDevContext.ts) | 0 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/context/translation.ts | 68 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/hooks/useLang.ts | 7 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts | 21 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/i18n/strings.ts | 70 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx | 34 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/Settings.tsx | 42 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popup/popup.tsx | 2 | ||||
-rw-r--r-- | packages/taler-wallet-webextension/src/popupEntryPoint.tsx | 2 |
13 files changed, 272 insertions, 41 deletions
diff --git a/packages/taler-util/src/i18n.ts b/packages/taler-util/src/i18n.ts index 0324d8e07..e452ffa9f 100644 --- a/packages/taler-util/src/i18n.ts +++ b/packages/taler-util/src/i18n.ts @@ -45,7 +45,7 @@ function toI18nString(stringSeq: ReadonlyArray<string>): string { /** * Internationalize a string template with arbitrary serialized values. */ -export function str(stringSeq: TemplateStringsArray, ...values: any[]): string { +export function singular(stringSeq: TemplateStringsArray, ...values: any[]): string { const s = toI18nString(stringSeq); const tr = jed .translate(s) @@ -141,7 +141,9 @@ function stringifyArray(children: Array<any>): string { } export const i18n = { - str, + str: singular, + singular, Translate, translate, }; + diff --git a/packages/taler-wallet-webextension/.storybook/preview.js b/packages/taler-wallet-webextension/.storybook/preview.js index af768dde8..169b726f9 100644 --- a/packages/taler-wallet-webextension/.storybook/preview.js +++ b/packages/taler-wallet-webextension/.storybook/preview.js @@ -14,15 +14,9 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ -import { setupI18n } from "@gnu-taler/taler-util" import { Fragment } from "preact" -import { strings } from '../src/i18n/strings.ts' import { NavBar } from '../src/popup/popup' - -const mockConfig = { - backendURL: 'http://demo.taler.net', - currency: 'KUDOS' -} +import { TranslationProvider } from '../src/context/translation' export const parameters = { controls: { expanded: true }, @@ -38,7 +32,7 @@ export const globalTypes = { icon: 'globe', items: [ { value: 'en', right: '🇺🇸', title: 'English' }, - { value: 'es', right: '🇪🇸', title: 'Spanish' }, + { value: 'de', right: '🇪🇸', title: 'German' }, ], }, }, @@ -58,7 +52,7 @@ export const decorators = [ <Story /> </div> } else { - const path = !isTestingHeader ? /popup(\/.*)\/.*/.exec(kind)[1] : '' + const path = !isTestingHeader ? /popup(\/.*).*/.exec(kind)[1] : '' // add a fake header so it looks similar return <Fragment> <NavBar path={path} devMode={path === '/dev'} /> @@ -113,9 +107,7 @@ export const decorators = [ <Story /> </div> }, - (Story, { globals }) => { - setupI18n(globals.locale, strings); - return <Story /> - }, - // (Story) => <ConfigContextProvider value={mockConfig}> <Story /> </ConfigContextProvider> + (Story, { globals }) => <TranslationProvider initial='en' forceLang={globals.locale}> + <Story /> + </TranslationProvider>, ]; diff --git a/packages/taler-wallet-webextension/clean_and_build.sh b/packages/taler-wallet-webextension/clean_and_build.sh index e862be37e..fb8b31c7e 100755 --- a/packages/taler-wallet-webextension/clean_and_build.sh +++ b/packages/taler-wallet-webextension/clean_and_build.sh @@ -1,5 +1,6 @@ #!/usr/bin/env bash # This file is in the public domain. [ "also-wallet" == "$1" ] && { pnpm -C ../taler-wallet-core/ compile || exit 1; } +[ "also-util" == "$1" ] && { pnpm -C ../taler-util/ prepare || exit 1; } pnpm clean && pnpm compile && rm -rf extension/ && ./pack.sh && (cd extension/ && unzip taler*.zip) diff --git a/packages/taler-wallet-webextension/src/components/SelectList.tsx b/packages/taler-wallet-webextension/src/components/SelectList.tsx new file mode 100644 index 000000000..2c4a106ee --- /dev/null +++ b/packages/taler-wallet-webextension/src/components/SelectList.tsx @@ -0,0 +1,40 @@ +/* + This file is part of GNU Taler + (C) 2019 Taler Systems SA + + GNU 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. + + GNU 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 + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +import { VNode } from "preact"; +import { useRef, useState } from "preact/hooks"; +import { JSX } from "preact/jsx-runtime"; + +interface Props { + value: string; + onChange: (s: string) => void; + label: string; + list: { + [label: string]: string + } + name: string; + description?: string; +} + +export function SelectList({ name, value, list, onChange, label, description }: Props): JSX.Element { + return <select name={name} id="slct"> + <option selected disabled>Choose an option</option> + {Object.keys(list) + .filter((l) => l !== value) + .map(key => <option value={key} key={key}>{list[key]}</option> ) + } + </select> +} diff --git a/packages/taler-wallet-webextension/src/context/useDevContext.ts b/packages/taler-wallet-webextension/src/context/devContext.ts index ea2ba4ceb..ea2ba4ceb 100644 --- a/packages/taler-wallet-webextension/src/context/useDevContext.ts +++ b/packages/taler-wallet-webextension/src/context/devContext.ts diff --git a/packages/taler-wallet-webextension/src/context/translation.ts b/packages/taler-wallet-webextension/src/context/translation.ts new file mode 100644 index 000000000..5f57958de --- /dev/null +++ b/packages/taler-wallet-webextension/src/context/translation.ts @@ -0,0 +1,68 @@ +/* + This file is part of GNU Taler + (C) 2021 Taler Systems S.A. + + GNU 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. + + GNU 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 + GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> + */ + +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ + +import { createContext, h, VNode } from 'preact' +import { useContext, useEffect } from 'preact/hooks' +import { useLang } from '../hooks/useLang' +//@ts-ignore: type declaration +import * as jedLib from "jed"; +import { strings } from "../i18n/strings"; +import { setupI18n } from '@gnu-taler/taler-util'; + +interface Type { + lang: string; + changeLanguage: (l: string) => void; +} +const initial = { + lang: 'en', + changeLanguage: () => { + // do not change anything + } +} +const Context = createContext<Type>(initial) + +interface Props { + initial?: string, + children: any, + forceLang?: string +} + +//we use forceLang when we don't want to use the saved state, but sone forced +//runtime lang predefined lang +export const TranslationProvider = ({ initial, children, forceLang }: Props): VNode => { + const [lang, changeLanguage] = useLang(initial) + useEffect(() => { + if (forceLang) { + changeLanguage(forceLang) + } + }) + useEffect(()=> { + setupI18n(lang, strings) + },[lang]) + if (forceLang) { + setupI18n(forceLang, strings) + } else { + setupI18n(lang, strings) + } + return h(Context.Provider, { value: { lang, changeLanguage }, children }); +} + +export const useTranslationContext = (): Type => useContext(Context); diff --git a/packages/taler-wallet-webextension/src/hooks/useLang.ts b/packages/taler-wallet-webextension/src/hooks/useLang.ts new file mode 100644 index 000000000..d9ad7cd55 --- /dev/null +++ b/packages/taler-wallet-webextension/src/hooks/useLang.ts @@ -0,0 +1,7 @@ +import { useNotNullLocalStorage } from './useLocalStorage'; + +export function useLang(initial?: string): [string, (s:string) => void] { + const browserLang: string | undefined = typeof window !== "undefined" ? navigator.language || (navigator as any).userLanguage : undefined; + const defaultLang = (browserLang || initial || 'en').substring(0, 2) + return useNotNullLocalStorage('lang-preference', defaultLang) +} diff --git a/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts b/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts index 30f681940..78a8b65d5 100644 --- a/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts +++ b/packages/taler-wallet-webextension/src/hooks/useLocalStorage.ts @@ -42,3 +42,24 @@ export function useLocalStorage(key: string, initialValue?: string): [string | u return [storedValue, setValue]; } + +//TODO: merge with the above function +export function useNotNullLocalStorage(key: string, initialValue: string): [string, StateUpdater<string>] { + const [storedValue, setStoredValue] = useState<string>((): string => { + return typeof window !== "undefined" ? window.localStorage.getItem(key) || initialValue : initialValue; + }); + + const setValue = (value: string | ((val: string) => string)) => { + const valueToStore = value instanceof Function ? value(storedValue) : value; + setStoredValue(valueToStore); + if (typeof window !== "undefined") { + if (!valueToStore) { + window.localStorage.removeItem(key) + } else { + window.localStorage.setItem(key, valueToStore); + } + } + }; + + return [storedValue, setValue]; +} diff --git a/packages/taler-wallet-webextension/src/i18n/strings.ts b/packages/taler-wallet-webextension/src/i18n/strings.ts index 748b9656a..5b1257830 100644 --- a/packages/taler-wallet-webextension/src/i18n/strings.ts +++ b/packages/taler-wallet-webextension/src/i18n/strings.ts @@ -159,6 +159,76 @@ strings["en-US"] = { }, }; +strings["es"] = { + domain: "messages", + locale_data: { + messages: { + "": { + domain: "messages", + plural_forms: "nplurals=2; plural=(n != 1);", + lang: "", + }, + "Invalid Wire": [""], + "Invalid Test Wire Detail": [""], + "Test Wire Acct #%1$s on %2$s": [""], + "Unknown Wire Detail": [""], + Operation: [""], + "time (ms/op)": [""], + "The merchant %1$s offers you to purchase:": [""], + "The total price is %1$s (plus %2$s fees).": [""], + "The total price is %1$s.": [""], + Retry: [""], + "Confirm payment": [""], + Balance: [""], + History: ["Historial"], + Debug: [""], + "You have no balance to show. Need some %1$s getting started?": [""], + "%1$s incoming": [""], + "%1$s being spent": [""], + "Error: could not retrieve balance information.": [""], + "Invalid ": [""], + "Fees ": [""], + "Refresh sessions has completed": [""], + "Order Refused": [""], + "Order redirected": [""], + "Payment aborted": [""], + "Payment Sent": [""], + "Backup": ["Resguardo"], + "Order accepted": [""], + "Reserve balance updated": [""], + "Payment refund": [""], + Withdrawn: [""], + "Tip Accepted": [""], + "Tip Declined": [""], + "%1$s": [""], + "Your wallet has no events recorded.": [""], + "Wire to bank account": [""], + Confirm: ["Confirmar"], + Cancel: ["Cancelar"], + "Could not get details for withdraw operation:": [""], + "Chose different exchange provider": [""], + "Please select an exchange. You can review the details before after your selection.": [ + "", + ], + "Select %1$s": [""], + "Select custom exchange": [""], + "You are about to withdraw %1$s from your bank account into your wallet.": [ + "", + ], + "Accept fees and withdraw": [""], + "Cancel withdraw operation": [""], + "Withdrawal fees:": [""], + "Rounding loss:": [""], + "Earliest expiration (for deposit): %1$s": [""], + "# Coins": [""], + Value: [""], + "Withdraw Fee": [""], + "Refresh Fee": [""], + "Deposit Fee": [""], + }, + }, +}; + strings["fr"] = { domain: "messages", locale_data: { diff --git a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx index 0b76d7560..30512d227 100644 --- a/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx +++ b/packages/taler-wallet-webextension/src/popup/ProviderDetailPage.tsx @@ -31,7 +31,7 @@ interface Props { export function ProviderDetailPage({ pid, onBack }: Props): VNode { const status = useProviderStatus(pid) if (!status) { - return <div>Loading...</div> + return <div><i18n.Translate>Loading...</i18n.Translate></div> } if (!status.info) { onBack() @@ -67,26 +67,26 @@ export function ProviderView({ info, onDelete, onSync, onBack, onExtend }: ViewP <p>{daysSince(info?.lastSuccessfulBackupTimestamp)} </p> <p>{descriptionByStatus(info.paymentStatus)}</p> {info.paymentStatus.type === ProviderPaymentType.TermsChanged && <div> - <p>terms has changed, extending the service will imply accepting the new terms of service</p> + <p><i18n.Translate>terms has changed, extending the service will imply accepting the new terms of service</i18n.Translate></p> <table> <thead> <tr> <td></td> - <td>old</td> + <td><i18n.Translate>old</i18n.Translate></td> <td> -></td> - <td>new</td> + <td><i18n.Translate>new</i18n.Translate></td> </tr> </thead> <tbody> <tr> - <td>fee</td> + <td><i18n.Translate>fee</i18n.Translate></td> <td>{info.paymentStatus.oldTerms.annualFee}</td> <td>-></td> <td>{info.paymentStatus.newTerms.annualFee}</td> </tr> <tr> - <td>storage</td> + <td><i18n.Translate>storage</i18n.Translate></td> <td>{info.paymentStatus.oldTerms.storageLimitInMegabytes}</td> <td>-></td> <td>{info.paymentStatus.newTerms.storageLimitInMegabytes}</td> @@ -117,11 +117,11 @@ function daysSince(d?: Timestamp) { const str = formatDuration(duration, { delimiter: ', ', format: [ - duration?.years ? 'years' : ( - duration?.months ? 'months' : ( - duration?.days ? 'days' : ( - duration?.hours ? 'hours' : ( - duration?.minutes ? 'minutes' : 'seconds' + duration?.years ? i18n.str`years` : ( + duration?.months ? i18n.str`months` : ( + duration?.days ? i18n.str`days` : ( + duration?.hours ? i18n.str`hours` : ( + duration?.minutes ? i18n.str`minutes` : i18n.str`seconds` ) ) ) @@ -139,13 +139,13 @@ function Error({ info }: { info: ProviderInfo }) { switch (info.backupProblem.type) { case "backup-conflicting-device": return <ErrorMessage title={<Fragment> - There is conflict with another backup from <b>{info.backupProblem.otherDeviceId}</b> + <i18n.Translate>There is conflict with another backup from <b>{info.backupProblem.otherDeviceId}</b></i18n.Translate> </Fragment>} /> case "backup-unreadable": return <ErrorMessage title="Backup is not readable" /> default: return <ErrorMessage title={<Fragment> - Unknown backup problem: {JSON.stringify(info.backupProblem)} + <i18n.Translate>Unknown backup problem: {JSON.stringify(info.backupProblem)}</i18n.Translate> </Fragment>} /> } } @@ -172,15 +172,15 @@ function colorByStatus(status: ProviderPaymentType) { function descriptionByStatus(status: ProviderPaymentStatus) { switch (status.type) { case ProviderPaymentType.InsufficientBalance: - return 'no enough balance to make the payment' + return i18n.str`no enough balance to make the payment` case ProviderPaymentType.Unpaid: - return 'not paid yet' + return i18n.str`not paid yet` case ProviderPaymentType.Paid: case ProviderPaymentType.TermsChanged: if (status.paidUntil.t_ms === 'never') { - return 'service paid.' + return i18n.str`service paid` } else { - return `service paid until ${format(status.paidUntil.t_ms, 'yyyy/MM/dd HH:mm:ss')}` + return i18n.str`service paid until ${format(status.paidUntil.t_ms, 'yyyy/MM/dd HH:mm:ss')}` } case ProviderPaymentType.Pending: return '' diff --git a/packages/taler-wallet-webextension/src/popup/Settings.tsx b/packages/taler-wallet-webextension/src/popup/Settings.tsx index d8cd04380..9bb10a4e3 100644 --- a/packages/taler-wallet-webextension/src/popup/Settings.tsx +++ b/packages/taler-wallet-webextension/src/popup/Settings.tsx @@ -15,18 +15,23 @@ */ +import { i18n } from "@gnu-taler/taler-util"; import { VNode } from "preact"; import { Checkbox } from "../components/Checkbox"; import { EditableText } from "../components/EditableText"; -import { useDevContext } from "../context/useDevContext"; +import { SelectList } from "../components/SelectList"; +import { useDevContext } from "../context/devContext"; import { useBackupDeviceName } from "../hooks/useBackupDeviceName"; import { useExtendedPermissions } from "../hooks/useExtendedPermissions"; +import { useLang } from "../hooks/useLang"; export function SettingsPage(): VNode { const [permissionsEnabled, togglePermissions] = useExtendedPermissions(); const { devMode, toggleDevMode } = useDevContext() const { name, update } = useBackupDeviceName() + const [lang, changeLang] = useLang() return <SettingsView + lang={lang} changeLang={changeLang} deviceName={name} setDeviceName={update} permissionsEnabled={permissionsEnabled} togglePermissions={togglePermissions} developerMode={devMode} toggleDeveloperMode={toggleDevMode} @@ -34,6 +39,8 @@ export function SettingsPage(): VNode { } export interface ViewProps { + lang: string; + changeLang: (s: string) => void; deviceName: string; setDeviceName: (s: string) => Promise<void>; permissionsEnabled: boolean; @@ -42,20 +49,43 @@ export interface ViewProps { toggleDeveloperMode: () => void; } -export function SettingsView({ deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode { +import { strings as messages } from '../i18n/strings' + +type LangsNames = { + [P in keyof typeof messages]: string +} + +const names: LangsNames = { + es: 'Español [es]', + en: 'English [en]', + fr: 'Français [fr]', + de: 'Deutsch [de]', + sv: 'Svenska [sv]', + it: 'Italiano [it]', +} + + +export function SettingsView({ lang, changeLang, deviceName, setDeviceName, permissionsEnabled, togglePermissions, developerMode, toggleDeveloperMode }: ViewProps): VNode { return ( <div> <section style={{ height: 'calc(320px - 34px - 16px)', overflow: 'auto' }}> - - <h2>Wallet</h2> + <h2><i18n.Translate>Wallet</i18n.Translate></h2> + <SelectList + value={lang} + onChange={changeLang} + name="lang" + list={names} + label={i18n.str`Lang`} + description="(Choose your preferred lang)" + /> <EditableText value={deviceName} onChange={setDeviceName} name="device-id" - label="Device name" + label={i18n.str`Device name`} description="(This is how you will recognize the wallet in the backup provider)" /> - <h2>Permissions</h2> + <h2><i18n.Translate>Permissions</i18n.Translate></h2> <Checkbox label="Automatically open wallet based on page content" name="perm" description="(Enabling this option below will make using the wallet faster, but requires more permissions from your browser.)" diff --git a/packages/taler-wallet-webextension/src/popup/popup.tsx b/packages/taler-wallet-webextension/src/popup/popup.tsx index 32ff10a85..a6be4d192 100644 --- a/packages/taler-wallet-webextension/src/popup/popup.tsx +++ b/packages/taler-wallet-webextension/src/popup/popup.tsx @@ -27,7 +27,7 @@ import { i18n } from "@gnu-taler/taler-util"; import { ComponentChildren, JSX } from "preact"; import Match from "preact-router/match"; -import { useDevContext } from "../context/useDevContext"; +import { useDevContext } from "../context/devContext"; import { PopupNavigation } from '../components/styled' export enum Pages { diff --git a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx index 613218b80..39c25d508 100644 --- a/packages/taler-wallet-webextension/src/popupEntryPoint.tsx +++ b/packages/taler-wallet-webextension/src/popupEntryPoint.tsx @@ -25,7 +25,7 @@ import { createHashHistory } from "history"; import { render } from "preact"; import Router, { route, Route } from "preact-router"; import { useEffect } from "preact/hooks"; -import { DevContextProvider } from "./context/useDevContext"; +import { DevContextProvider } from "./context/devContext"; import { useTalerActionURL } from "./hooks/useTalerActionURL"; import { strings } from "./i18n/strings"; import { BackupPage } from "./popup/BackupPage"; |