diff options
author | Sebastian <sebasjm@gmail.com> | 2022-12-20 17:45:24 -0300 |
---|---|---|
committer | Sebastian <sebasjm@gmail.com> | 2022-12-20 17:45:24 -0300 |
commit | c59f9a2556731ad95ab8bd7eefe7fa8a41629834 (patch) | |
tree | 5cb60195d66cebbee0ba99e05eafe22f369a46a8 | |
parent | 382e66b179d6fda2598936196b2ae1b97bfab8ef (diff) |
use translation context from web-utils, don't use match react-router since is broken
81 files changed, 1167 insertions, 1422 deletions
diff --git a/packages/merchant-backoffice-ui/build.mjs b/packages/merchant-backoffice-ui/build.mjs index 611ef7044..4097e4e9a 100755 --- a/packages/merchant-backoffice-ui/build.mjs +++ b/packages/merchant-backoffice-ui/build.mjs @@ -64,8 +64,7 @@ function getFilesInDirectory(startPath, regex) { return result } -const allTestFiles = getFilesInDirectory(path.join(BASE, 'tests'), /.test.tsx?$/) - +const allTestFiles = getFilesInDirectory(path.join(BASE, 'src'), /.test.tsx?$/) const entryPoints = ["src/index.tsx", "src/stories.tsx", ...allTestFiles]; let GIT_ROOT = BASE; diff --git a/packages/merchant-backoffice-ui/package.json b/packages/merchant-backoffice-ui/package.json index 085f92d76..ca8923e05 100644 --- a/packages/merchant-backoffice-ui/package.json +++ b/packages/merchant-backoffice-ui/package.json @@ -3,12 +3,13 @@ "name": "@gnu-taler/merchant-backoffice", "version": "0.0.4", "license": "MIT", + "type": "module", "scripts": { "build": "./build.mjs", "check": "tsc", "compile": "tsc && ./build.mjs", "dev": "preact watch --port ${PORT:=8080} --no-sw --no-esm", - "test": "pnpm compile && mocha --require source-map-support/register '*.test.js' 'dist/**/test.js'", + "test": "pnpm compile && mocha --require source-map-support/register 'dist/**/*.test.js' 'dist/**/test.js'", "lint": "eslint 'src/**/*.{js,jsx,ts,tsx}'", "i18n:extract": "pogen extract", "i18n:merge": "pogen merge", @@ -38,7 +39,7 @@ "date-fns": "2.29.3", "history": "4.10.1", "jed": "1.1.1", - "preact": "10.6.5", + "preact": "10.11.3", "preact-router": "3.2.1", "qrcode-generator": "1.4.4", "swr": "1.3.0", @@ -79,22 +80,5 @@ "sass": "1.56.1", "typedoc": "^0.20.36", "typescript": "4.8.4" - }, - "jest": { - "preset": "jest-preset-preact", - "transformIgnorePatterns": [ - "node_modules/.pnpm/(?!(@gnu-taler\\+taler-util))", - "\\.pnp\\.[^\\/]+$" - ], - "setupFiles": [ - "<rootDir>/tests/__mocks__/browserMocks.ts", - "<rootDir>/tests/__mocks__/setupTests.ts" - ], - "moduleNameMapper": { - "\\.(css|less)$": "identity-obj-proxy" - }, - "transform": { - "\\.(jpg|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga|po)$": "<rootDir>/tests/__mocks__/fileTransformer.js" - } } -} +}
\ No newline at end of file diff --git a/packages/merchant-backoffice-ui/src/Application.tsx b/packages/merchant-backoffice-ui/src/Application.tsx index 0b92375c1..84536396d 100644 --- a/packages/merchant-backoffice-ui/src/Application.tsx +++ b/packages/merchant-backoffice-ui/src/Application.tsx @@ -19,6 +19,10 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { + TranslationProvider, + useTranslationContext, +} from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { route } from "preact-router"; import { useMemo } from "preact/hooks"; @@ -33,16 +37,15 @@ import { useBackendContext, } from "./context/backend.js"; import { ConfigContextProvider } from "./context/config.js"; -import { TranslationProvider } from "./context/translation.js"; import { useBackendConfig } from "./hooks/backend.js"; -import { useTranslator } from "./i18n/index.js"; +import { strings } from "./i18n/strings.js"; import LoginPage from "./paths/login/index.js"; export function Application(): VNode { return ( // <FetchContextProvider> <BackendContextProvider> - <TranslationProvider> + <TranslationProvider source={strings}> <ApplicationStatusRoutes /> </TranslationProvider> </BackendContextProvider> @@ -53,7 +56,7 @@ export function Application(): VNode { function ApplicationStatusRoutes(): VNode { const { updateLoginStatus, triedToLog } = useBackendContext(); const result = useBackendConfig(); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const updateLoginInfoAndGoToRoot = (url: string, token?: string) => { updateLoginStatus(url, token); @@ -88,7 +91,7 @@ function ApplicationStatusRoutes(): VNode { <NotYetReadyAppMenu title="Error" /> <NotificationCard notification={{ - message: i18n`Server not found`, + message: i18n.str`Server not found`, type: "ERROR", description: `Check your url`, }} @@ -103,9 +106,9 @@ function ApplicationStatusRoutes(): VNode { <NotYetReadyAppMenu title="Error" /> <NotificationCard notification={{ - message: i18n`Couldn't access the server`, + message: i18n.str`Couldn't access the server`, type: "ERROR", - description: i18n`Got message ${result.message} from ${result.info?.url}`, + description: i18n.str`Got message ${result.message} from ${result.info?.url}`, }} /> <LoginPage onConfirm={updateLoginInfoAndGoToRoot} /> @@ -120,9 +123,9 @@ function ApplicationStatusRoutes(): VNode { <NotYetReadyAppMenu title="Error" /> <NotificationCard notification={{ - message: i18n`Unexpected Error`, + message: i18n.str`Unexpected Error`, type: "ERROR", - description: i18n`Got message ${result.message} from ${result.info?.url}`, + description: i18n.str`Got message ${result.message} from ${result.info?.url}`, }} /> <LoginPage onConfirm={updateLoginInfoAndGoToRoot} /> @@ -130,7 +133,7 @@ function ApplicationStatusRoutes(): VNode { ); return ( - <div id="app" class="has-navbar-fixed-top"> + <div class="has-navbar-fixed-top"> <ConfigContextProvider value={ctx}> <ApplicationReadyRoutes /> </ConfigContextProvider> diff --git a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx index fe4493bff..4390ef877 100644 --- a/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx +++ b/packages/merchant-backoffice-ui/src/ApplicationReadyRoutes.tsx @@ -18,23 +18,23 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; +import { createHashHistory } from "history"; import { Fragment, h, VNode } from "preact"; import Router, { Route, route } from "preact-router"; +import { useState } from "preact/hooks"; +import { + NotificationCard, + NotYetReadyAppMenu, +} from "./components/menu/index.js"; import { useBackendContext } from "./context/backend.js"; import { useBackendInstancesTestForAdmin } from "./hooks/backend.js"; import { InstanceRoutes } from "./InstanceRoutes.js"; import LoginPage from "./paths/login/index.js"; import { INSTANCE_ID_LOOKUP } from "./utils/constants.js"; -import { - NotYetReadyAppMenu, - NotificationCard, -} from "./components/menu/index.js"; -import { useTranslator } from "./i18n/index.js"; -import { createHashHistory } from "history"; -import { useState } from "preact/hooks"; export function ApplicationReadyRoutes(): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const { url: backendURL, updateLoginStatus, @@ -54,8 +54,8 @@ export function ApplicationReadyRoutes(): VNode { <NotYetReadyAppMenu title="Login" onLogout={clearTokenAndGoToRoot} /> <NotificationCard notification={{ - message: i18n`Access denied`, - description: i18n`Check your token is valid`, + message: i18n.str`Access denied`, + description: i18n.str`Check your token is valid`, type: "ERROR", }} /> @@ -81,8 +81,8 @@ export function ApplicationReadyRoutes(): VNode { <NotYetReadyAppMenu title="Error" onLogout={clearTokenAndGoToRoot} /> <NotificationCard notification={{ - message: i18n`Couldn't access the server.`, - description: i18n`Could not infer instance id from url ${backendURL}`, + message: i18n.str`Couldn't access the server.`, + description: i18n.str`Could not infer instance id from url ${backendURL}`, type: "ERROR", }} /> @@ -108,7 +108,12 @@ export function ApplicationReadyRoutes(): VNode { ); } -function DefaultMainRoute({ instance, admin, instanceNameByBackendURL }: any) { +function DefaultMainRoute({ + instance, + admin, + instanceNameByBackendURL, + url, //from preact-router +}: any): VNode { const [instanceName, setInstanceName] = useState( instanceNameByBackendURL || instance || "default", ); @@ -116,6 +121,7 @@ function DefaultMainRoute({ instance, admin, instanceNameByBackendURL }: any) { return ( <InstanceRoutes admin={admin} + path={url} id={instanceName} setInstanceName={setInstanceName} /> diff --git a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx index 817de5f7b..8ac5c698b 100644 --- a/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx +++ b/packages/merchant-backoffice-ui/src/InstanceRoutes.tsx @@ -19,6 +19,8 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; +import { format } from "date-fns"; import { Fragment, FunctionComponent, h, VNode } from "preact"; import { Route, route, Router } from "preact-router"; import { useCallback, useEffect, useMemo, useState } from "preact/hooks"; @@ -26,39 +28,37 @@ import { Loading } from "./components/exception/loading.js"; import { Menu, NotificationCard } from "./components/menu/index.js"; import { useBackendContext } from "./context/backend.js"; import { InstanceContextProvider } from "./context/instance.js"; +import { HttpError } from "./hooks/backend.js"; import { useBackendDefaultToken, useBackendInstanceToken, useLocalStorage, } from "./hooks/index.js"; -import { HttpError } from "./hooks/backend.js"; -import { Translate, useTranslator } from "./i18n/index.js"; +import { useInstanceKYCDetails } from "./hooks/instance.js"; import InstanceCreatePage from "./paths/admin/create/index.js"; import InstanceListPage from "./paths/admin/list/index.js"; +import ListKYCPage from "./paths/instance/kyc/list/index.js"; import OrderCreatePage from "./paths/instance/orders/create/index.js"; import OrderDetailsPage from "./paths/instance/orders/details/index.js"; import OrderListPage from "./paths/instance/orders/list/index.js"; import ProductCreatePage from "./paths/instance/products/create/index.js"; import ProductListPage from "./paths/instance/products/list/index.js"; import ProductUpdatePage from "./paths/instance/products/update/index.js"; -import TransferListPage from "./paths/instance/transfers/list/index.js"; -import TransferCreatePage from "./paths/instance/transfers/create/index.js"; -import TemplateListPage from "./paths/instance/templates/list/index.js"; -import TemplateUpdatePage from "./paths/instance/templates/update/index.js"; -import TemplateCreatePage from "./paths/instance/templates/create/index.js"; import ReservesCreatePage from "./paths/instance/reserves/create/index.js"; import ReservesDetailsPage from "./paths/instance/reserves/details/index.js"; import ReservesListPage from "./paths/instance/reserves/list/index.js"; -import ListKYCPage from "./paths/instance/kyc/list/index.js"; +import TemplateCreatePage from "./paths/instance/templates/create/index.js"; +import TemplateListPage from "./paths/instance/templates/list/index.js"; +import TemplateUpdatePage from "./paths/instance/templates/update/index.js"; +import TransferCreatePage from "./paths/instance/transfers/create/index.js"; +import TransferListPage from "./paths/instance/transfers/list/index.js"; import InstanceUpdatePage, { - Props as InstanceUpdatePageProps, AdminUpdate as InstanceAdminUpdatePage, + Props as InstanceUpdatePageProps, } from "./paths/instance/update/index.js"; import LoginPage from "./paths/login/index.js"; import NotFoundPage from "./paths/notfound/index.js"; import { Notification } from "./utils/types.js"; -import { useInstanceKYCDetails } from "./hooks/instance.js"; -import { format } from "date-fns"; export enum InstancePaths { // details = '/', @@ -99,10 +99,16 @@ export enum AdminPaths { export interface Props { id: string; admin?: boolean; + path: string; setInstanceName: (s: string) => void; } -export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode { +export function InstanceRoutes({ + id, + admin, + path, + setInstanceName, +}: Props): VNode { const [_, updateDefaultToken] = useBackendDefaultToken(); const [token, updateToken] = useBackendInstanceToken(id); const { @@ -113,7 +119,7 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode { const cleaner = useCallback(() => { updateToken(undefined); }, [id]); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); type GlobalNotifState = (Notification & { to: string }) | undefined; const [globalNotification, setGlobalNotification] = @@ -144,8 +150,8 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode { function ServerErrorRedirectTo(to: InstancePaths | AdminPaths) { return function ServerErrorRedirectToImpl(error: HttpError) { setGlobalNotification({ - message: i18n`The backend reported a problem: HTTP status #${error.status}`, - description: i18n`Diagnostic from ${error.info?.url} is "${error.message}"`, + message: i18n.str`The backend reported a problem: HTTP status #${error.status}`, + description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`, details: error.clientError || error.serverError ? error.error?.detail @@ -161,8 +167,8 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode { <Fragment> <NotificationCard notification={{ - message: i18n`Access denied`, - description: i18n`The access token provided is invalid.`, + message: i18n.str`Access denied`, + description: i18n.str`The access token provided is invalid.`, type: "ERROR", }} /> @@ -177,8 +183,8 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode { <Fragment> <NotificationCard notification={{ - message: i18n`No 'default' instance configured yet.`, - description: i18n`Create a 'default' instance to begin using the merchant backoffice.`, + message: i18n.str`No 'default' instance configured yet.`, + description: i18n.str`Create a 'default' instance to begin using the merchant backoffice.`, type: "INFO", }} /> @@ -208,6 +214,7 @@ export function InstanceRoutes({ id, admin, setInstanceName }: Props): VNode { <Menu instance={id} admin={admin} + path={path} onLogout={clearTokenAndGoToRoot} setInstanceName={setInstanceName} /> @@ -488,7 +495,7 @@ function AdminInstanceUpdatePage({ () => ({ id, token, admin: true, changeToken }), [id, token], ); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <InstanceContextProvider value={value}> @@ -500,8 +507,8 @@ function AdminInstanceUpdatePage({ <Fragment> <NotificationCard notification={{ - message: i18n`The backend reported a problem: HTTP status #${error.status}`, - description: i18n`Diagnostic from ${error.info?.url} is "${error.message}"`, + message: i18n.str`The backend reported a problem: HTTP status #${error.status}`, + description: i18n.str`Diagnostic from ${error.info?.url} is "${error.message}"`, details: error.clientError || error.serverError ? error.error?.detail @@ -518,8 +525,8 @@ function AdminInstanceUpdatePage({ <Fragment> <NotificationCard notification={{ - message: i18n`Access denied`, - description: i18n`The access token provided is invalid`, + message: i18n.str`Access denied`, + description: i18n.str`The access token provided is invalid`, type: "ERROR", }} /> @@ -534,6 +541,7 @@ function AdminInstanceUpdatePage({ function KycBanner(): VNode { const kycStatus = useInstanceKYCDetails(); + const { i18n } = useTranslationContext(); const today = format(new Date(), "yyyy-MM-dd"); const [lastHide, setLastHide] = useLocalStorage("kyc-last-hide"); const hasBeenHidden = today === lastHide; @@ -552,7 +560,7 @@ function KycBanner(): VNode { </p> <div class="buttons is-right"> <button class="button" onClick={() => setLastHide(today)}> - <Translate>Hide for today</Translate> + <i18n.Translate>Hide for today</i18n.Translate> </button> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/components/exception/AsyncButton.tsx b/packages/merchant-backoffice-ui/src/components/exception/AsyncButton.tsx index 510bc29b8..aa50dfa91 100644 --- a/packages/merchant-backoffice-ui/src/components/exception/AsyncButton.tsx +++ b/packages/merchant-backoffice-ui/src/components/exception/AsyncButton.tsx @@ -22,7 +22,7 @@ import { ComponentChildren, h } from "preact"; import { LoadingModal } from "../modal/index.js"; import { useAsync } from "../../hooks/async.js"; -import { Translate } from "../../i18n/index.js"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; type Props = { children: ComponentChildren; @@ -33,14 +33,14 @@ type Props = { export function AsyncButton({ onClick, disabled, children, ...rest }: Props) { const { isSlow, isLoading, request, cancel } = useAsync(onClick); - + const { i18n } = useTranslationContext(); if (isSlow) { return <LoadingModal onCancel={cancel} />; } if (isLoading) { return ( <button class="button"> - <Translate>Loading...</Translate> + <i18n.Translate>Loading...</i18n.Translate> </button> ); } diff --git a/packages/merchant-backoffice-ui/src/components/exception/login.tsx b/packages/merchant-backoffice-ui/src/components/exception/login.tsx index d1898915d..552e76ed6 100644 --- a/packages/merchant-backoffice-ui/src/components/exception/login.tsx +++ b/packages/merchant-backoffice-ui/src/components/exception/login.tsx @@ -19,11 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useBackendContext } from "../../context/backend.js"; import { useInstanceContext } from "../../context/instance.js"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { Notification } from "../../utils/types.js"; interface Props { @@ -51,7 +51,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { const [token, setToken] = useState(currentToken); const [url, setURL] = useState(backendUrl); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="columns is-centered"> @@ -61,13 +61,13 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { class="modal-card-head" style={{ border: "1px solid", borderBottom: 0 }} > - <p class="modal-card-title">{i18n`Login required`}</p> + <p class="modal-card-title">{i18n.str`Login required`}</p> </header> <section class="modal-card-body" style={{ border: "1px solid", borderTop: 0, borderBottom: 0 }} > - <Translate>Please enter your access token.</Translate> + <i18n.Translate>Please enter your access token.</i18n.Translate> <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label">URL</label> @@ -95,7 +95,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label"> - <Translate>Access Token</Translate> + <i18n.Translate>Access Token</i18n.Translate> </label> </div> <div class="field-body"> @@ -133,7 +133,7 @@ export function LoginModal({ onConfirm, withMessage }: Props): VNode { onConfirm(url, normalizeToken(token)); }} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </button> </footer> </div> diff --git a/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx b/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx index b5da1117a..8f0249bb2 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputArray.tsx @@ -18,9 +18,9 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { InputProps, useField } from "./useField.js"; export interface Props<T> extends InputProps<T> { @@ -52,7 +52,7 @@ export function InputArray<T>({ const array: any[] = (value ? value! : []) as any; const [currentValue, setCurrentValue] = useState(""); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="field is-horizontal"> @@ -99,7 +99,7 @@ export function InputArray<T>({ const v = fromStr(currentValue); if (!isValid(v)) { setLocalError( - i18n`The value ${v} is invalid for a payment url`, + i18n.str`The value ${v} is invalid for a payment url`, ); return; } @@ -107,9 +107,9 @@ export function InputArray<T>({ onChange([v, ...array] as any); setCurrentValue(""); }} - data-tooltip={i18n`add element to the list`} + data-tooltip={i18n.str`add element to the list`} > - <Translate>add</Translate> + <i18n.Translate>add</i18n.Translate> </button> </p> </div> diff --git a/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx b/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx index 57a5163b7..3b84855da 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputCurrency.tsx @@ -18,7 +18,7 @@ * * @author Sebastian Javier Marchano (sebasjm) */ -import { ComponentChildren, h } from "preact"; +import { ComponentChildren, h, VNode } from "preact"; import { useConfigContext } from "../../context/config.js"; import { Amount } from "../../declaration.js"; import { InputWithAddon } from "./InputWithAddon.js"; @@ -42,7 +42,7 @@ export function InputCurrency<T>({ addonAfter, children, side, -}: Props<keyof T>) { +}: Props<keyof T>): VNode { const config = useConfigContext(); return ( <InputWithAddon<T> @@ -60,7 +60,8 @@ export function InputCurrency<T>({ toStr={(v?: Amount) => v?.split(":")[1] || ""} fromStr={(v: string) => (!v ? "" : `${config.currency}:${v}`)} inputExtra={{ min: 0 }} - children={children} - /> + > + {children} + </InputWithAddon> ); } diff --git a/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx b/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx index 4704b3312..fba798bd5 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputDate.tsx @@ -18,10 +18,10 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { DatePicker } from "../picker/DatePicker.js"; import { InputProps, useField } from "./useField.js"; @@ -43,7 +43,7 @@ export function InputDate<T>({ withTimestampSupport, }: Props<keyof T>): VNode { const [opened, setOpened] = useState(false); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const { error, required, value, onChange } = useField<T>(name); @@ -120,25 +120,25 @@ export function InputDate<T>({ <span data-tooltip={ withTimestampSupport - ? i18n`change value to unknown date` - : i18n`change value to empty` + ? i18n.str`change value to unknown date` + : i18n.str`change value to empty` } > <button class="button is-info mr-3" onClick={() => onChange(undefined as any)} > - <Translate>clear</Translate> + <i18n.Translate>clear</i18n.Translate> </button> </span> )} {withTimestampSupport && ( - <span data-tooltip={i18n`change value to never`}> + <span data-tooltip={i18n.str`change value to never`}> <button class="button is-info" onClick={() => onChange({ t_s: "never" } as any)} > - <Translate>never</Translate> + <i18n.Translate>never</i18n.Translate> </button> </span> )} diff --git a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx index 658cc4db7..3f25d1dc7 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputDuration.tsx @@ -18,10 +18,10 @@ * * @author Sebastian Javier Marchano (sebasjm) */ -import { intervalToDuration, formatDuration } from "date-fns"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; +import { formatDuration, intervalToDuration } from "date-fns"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { SimpleModal } from "../modal/index.js"; import { DurationPicker } from "../picker/DurationPicker.js"; import { InputProps, useField } from "./useField.js"; @@ -43,14 +43,14 @@ export function InputDuration<T>({ withForever, }: Props<keyof T>): VNode { const [opened, setOpened] = useState(false); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const { error, required, value, onChange } = useField<T>(name); let strValue = ""; if (!value) { strValue = ""; } else if (value.d_us === "forever") { - strValue = i18n`forever`; + strValue = i18n.str`forever`; } else { strValue = formatDuration( intervalToDuration({ start: 0, end: value.d_us / 1000 }), @@ -59,17 +59,17 @@ export function InputDuration<T>({ formatDistance: (name, value) => { switch (name) { case "xMonths": - return i18n`${value}M`; + return i18n.str`${value}M`; case "xYears": - return i18n`${value}Y`; + return i18n.str`${value}Y`; case "xDays": - return i18n`${value}d`; + return i18n.str`${value}d`; case "xHours": - return i18n`${value}h`; + return i18n.str`${value}h`; case "xMinutes": - return i18n`${value}min`; + return i18n.str`${value}min`; case "xSeconds": - return i18n`${value}sec`; + return i18n.str`${value}sec`; } }, localize: { @@ -134,22 +134,22 @@ export function InputDuration<T>({ {error && <p class="help is-danger">{error}</p>} </div> {withForever && ( - <span data-tooltip={i18n`change value to never`}> + <span data-tooltip={i18n.str`change value to never`}> <button class="button is-info mr-3" onClick={() => onChange({ d_us: "forever" } as any)} > - <Translate>forever</Translate> + <i18n.Translate>forever</i18n.Translate> </button> </span> )} {!readonly && ( - <span data-tooltip={i18n`change value to empty`}> + <span data-tooltip={i18n.str`change value to empty`}> <button class="button is-info " onClick={() => onChange(undefined as any)} > - <Translate>clear</Translate> + <i18n.Translate>clear</i18n.Translate> </button> </span> )} diff --git a/packages/merchant-backoffice-ui/src/components/form/InputImage.tsx b/packages/merchant-backoffice-ui/src/components/form/InputImage.tsx index d5b2aadb6..43a7af1a3 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputImage.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputImage.tsx @@ -18,9 +18,9 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { ComponentChildren, h, VNode } from "preact"; import { useRef, useState } from "preact/hooks"; -import { Translate } from "../../i18n/index.js"; import { MAX_IMAGE_SIZE as MAX_IMAGE_UPLOAD_SIZE } from "../../utils/constants.js"; import { InputProps, useField } from "./useField.js"; @@ -43,7 +43,7 @@ export function InputImage<T>({ const { error, value, onChange } = useField<T>(name); const image = useRef<HTMLInputElement>(null); - + const { i18n } = useTranslationContext(); const [sizeError, setSizeError] = useState(false); return ( @@ -102,17 +102,17 @@ export function InputImage<T>({ {error && <p class="help is-danger">{error}</p>} {sizeError && ( <p class="help is-danger"> - <Translate>Image should be smaller than 1 MB</Translate> + <i18n.Translate>Image should be smaller than 1 MB</i18n.Translate> </p> )} {!value && ( <button class="button" onClick={() => image.current?.click()}> - <Translate>Add</Translate> + <i18n.Translate>Add</i18n.Translate> </button> )} {value && ( <button class="button" onClick={() => onChange(undefined!)}> - <Translate>Remove</Translate> + <i18n.Translate>Remove</i18n.Translate> </button> )} </div> diff --git a/packages/merchant-backoffice-ui/src/components/form/InputLocation.tsx b/packages/merchant-backoffice-ui/src/components/form/InputLocation.tsx index 613b2f1e6..ef5a0995c 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputLocation.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputLocation.tsx @@ -19,31 +19,34 @@ * @author Sebastian Javier Marchano (sebasjm) */ import { Fragment, h } from "preact"; -import { useTranslator } from "../../i18n/index.js"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Input } from "./Input.js"; export function InputLocation({ name }: { name: string }) { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <> - <Input name={`${name}.country`} label={i18n`Country`} /> + <Input name={`${name}.country`} label={i18n.str`Country`} /> <Input name={`${name}.address_lines`} inputType="multiline" - label={i18n`Address`} + label={i18n.str`Address`} toStr={(v: string[] | undefined) => (!v ? "" : v.join("\n"))} fromStr={(v: string) => v.split("\n")} /> - <Input name={`${name}.building_number`} label={i18n`Building number`} /> - <Input name={`${name}.building_name`} label={i18n`Building name`} /> - <Input name={`${name}.street`} label={i18n`Street`} /> - <Input name={`${name}.post_code`} label={i18n`Post code`} /> - <Input name={`${name}.town_location`} label={i18n`Town location`} /> - <Input name={`${name}.town`} label={i18n`Town`} /> - <Input name={`${name}.district`} label={i18n`District`} /> + <Input + name={`${name}.building_number`} + label={i18n.str`Building number`} + /> + <Input name={`${name}.building_name`} label={i18n.str`Building name`} /> + <Input name={`${name}.street`} label={i18n.str`Street`} /> + <Input name={`${name}.post_code`} label={i18n.str`Post code`} /> + <Input name={`${name}.town_location`} label={i18n.str`Town location`} /> + <Input name={`${name}.town`} label={i18n.str`Town`} /> + <Input name={`${name}.district`} label={i18n.str`District`} /> <Input name={`${name}.country_subdivision`} - label={i18n`Country subdivision`} + label={i18n.str`Country subdivision`} /> </> ); diff --git a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx index 085febea4..7bf39152b 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputPaytoForm.tsx @@ -18,9 +18,9 @@ * * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode, Fragment } from "preact"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; +import { Fragment, h, VNode } from "preact"; import { useCallback, useState } from "preact/hooks"; -import { Translate, Translator, useTranslator } from "../../i18n/index.js"; import { COUNTRY_TABLE } from "../../utils/constants.js"; import { undefinedIfEmpty } from "../../utils/table.js"; import { FormErrors, FormProvider } from "./FormProvider.js"; @@ -69,24 +69,30 @@ function checkAddressChecksum(address: string) { return true; } -function validateBitcoin(addr: string, i18n: Translator): string | undefined { +function validateBitcoin( + addr: string, + i18n: ReturnType<typeof useTranslationContext>["i18n"], +): string | undefined { try { const valid = /^(bc1|[13])[a-zA-HJ-NP-Z0-9]{25,39}$/.test(addr); if (valid) return undefined; } catch (e) { console.log(e); } - return i18n`This is not a valid bitcoin address.`; + return i18n.str`This is not a valid bitcoin address.`; } -function validateEthereum(addr: string, i18n: Translator): string | undefined { +function validateEthereum( + addr: string, + i18n: ReturnType<typeof useTranslationContext>["i18n"], +): string | undefined { try { const valid = isEthereumAddress(addr); if (valid) return undefined; } catch (e) { console.log(e); } - return i18n`This is not a valid Ethereum address.`; + return i18n.str`This is not a valid Ethereum address.`; } /** @@ -103,12 +109,15 @@ function validateEthereum(addr: string, i18n: Translator): string | undefined { * If the remainder is 1, the check digit test is passed and the IBAN might be valid. * */ -function validateIBAN(iban: string, i18n: Translator): string | undefined { +function validateIBAN( + iban: string, + i18n: ReturnType<typeof useTranslationContext>["i18n"], +): string | undefined { // Check total length if (iban.length < 4) - return i18n`IBAN numbers usually have more that 4 digits`; + return i18n.str`IBAN numbers usually have more that 4 digits`; if (iban.length > 34) - return i18n`IBAN numbers usually have less that 34 digits`; + return i18n.str`IBAN numbers usually have less that 34 digits`; const A_code = "A".charCodeAt(0); const Z_code = "Z".charCodeAt(0); @@ -116,7 +125,7 @@ function validateIBAN(iban: string, i18n: Translator): string | undefined { // check supported country const code = IBAN.substr(0, 2); const found = code in COUNTRY_TABLE; - if (!found) return i18n`IBAN country code not found`; + if (!found) return i18n.str`IBAN country code not found`; // 2.- Move the four initial characters to the end of the string const step2 = IBAN.substr(4) + iban.substr(0, 4); @@ -140,7 +149,8 @@ function validateIBAN(iban: string, i18n: Translator): string | undefined { } const checksum = calculate_iban_checksum(step3); - if (checksum !== 1) return i18n`IBAN number is not valid, checksum is wrong`; + if (checksum !== 1) + return i18n.str`IBAN number is not valid, checksum is wrong`; return undefined; } @@ -175,7 +185,7 @@ export function InputPaytoForm<T>({ payToPath = `/${value.path1}`; } } - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const ops = value.options!; const url = tryUrl(`payto://${value.target}${payToPath}`); @@ -188,9 +198,9 @@ export function InputPaytoForm<T>({ const paytoURL = !url ? "" : url.toString(); const errors: FormErrors<Entity> = { - target: value.target === noTargetValue ? i18n`required` : undefined, + target: value.target === noTargetValue ? i18n.str`required` : undefined, path1: !value.path1 - ? i18n`required` + ? i18n.str`required` : value.target === "iban" ? validateIBAN(value.path1, i18n) : value.target === "bitcoin" @@ -201,12 +211,12 @@ export function InputPaytoForm<T>({ path2: value.target === "x-taler-bank" ? !value.path2 - ? i18n`required` + ? i18n.str`required` : undefined : undefined, options: undefinedIfEmpty({ "receiver-name": !value.options?.["receiver-name"] - ? i18n`required` + ? i18n.str`required` : undefined, }), }; @@ -235,23 +245,23 @@ export function InputPaytoForm<T>({ > <InputSelector<Entity> name="target" - label={i18n`Target type`} - tooltip={i18n`Method to use for wire transfer`} + label={i18n.str`Target type`} + tooltip={i18n.str`Method to use for wire transfer`} values={targets} - toStr={(v) => (v === noTargetValue ? i18n`Choose one...` : v)} + toStr={(v) => (v === noTargetValue ? i18n.str`Choose one...` : v)} /> {value.target === "ach" && ( <Fragment> <Input<Entity> name="path1" - label={i18n`Routing`} - tooltip={i18n`Routing number.`} + label={i18n.str`Routing`} + tooltip={i18n.str`Routing number.`} /> <Input<Entity> name="path2" - label={i18n`Account`} - tooltip={i18n`Account number.`} + label={i18n.str`Account`} + tooltip={i18n.str`Account number.`} /> </Fragment> )} @@ -259,8 +269,8 @@ export function InputPaytoForm<T>({ <Fragment> <Input<Entity> name="path1" - label={i18n`Code`} - tooltip={i18n`Business Identifier Code.`} + label={i18n.str`Code`} + tooltip={i18n.str`Business Identifier Code.`} /> </Fragment> )} @@ -268,8 +278,8 @@ export function InputPaytoForm<T>({ <Fragment> <Input<Entity> name="path1" - label={i18n`Account`} - tooltip={i18n`Bank Account Number.`} + label={i18n.str`Account`} + tooltip={i18n.str`Bank Account Number.`} inputExtra={{ style: { textTransform: "uppercase" } }} /> </Fragment> @@ -278,8 +288,8 @@ export function InputPaytoForm<T>({ <Fragment> <Input<Entity> name="path1" - label={i18n`Account`} - tooltip={i18n`Unified Payment Interface.`} + label={i18n.str`Account`} + tooltip={i18n.str`Unified Payment Interface.`} /> </Fragment> )} @@ -287,8 +297,8 @@ export function InputPaytoForm<T>({ <Fragment> <Input<Entity> name="path1" - label={i18n`Address`} - tooltip={i18n`Bitcoin protocol.`} + label={i18n.str`Address`} + tooltip={i18n.str`Bitcoin protocol.`} /> </Fragment> )} @@ -296,8 +306,8 @@ export function InputPaytoForm<T>({ <Fragment> <Input<Entity> name="path1" - label={i18n`Address`} - tooltip={i18n`Ethereum protocol.`} + label={i18n.str`Address`} + tooltip={i18n.str`Ethereum protocol.`} /> </Fragment> )} @@ -305,8 +315,8 @@ export function InputPaytoForm<T>({ <Fragment> <Input<Entity> name="path1" - label={i18n`Address`} - tooltip={i18n`Interledger protocol.`} + label={i18n.str`Address`} + tooltip={i18n.str`Interledger protocol.`} /> </Fragment> )} @@ -315,13 +325,13 @@ export function InputPaytoForm<T>({ <Fragment> <Input<Entity> name="path1" - label={i18n`Host`} - tooltip={i18n`Bank host.`} + label={i18n.str`Host`} + tooltip={i18n.str`Bank host.`} /> <Input<Entity> name="path2" - label={i18n`Account`} - tooltip={i18n`Bank account.`} + label={i18n.str`Account`} + tooltip={i18n.str`Bank account.`} /> </Fragment> )} @@ -329,8 +339,8 @@ export function InputPaytoForm<T>({ {value.target !== noTargetValue && ( <Input name="options.receiver-name" - label={i18n`Name`} - tooltip={i18n`Bank account owner's name.`} + label={i18n.str`Name`} + tooltip={i18n.str`Bank account owner's name.`} /> )} @@ -357,7 +367,7 @@ export function InputPaytoForm<T>({ /> </div> ))} - {!paytos.length && i18n`No accounts yet.`} + {!paytos.length && i18n.str`No accounts yet.`} </div> </div> @@ -365,11 +375,11 @@ export function InputPaytoForm<T>({ <div class="buttons is-right mt-5"> <button class="button is-info" - data-tooltip={i18n`add tax to the tax list`} + data-tooltip={i18n.str`add tax to the tax list`} disabled={hasErrors} onClick={submit} > - <Translate>Add</Translate> + <i18n.Translate>Add</i18n.Translate> </button> </div> )} diff --git a/packages/merchant-backoffice-ui/src/components/form/InputSearchProduct.tsx b/packages/merchant-backoffice-ui/src/components/form/InputSearchProduct.tsx index fceee9d56..7f65fb8ae 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputSearchProduct.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputSearchProduct.tsx @@ -18,11 +18,11 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import emptyImage from "../../assets/empty.png"; import { MerchantBackend, WithId } from "../../declaration.js"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { FormErrors, FormProvider } from "./FormProvider.js"; import { InputWithAddon } from "./InputWithAddon.js"; @@ -50,7 +50,7 @@ export function InputSearchProduct({ const errors: FormErrors<ProductSearch> = { name: undefined, }; - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (selected) { return ( @@ -63,10 +63,11 @@ export function InputSearchProduct({ <div class="media-content"> <div class="content"> <p class="media-meta"> - <Translate>Product id</Translate>: <b>{selected.id}</b> + <i18n.Translate>Product id</i18n.Translate>: <b>{selected.id}</b> </p> <p> - <Translate>Description</Translate>: {selected.description} + <i18n.Translate>Description</i18n.Translate>:{" "} + {selected.description} </p> <div class="buttons is-right mt-5"> <button @@ -90,8 +91,8 @@ export function InputSearchProduct({ > <InputWithAddon<ProductSearch> name="name" - label={i18n`Product`} - tooltip={i18n`search products by it's description or id`} + label={i18n.str`Product`} + tooltip={i18n.str`search products by it's description or id`} addonAfter={ <span class="icon"> <i class="mdi mdi-magnify" /> @@ -120,6 +121,7 @@ interface ProductListProps { } function ProductList({ name, onSelect, list }: ProductListProps) { + const { i18n } = useTranslationContext(); if (!name) { /* FIXME this BR is added to occupy the space that will be added when the @@ -146,7 +148,9 @@ function ProductList({ name, onSelect, list }: ProductListProps) { <div class="dropdown-content"> {!filtered.length ? ( <div class="dropdown-item"> - <Translate>no products found with that description</Translate> + <i18n.Translate> + no products found with that description + </i18n.Translate> </div> ) : ( filtered.map((p) => ( diff --git a/packages/merchant-backoffice-ui/src/components/form/InputSecured.tsx b/packages/merchant-backoffice-ui/src/components/form/InputSecured.tsx index 799978683..d900168b4 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputSecured.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputSecured.tsx @@ -18,14 +18,15 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { InputProps, useField } from "./useField.js"; export type Props<T> = InputProps<T>; const TokenStatus = ({ prev, post }: any) => { + const { i18n } = useTranslationContext(); if ( (prev === undefined || prev === null) && (post === undefined || post === null) @@ -33,11 +34,11 @@ const TokenStatus = ({ prev, post }: any) => { return null; return prev === post ? null : post === null ? ( <span class="tag is-danger is-align-self-center ml-2"> - <Translate>Deleting</Translate> + <i18n.Translate>Deleting</i18n.Translate> </span> ) : ( <span class="tag is-warning is-align-self-center ml-2"> - <Translate>Changing</Translate> + <i18n.Translate>Changing</i18n.Translate> </span> ); }; @@ -55,7 +56,7 @@ export function InputSecured<T>({ const [active, setActive] = useState(false); const [newValue, setNuewValue] = useState(""); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <Fragment> @@ -84,7 +85,7 @@ export function InputSecured<T>({ <i class="mdi mdi-lock-reset" /> </div> <span> - <Translate>Manage access token</Translate> + <i18n.Translate>Manage access token</i18n.Translate> </span> </button> <TokenStatus prev={initial} post={value} /> @@ -125,7 +126,7 @@ export function InputSecured<T>({ <i class="mdi mdi-lock-outline" /> </div> <span> - <Translate>Update</Translate> + <i18n.Translate>Update</i18n.Translate> </span> </button> </div> @@ -154,7 +155,7 @@ export function InputSecured<T>({ <i class="mdi mdi-lock-open-variant" /> </div> <span> - <Translate>Remove</Translate> + <i18n.Translate>Remove</i18n.Translate> </span> </button> </div> @@ -171,7 +172,7 @@ export function InputSecured<T>({ <i class="mdi mdi-lock-open-variant" /> </div> <span> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </span> </button> </div> diff --git a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx index 57aa5968d..1052aaaae 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputStock.tsx @@ -18,17 +18,16 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h } from "preact"; -import { MerchantBackend, Timestamp } from "../../declaration.js"; -import { InputProps, useField } from "./useField.js"; -import { FormProvider, FormErrors } from "./FormProvider.js"; import { useLayoutEffect, useState } from "preact/hooks"; -import { Input } from "./Input.js"; -import { InputGroup } from "./InputGroup.js"; -import { InputNumber } from "./InputNumber.js"; +import { MerchantBackend, Timestamp } from "../../declaration.js"; +import { FormErrors, FormProvider } from "./FormProvider.js"; import { InputDate } from "./InputDate.js"; -import { Translate, useTranslator } from "../../i18n/index.js"; +import { InputGroup } from "./InputGroup.js"; import { InputLocation } from "./InputLocation.js"; +import { InputNumber } from "./InputNumber.js"; +import { InputProps, useField } from "./useField.js"; export interface Props<T> extends InputProps<T> { alreadyExist?: boolean; @@ -64,7 +63,7 @@ export function InputStock<T>({ incoming: 0, lost: 0, }); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); useLayoutEffect(() => { if (!formValue) { @@ -97,7 +96,7 @@ export function InputStock<T>({ {!alreadyExist ? ( <button class="button" - data-tooltip={i18n`click here to configure the stock of the product, leave it as is and the backend will not control stock`} + data-tooltip={i18n.str`click here to configure the stock of the product, leave it as is and the backend will not control stock`} onClick={(): void => { valueHandler({ current: 0, @@ -107,17 +106,17 @@ export function InputStock<T>({ }} > <span> - <Translate>Manage stock</Translate> + <i18n.Translate>Manage stock</i18n.Translate> </span> </button> ) : ( <button class="button" - data-tooltip={i18n`this product has been configured without stock control`} + data-tooltip={i18n.str`this product has been configured without stock control`} disabled > <span> - <Translate>Infinite</Translate> + <i18n.Translate>Infinite</i18n.Translate> </span> </button> )} @@ -134,7 +133,7 @@ export function InputStock<T>({ const stockAddedErrors: FormErrors<typeof addedStock> = { lost: currentStock + addedStock.incoming < addedStock.lost - ? i18n`lost cannot be greater than current and incoming (max ${ + ? i18n.str`lost cannot be greater than current and incoming (max ${ currentStock + addedStock.incoming })` : undefined, @@ -142,8 +141,8 @@ export function InputStock<T>({ // const stockUpdateDescription = stockAddedErrors.lost ? '' : ( // !!addedStock.incoming || !!addedStock.lost ? - // i18n`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` : - // i18n`current stock will stay at ${currentStock}` + // i18n.str`current stock will change from ${currentStock} to ${currentStock + addedStock.incoming - addedStock.lost}` : + // i18n.str`current stock will stay at ${currentStock}` // ) return ( @@ -174,8 +173,8 @@ export function InputStock<T>({ object={addedStock} valueHandler={setAddedStock as any} > - <InputNumber name="incoming" label={i18n`Incoming`} /> - <InputNumber name="lost" label={i18n`Lost`} /> + <InputNumber name="incoming" label={i18n.str`Incoming`} /> + <InputNumber name="lost" label={i18n.str`Lost`} /> </FormProvider> {/* <div class="field is-horizontal"> @@ -190,17 +189,17 @@ export function InputStock<T>({ ) : ( <InputNumber<Entity> name="current" - label={i18n`Current`} + label={i18n.str`Current`} side={ <button class="button is-danger" - data-tooltip={i18n`remove stock control for this product`} + data-tooltip={i18n.str`remove stock control for this product`} onClick={(): void => { valueHandler(undefined as any); }} > <span> - <Translate>without stock</Translate> + <i18n.Translate>without stock</i18n.Translate> </span> </button> } @@ -209,11 +208,14 @@ export function InputStock<T>({ <InputDate<Entity> name="nextRestock" - label={i18n`Next restock`} + label={i18n.str`Next restock`} withTimestampSupport /> - <InputGroup<Entity> name="address" label={i18n`Delivery address`}> + <InputGroup<Entity> + name="address" + label={i18n.str`Delivery address`} + > <InputLocation name="address" /> </InputGroup> </FormProvider> diff --git a/packages/merchant-backoffice-ui/src/components/form/InputTaxes.tsx b/packages/merchant-backoffice-ui/src/components/form/InputTaxes.tsx index d95463790..552ee73e2 100644 --- a/packages/merchant-backoffice-ui/src/components/form/InputTaxes.tsx +++ b/packages/merchant-backoffice-ui/src/components/form/InputTaxes.tsx @@ -18,11 +18,11 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useCallback, useState } from "preact/hooks"; import * as yup from "yup"; import { MerchantBackend } from "../../declaration.js"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { TaxSchema as schema } from "../../schemas/index.js"; import { FormErrors, FormProvider } from "./FormProvider.js"; import { Input } from "./Input.js"; @@ -67,7 +67,7 @@ export function InputTaxes<T>({ valueHandler({}); }, [value]); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); //FIXME: translating plural singular return ( @@ -110,34 +110,35 @@ export function InputTaxes<T>({ /> </div> ))} - {!taxes.length && i18n`No taxes configured for this product.`} + {!taxes.length && i18n.str`No taxes configured for this product.`} </div> </div> <Input<Entity> name="tax" - label={i18n`Amount`} - tooltip={i18n`Taxes can be in currencies that differ from the main currency used by the merchant.`} + label={i18n.str`Amount`} + tooltip={i18n.str`Taxes can be in currencies that differ from the main currency used by the merchant.`} > - <Translate> - Enter currency and value separated with a colon, e.g. "USD:2.3". - </Translate> + <i18n.Translate> + Enter currency and value separated with a colon, e.g. + "USD:2.3". + </i18n.Translate> </Input> <Input<Entity> name="name" - label={i18n`Description`} - tooltip={i18n`Legal name of the tax, e.g. VAT or import duties.`} + label={i18n.str`Description`} + tooltip={i18n.str`Legal name of the tax, e.g. VAT or import duties.`} /> <div class="buttons is-right mt-5"> <button class="button is-info" - data-tooltip={i18n`add tax to the tax list`} + data-tooltip={i18n.str`add tax to the tax list`} disabled={hasErrors} onClick={submit} > - <Translate>Add</Translate> + <i18n.Translate>Add</i18n.Translate> </button> </div> </FormProvider> diff --git a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx index 9dfa33840..c497773d9 100644 --- a/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx +++ b/packages/merchant-backoffice-ui/src/components/instance/DefaultInstanceFormFields.tsx @@ -19,9 +19,9 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useBackendContext } from "../../context/backend.js"; -import { useTranslator } from "../../i18n/index.js"; import { Entity } from "../../paths/admin/create/CreatePage.js"; import { Input } from "../form/Input.js"; import { InputCurrency } from "../form/InputCurrency.js"; @@ -39,7 +39,7 @@ export function DefaultInstanceFormFields({ readonlyId?: boolean; showId: boolean; }): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const backend = useBackendContext(); return ( <Fragment> @@ -48,86 +48,86 @@ export function DefaultInstanceFormFields({ name="id" addonBefore={`${backend.url}/instances/`} readonly={readonlyId} - label={i18n`Identifier`} - tooltip={i18n`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`} + label={i18n.str`Identifier`} + tooltip={i18n.str`Name of the instance in URLs. The 'default' instance is special in that it is used to administer other instances.`} /> )} <Input<Entity> name="name" - label={i18n`Business name`} - tooltip={i18n`Legal name of the business represented by this instance.`} + label={i18n.str`Business name`} + tooltip={i18n.str`Legal name of the business represented by this instance.`} /> <Input<Entity> name="email" - label={i18n`Email`} - tooltip={i18n`Contact email`} + label={i18n.str`Email`} + tooltip={i18n.str`Contact email`} /> <Input<Entity> name="website" - label={i18n`Website URL`} - tooltip={i18n`URL.`} + label={i18n.str`Website URL`} + tooltip={i18n.str`URL.`} /> <InputImage<Entity> name="logo" - label={i18n`Logo`} - tooltip={i18n`Logo image.`} + label={i18n.str`Logo`} + tooltip={i18n.str`Logo image.`} /> <InputPaytoForm<Entity> name="payto_uris" - label={i18n`Bank account`} - tooltip={i18n`URI specifying bank account for crediting revenue.`} + label={i18n.str`Bank account`} + tooltip={i18n.str`URI specifying bank account for crediting revenue.`} /> <InputCurrency<Entity> name="default_max_deposit_fee" - label={i18n`Default max deposit fee`} - tooltip={i18n`Maximum deposit fees this merchant is willing to pay per order by default.`} + label={i18n.str`Default max deposit fee`} + tooltip={i18n.str`Maximum deposit fees this merchant is willing to pay per order by default.`} /> <InputCurrency<Entity> name="default_max_wire_fee" - label={i18n`Default max wire fee`} - tooltip={i18n`Maximum wire fees this merchant is willing to pay per wire transfer by default.`} + label={i18n.str`Default max wire fee`} + tooltip={i18n.str`Maximum wire fees this merchant is willing to pay per wire transfer by default.`} /> <Input<Entity> name="default_wire_fee_amortization" - label={i18n`Default wire fee amortization`} - tooltip={i18n`Number of orders excess wire transfer fees will be divided by to compute per order surcharge.`} + label={i18n.str`Default wire fee amortization`} + tooltip={i18n.str`Number of orders excess wire transfer fees will be divided by to compute per order surcharge.`} /> <InputGroup name="address" - label={i18n`Address`} - tooltip={i18n`Physical location of the merchant.`} + label={i18n.str`Address`} + tooltip={i18n.str`Physical location of the merchant.`} > <InputLocation name="address" /> </InputGroup> <InputGroup name="jurisdiction" - label={i18n`Jurisdiction`} - tooltip={i18n`Jurisdiction for legal disputes with the merchant.`} + label={i18n.str`Jurisdiction`} + tooltip={i18n.str`Jurisdiction for legal disputes with the merchant.`} > <InputLocation name="jurisdiction" /> </InputGroup> <InputDuration<Entity> name="default_pay_delay" - label={i18n`Default payment delay`} + label={i18n.str`Default payment delay`} withForever - tooltip={i18n`Time customers have to pay an order before the offer expires by default.`} + tooltip={i18n.str`Time customers have to pay an order before the offer expires by default.`} /> <InputDuration<Entity> name="default_wire_transfer_delay" - label={i18n`Default wire transfer delay`} - tooltip={i18n`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`} + label={i18n.str`Default wire transfer delay`} + tooltip={i18n.str`Maximum time an exchange is allowed to delay wiring funds to the merchant, enabling it to aggregate smaller payments into larger wire transfers and reducing wire fees.`} withForever /> </Fragment> diff --git a/packages/merchant-backoffice-ui/src/components/menu/LangSelector.tsx b/packages/merchant-backoffice-ui/src/components/menu/LangSelector.tsx index d618d6480..a1de8541e 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/LangSelector.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/LangSelector.tsx @@ -19,11 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import langIcon from "../../assets/icons/languageicon.svg"; -import { useTranslationContext } from "../../context/translation.js"; -import { strings as messages } from "../../i18n/strings"; +import { strings as messages } from "../../i18n/strings.js"; type LangsNames = { [P in keyof typeof messages]: string; diff --git a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx index ea49be99a..92d144b1a 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/SideBar.tsx @@ -19,13 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; -import { useCallback } from "preact/hooks"; import { useBackendContext } from "../../context/backend.js"; import { useConfigContext } from "../../context/config.js"; -import { useInstanceContext } from "../../context/instance.js"; import { useInstanceKYCDetails } from "../../hooks/instance.js"; -import { Translate } from "../../i18n/index.js"; import { LangSelector } from "./LangSelector.js"; const GIT_HASH = typeof __GIT_HASH__ !== "undefined" ? __GIT_HASH__ : undefined; @@ -48,7 +46,7 @@ export function Sidebar({ }: Props): VNode { const config = useConfigContext(); const backend = useBackendContext(); - + const { i18n } = useTranslationContext(); const kycStatus = useInstanceKYCDetails(); const needKYC = kycStatus.ok && kycStatus.data.type === "redirect"; @@ -81,7 +79,7 @@ export function Sidebar({ {instance ? ( <Fragment> <p class="menu-label"> - <Translate>Instance</Translate> + <i18n.Translate>Instance</i18n.Translate> </p> <ul class="menu-list"> <li> @@ -90,7 +88,7 @@ export function Sidebar({ <i class="mdi mdi-square-edit-outline" /> </span> <span class="menu-item-label"> - <Translate>Settings</Translate> + <i18n.Translate>Settings</i18n.Translate> </span> </a> </li> @@ -100,7 +98,7 @@ export function Sidebar({ <i class="mdi mdi-cash-register" /> </span> <span class="menu-item-label"> - <Translate>Orders</Translate> + <i18n.Translate>Orders</i18n.Translate> </span> </a> </li> @@ -110,7 +108,7 @@ export function Sidebar({ <i class="mdi mdi-shopping" /> </span> <span class="menu-item-label"> - <Translate>Products</Translate> + <i18n.Translate>Products</i18n.Translate> </span> </a> </li> @@ -120,7 +118,7 @@ export function Sidebar({ <i class="mdi mdi-bank" /> </span> <span class="menu-item-label"> - <Translate>Transfers</Translate> + <i18n.Translate>Transfers</i18n.Translate> </span> </a> </li> @@ -130,7 +128,7 @@ export function Sidebar({ <i class="mdi mdi-newspaper" /> </span> <span class="menu-item-label"> - <Translate>Templates</Translate> + <i18n.Translate>Templates</i18n.Translate> </span> </a> </li> @@ -156,7 +154,7 @@ export function Sidebar({ </Fragment> ) : undefined} <p class="menu-label"> - <Translate>Connection</Translate> + <i18n.Translate>Connection</i18n.Translate> </p> <ul class="menu-list"> <li> @@ -190,7 +188,7 @@ export function Sidebar({ {admin && !mimic && ( <Fragment> <p class="menu-label"> - <Translate>Instances</Translate> + <i18n.Translate>Instances</i18n.Translate> </p> <li> <a href={"/instance/new"} class="has-icon"> @@ -198,7 +196,7 @@ export function Sidebar({ <i class="mdi mdi-plus" /> </span> <span class="menu-item-label"> - <Translate>New</Translate> + <i18n.Translate>New</i18n.Translate> </span> </a> </li> @@ -208,7 +206,7 @@ export function Sidebar({ <i class="mdi mdi-format-list-bulleted" /> </span> <span class="menu-item-label"> - <Translate>List</Translate> + <i18n.Translate>List</i18n.Translate> </span> </a> </li> @@ -223,7 +221,7 @@ export function Sidebar({ <i class="mdi mdi-logout default" /> </span> <span class="menu-item-label"> - <Translate>Log out</Translate> + <i18n.Translate>Log out</i18n.Translate> </span> </a> </li> diff --git a/packages/merchant-backoffice-ui/src/components/menu/index.tsx b/packages/merchant-backoffice-ui/src/components/menu/index.tsx index 23935ec98..ccbd9442f 100644 --- a/packages/merchant-backoffice-ui/src/components/menu/index.tsx +++ b/packages/merchant-backoffice-ui/src/components/menu/index.tsx @@ -15,7 +15,6 @@ */ import { ComponentChildren, Fragment, h, VNode } from "preact"; -import Match from "preact-router/match"; import { useEffect, useState } from "preact/hooks"; import { AdminPaths } from "../../AdminRoutes.js"; import { InstancePaths } from "../../InstanceRoutes.js"; @@ -58,6 +57,7 @@ function getAdminTitle(path: string, instance: string) { interface MenuProps { title?: string; + path: string; instance: string; admin?: boolean; onLogout?: () => void; @@ -81,64 +81,59 @@ export function Menu({ onLogout, title, instance, + path, admin, setInstanceName, }: MenuProps): VNode { const [mobileOpen, setMobileOpen] = useState(false); + const titleWithSubtitle = title + ? title + : !admin + ? getInstanceTitle(path, instance) + : getAdminTitle(path, instance); + const adminInstance = instance === "default"; + const mimic = admin && !adminInstance; return ( - <Match> - {({ path }: any) => { - const titleWithSubtitle = title - ? title - : !admin - ? getInstanceTitle(path, instance) - : getAdminTitle(path, instance); - const adminInstance = instance === "default"; - const mimic = admin && !adminInstance; - return ( - <WithTitle title={titleWithSubtitle}> - <div - class={mobileOpen ? "has-aside-mobile-expanded" : ""} - onClick={() => setMobileOpen(false)} - > - <NavigationBar - onMobileMenu={() => setMobileOpen(!mobileOpen)} - title={titleWithSubtitle} - /> - - {onLogout && ( - <Sidebar - onLogout={onLogout} - admin={admin} - mimic={mimic} - instance={instance} - mobile={mobileOpen} - /> - )} - - {mimic && ( - <nav class="level"> - <div class="level-item has-text-centered has-background-warning"> - <p class="is-size-5"> - You are viewing the instance <b>"{instance}"</b>.{" "} - <a - href="#/instances" - onClick={(e) => { - setInstanceName("default"); - }} - > - go back - </a> - </p> - </div> - </nav> - )} + <WithTitle title={titleWithSubtitle}> + <div + class={mobileOpen ? "has-aside-mobile-expanded" : ""} + onClick={() => setMobileOpen(false)} + > + <NavigationBar + onMobileMenu={() => setMobileOpen(!mobileOpen)} + title={titleWithSubtitle} + /> + + {onLogout && ( + <Sidebar + onLogout={onLogout} + admin={admin} + mimic={mimic} + instance={instance} + mobile={mobileOpen} + /> + )} + + {mimic && ( + <nav class="level"> + <div class="level-item has-text-centered has-background-warning"> + <p class="is-size-5"> + You are viewing the instance <b>"{instance}"</b>.{" "} + <a + href="#/instances" + onClick={(e) => { + setInstanceName("default"); + }} + > + go back + </a> + </p> </div> - </WithTitle> - ); - }} - </Match> + </nav> + )} + </div> + </WithTitle> ); } diff --git a/packages/merchant-backoffice-ui/src/components/modal/index.tsx b/packages/merchant-backoffice-ui/src/components/modal/index.tsx index 6e5575f63..c9c7d0ce5 100644 --- a/packages/merchant-backoffice-ui/src/components/modal/index.tsx +++ b/packages/merchant-backoffice-ui/src/components/modal/index.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { ComponentChildren, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { useInstanceContext } from "../../context/instance.js"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { DEFAULT_REQUEST_TIMEOUT } from "../../utils/constants.js"; -import { Loading, Spinner } from "../exception/loading.js"; +import { Spinner } from "../exception/loading.js"; import { FormProvider } from "../form/FormProvider.js"; import { Input } from "../form/Input.js"; @@ -49,6 +49,7 @@ export function ConfirmModal({ disabled, label = "Confirm", }: Props): VNode { + const { i18n } = useTranslationContext(); return ( <div class={active ? "modal is-active" : "modal"}> <div class="modal-background " onClick={onCancel} /> @@ -65,14 +66,14 @@ export function ConfirmModal({ <footer class="modal-card-foot"> <div class="buttons is-right" style={{ width: "100%" }}> <button class="button " onClick={onCancel}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <button class={danger ? "button is-danger " : "button is-info "} disabled={disabled} onClick={onConfirm} > - <Translate>{label}</Translate> + <i18n.Translate>{label}</i18n.Translate> </button> </div> </footer> @@ -94,6 +95,7 @@ export function ContinueModal({ children, disabled, }: Props): VNode { + const { i18n } = useTranslationContext(); return ( <div class={active ? "modal is-active" : "modal"}> <div class="modal-background " onClick={onCancel} /> @@ -110,7 +112,7 @@ export function ContinueModal({ disabled={disabled} onClick={onConfirm} > - <Translate>Continue</Translate> + <i18n.Translate>Continue</i18n.Translate> </button> </div> </footer> @@ -147,6 +149,7 @@ export function ClearConfirmModal({ onConfirm, children, }: Props & { onClear?: () => void }): VNode { + const { i18n } = useTranslationContext(); return ( <div class="modal is-active"> <div class="modal-background " onClick={onCancel} /> @@ -163,19 +166,19 @@ export function ClearConfirmModal({ onClick={onClear} disabled={onClear === undefined} > - <Translate>Clear</Translate> + <i18n.Translate>Clear</i18n.Translate> </button> )} <div class="buttons is-right" style={{ width: "100%" }}> <button class="button " onClick={onCancel}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <button class="button is-info" onClick={onConfirm} disabled={onConfirm === undefined} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </button> </div> </footer> @@ -210,7 +213,7 @@ export function DeleteModal({ onConfirm={() => onConfirm(element.id)} > <p> - If you delete the instance named <b>"{element.name}"</b> (ID:{" "} + If you delete the instance named <b>"{element.name}"</b> (ID:{" "} <b>{element.id}</b>), the merchant will no longer be able to process orders or refunds </p> @@ -241,12 +244,13 @@ export function PurgeModal({ onConfirm={() => onConfirm(element.id)} > <p> - If you purge the instance named <b>"{element.name}"</b> (ID:{" "} - <b>{element.id}</b>), you will also delete all it's transaction data. + If you purge the instance named <b>"{element.name}"</b> (ID:{" "} + <b>{element.id}</b>), you will also delete all it's transaction + data. </p> <p> The instance will disappear from your list, and you will no longer be - able to access it's data. + able to access it's data. </p> <p class="warning"> Purging an instance <b>cannot be undone</b>. @@ -275,20 +279,22 @@ export function UpdateTokenModal({ new_token: "", repeat_token: "", }); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const hasInputTheCorrectOldToken = oldToken && oldToken !== form.old_token; const errors = { old_token: hasInputTheCorrectOldToken - ? i18n`is not the same as the current access token` + ? i18n.str`is not the same as the current access token` : undefined, new_token: !form.new_token - ? i18n`cannot be empty` + ? i18n.str`cannot be empty` : form.new_token === form.old_token - ? i18n`cannot be the same as the old token` + ? i18n.str`cannot be the same as the old token` : undefined, repeat_token: - form.new_token !== form.repeat_token ? i18n`is not the same` : undefined, + form.new_token !== form.repeat_token + ? i18n.str`is not the same` + : undefined, }; const hasErrors = Object.keys(errors).some( @@ -297,7 +303,7 @@ export function UpdateTokenModal({ const instance = useInstanceContext(); - const text = i18n`You are updating the access token from instance with id ${instance.id}`; + const text = i18n.str`You are updating the access token from instance with id ${instance.id}`; return ( <ClearConfirmModal @@ -313,28 +319,28 @@ export function UpdateTokenModal({ {oldToken && ( <Input<State> name="old_token" - label={i18n`Old access token`} - tooltip={i18n`access token currently in use`} + label={i18n.str`Old access token`} + tooltip={i18n.str`access token currently in use`} inputType="password" /> )} <Input<State> name="new_token" - label={i18n`New access token`} - tooltip={i18n`next access token to be used`} + label={i18n.str`New access token`} + tooltip={i18n.str`next access token to be used`} inputType="password" /> <Input<State> name="repeat_token" - label={i18n`Repeat access token`} - tooltip={i18n`confirm the same access token`} + label={i18n.str`Repeat access token`} + tooltip={i18n.str`confirm the same access token`} inputType="password" /> </FormProvider> <p> - <Translate> + <i18n.Translate> Clearing the access token will mean public access to the instance - </Translate> + </i18n.Translate> </p> </div> <div class="column" /> @@ -353,16 +359,18 @@ export function SetTokenNewInstanceModal({ new_token: "", repeat_token: "", }); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const errors = { new_token: !form.new_token - ? i18n`cannot be empty` + ? i18n.str`cannot be empty` : form.new_token === form.old_token - ? i18n`cannot be the same as the old access token` + ? i18n.str`cannot be the same as the old access token` : undefined, repeat_token: - form.new_token !== form.repeat_token ? i18n`is not the same` : undefined, + form.new_token !== form.repeat_token + ? i18n.str`is not the same` + : undefined, }; const hasErrors = Object.keys(errors).some( @@ -374,7 +382,7 @@ export function SetTokenNewInstanceModal({ <div class="modal-background " onClick={onCancel} /> <div class="modal-card"> <header class="modal-card-head"> - <p class="modal-card-title">{i18n`You are setting the access token for the new instance`}</p> + <p class="modal-card-title">{i18n.str`You are setting the access token for the new instance`}</p> <button class="delete " aria-label="close" onClick={onCancel} /> </header> <section class="modal-card-body is-main-section"> @@ -388,22 +396,22 @@ export function SetTokenNewInstanceModal({ > <Input<State> name="new_token" - label={i18n`New access token`} - tooltip={i18n`next access token to be used`} + label={i18n.str`New access token`} + tooltip={i18n.str`next access token to be used`} inputType="password" /> <Input<State> name="repeat_token" - label={i18n`Repeat access token`} - tooltip={i18n`confirm the same access token`} + label={i18n.str`Repeat access token`} + tooltip={i18n.str`confirm the same access token`} inputType="password" /> </FormProvider> <p> - <Translate> + <i18n.Translate> With external authorization method no check will be done by the merchant backend - </Translate> + </i18n.Translate> </p> </div> <div class="column" /> @@ -416,19 +424,19 @@ export function SetTokenNewInstanceModal({ onClick={onClear} disabled={onClear === undefined} > - <Translate>Set external authorization</Translate> + <i18n.Translate>Set external authorization</i18n.Translate> </button> )} <div class="buttons is-right" style={{ width: "100%" }}> <button class="button " onClick={onCancel}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <button class="button is-info" onClick={() => onConfirm(form.new_token!)} disabled={hasErrors} > - <Translate>Set access token</Translate> + <i18n.Translate>Set access token</i18n.Translate> </button> </div> </footer> @@ -443,14 +451,14 @@ export function SetTokenNewInstanceModal({ } export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="modal is-active"> <div class="modal-background " onClick={onCancel} /> <div class="modal-card"> <header class="modal-card-head"> <p class="modal-card-title"> - <Translate>Operation in progress...</Translate> + <i18n.Translate>Operation in progress...</i18n.Translate> </p> </header> <section class="modal-card-body"> @@ -459,12 +467,12 @@ export function LoadingModal({ onCancel }: { onCancel: () => void }): VNode { <Spinner /> <div class="column" /> </div> - <p>{i18n`The operation will be automatically canceled after ${DEFAULT_REQUEST_TIMEOUT} seconds`}</p> + <p>{i18n.str`The operation will be automatically canceled after ${DEFAULT_REQUEST_TIMEOUT} seconds`}</p> </section> <footer class="modal-card-foot"> <div class="buttons is-right" style={{ width: "100%" }}> <button class="button " onClick={onCancel}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> </div> </footer> diff --git a/packages/merchant-backoffice-ui/src/components/picker/DurationPicker.tsx b/packages/merchant-backoffice-ui/src/components/picker/DurationPicker.tsx index 629e50b8c..0e8baac06 100644 --- a/packages/merchant-backoffice-ui/src/components/picker/DurationPicker.tsx +++ b/packages/merchant-backoffice-ui/src/components/picker/DurationPicker.tsx @@ -19,9 +19,9 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { useTranslator } from "../../i18n/index.js"; import "../../scss/DurationPicker.scss"; export interface Props { @@ -46,13 +46,13 @@ export function DurationPicker({ const ms = ss * 60; const hs = ms * 60; const ds = hs * 24; - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="rdp-picker"> {days && ( <DurationColumn - unit={i18n`days`} + unit={i18n.str`days`} max={99} value={Math.floor(value / ds)} onDecrease={value >= ds ? () => onChange(value - ds) : undefined} @@ -62,7 +62,7 @@ export function DurationPicker({ )} {hours && ( <DurationColumn - unit={i18n`hours`} + unit={i18n.str`hours`} max={23} min={1} value={Math.floor(value / hs) % 24} @@ -73,7 +73,7 @@ export function DurationPicker({ )} {minutes && ( <DurationColumn - unit={i18n`minutes`} + unit={i18n.str`minutes`} max={59} min={1} value={Math.floor(value / ms) % 60} @@ -84,7 +84,7 @@ export function DurationPicker({ )} {seconds && ( <DurationColumn - unit={i18n`seconds`} + unit={i18n.str`seconds`} max={59} value={Math.floor(value / ss) % 60} onDecrease={value >= ss ? () => onChange(value - ss) : undefined} diff --git a/packages/merchant-backoffice-ui/src/components/product/InventoryProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/InventoryProductForm.tsx index da47f1be3..0cb2d555e 100644 --- a/packages/merchant-backoffice-ui/src/components/product/InventoryProductForm.tsx +++ b/packages/merchant-backoffice-ui/src/components/product/InventoryProductForm.tsx @@ -13,14 +13,14 @@ 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import { FormProvider, FormErrors } from "../form/FormProvider.js"; -import { InputNumber } from "../form/InputNumber.js"; -import { InputSearchProduct } from "../form/InputSearchProduct.js"; import { MerchantBackend, WithId } from "../../declaration.js"; -import { Translate, useTranslator } from "../../i18n/index.js"; import { ProductMap } from "../../paths/instance/orders/create/CreatePage.js"; +import { FormErrors, FormProvider } from "../form/FormProvider.js"; +import { InputNumber } from "../form/InputNumber.js"; +import { InputSearchProduct } from "../form/InputSearchProduct.js"; type Form = { product: MerchantBackend.Products.ProductDetail & WithId; @@ -45,21 +45,23 @@ export function InventoryProductForm({ const [state, setState] = useState<Partial<Form>>(initialState); const [errors, setErrors] = useState<FormErrors<Form>>({}); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const productWithInfiniteStock = state.product && state.product.total_stock === -1; const submit = (): void => { if (!state.product) { - setErrors({ product: i18n`You must enter a valid product identifier.` }); + setErrors({ + product: i18n.str`You must enter a valid product identifier.`, + }); return; } if (productWithInfiniteStock) { onAddProduct(state.product, 1); } else { if (!state.quantity || state.quantity <= 0) { - setErrors({ quantity: i18n`Quantity must be greater than 0!` }); + setErrors({ quantity: i18n.str`Quantity must be greater than 0!` }); return; } const currentStock = @@ -71,7 +73,7 @@ export function InventoryProductForm({ if (state.quantity + p.quantity > currentStock) { const left = currentStock - p.quantity; setErrors({ - quantity: i18n`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`, + quantity: i18n.str`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`, }); return; } @@ -80,7 +82,7 @@ export function InventoryProductForm({ if (state.quantity > currentStock) { const left = currentStock; setErrors({ - quantity: i18n`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`, + quantity: i18n.str`This quantity exceeds remaining stock. Currently, only ${left} units remain unreserved in stock.`, }); return; } @@ -104,15 +106,15 @@ export function InventoryProductForm({ {!productWithInfiniteStock && ( <InputNumber<Form> name="quantity" - label={i18n`Quantity`} - tooltip={i18n`how many products will be added`} + label={i18n.str`Quantity`} + tooltip={i18n.str`how many products will be added`} /> )} </div> <div class="column"> <div class="buttons is-right"> <button class="button is-success" onClick={submit}> - <Translate>Add from inventory</Translate> + <i18n.Translate>Add from inventory</i18n.Translate> </button> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/components/product/NonInventoryProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/NonInventoryProductForm.tsx index fe9692c02..3e7262657 100644 --- a/packages/merchant-backoffice-ui/src/components/product/NonInventoryProductForm.tsx +++ b/packages/merchant-backoffice-ui/src/components/product/NonInventoryProductForm.tsx @@ -13,19 +13,19 @@ 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useCallback, useEffect, useState } from "preact/hooks"; import * as yup from "yup"; +import { MerchantBackend } from "../../declaration.js"; +import { useListener } from "../../hooks/listener.js"; +import { NonInventoryProductSchema as schema } from "../../schemas/index.js"; import { FormErrors, FormProvider } from "../form/FormProvider.js"; import { Input } from "../form/Input.js"; import { InputCurrency } from "../form/InputCurrency.js"; import { InputImage } from "../form/InputImage.js"; import { InputNumber } from "../form/InputNumber.js"; import { InputTaxes } from "../form/InputTaxes.js"; -import { MerchantBackend } from "../../declaration.js"; -import { useListener } from "../../hooks/listener.js"; -import { Translate, useTranslator } from "../../i18n/index.js"; -import { NonInventoryProductSchema as schema } from "../../schemas/index.js"; type Entity = MerchantBackend.Product; @@ -62,17 +62,17 @@ export function NonInventoryProductFrom({ return Promise.resolve(); }); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <Fragment> <div class="buttons"> <button class="button is-success" - data-tooltip={i18n`describe and add a product that is not in the inventory list`} + data-tooltip={i18n.str`describe and add a product that is not in the inventory list`} onClick={() => setShowCreateProduct(true)} > - <Translate>Add custom product</Translate> + <i18n.Translate>Add custom product</i18n.Translate> </button> </div> {showCreateProduct && ( @@ -83,7 +83,7 @@ export function NonInventoryProductFrom({ /> <div class="modal-card"> <header class="modal-card-head"> - <p class="modal-card-title">{i18n`Complete information of the product`}</p> + <p class="modal-card-title">{i18n.str`Complete information of the product`}</p> <button class="delete " aria-label="close" @@ -102,14 +102,14 @@ export function NonInventoryProductFrom({ class="button " onClick={() => setShowCreateProduct(false)} > - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <button class="button is-info " disabled={!submitForm} onClick={submitForm} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </button> </div> </footer> @@ -170,7 +170,7 @@ export function ProductForm({ onSubscribe, initial }: ProductProps): VNode { onSubscribe(hasErrors ? undefined : submit); }, [submit, hasErrors]); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div> @@ -182,33 +182,33 @@ export function ProductForm({ onSubscribe, initial }: ProductProps): VNode { > <InputImage<NonInventoryProduct> name="image" - label={i18n`Image`} - tooltip={i18n`photo of the product`} + label={i18n.str`Image`} + tooltip={i18n.str`photo of the product`} /> <Input<NonInventoryProduct> name="description" inputType="multiline" - label={i18n`Description`} - tooltip={i18n`full product description`} + label={i18n.str`Description`} + tooltip={i18n.str`full product description`} /> <Input<NonInventoryProduct> name="unit" - label={i18n`Unit`} - tooltip={i18n`name of the product unit`} + label={i18n.str`Unit`} + tooltip={i18n.str`name of the product unit`} /> <InputCurrency<NonInventoryProduct> name="price" - label={i18n`Price`} - tooltip={i18n`amount in the current currency`} + label={i18n.str`Price`} + tooltip={i18n.str`amount in the current currency`} /> <InputNumber<NonInventoryProduct> name="quantity" - label={i18n`Quantity`} - tooltip={i18n`how many products will be added`} + label={i18n.str`Quantity`} + tooltip={i18n.str`how many products will be added`} /> - <InputTaxes<NonInventoryProduct> name="taxes" label={i18n`Taxes`} /> + <InputTaxes<NonInventoryProduct> name="taxes" label={i18n.str`Taxes`} /> </FormProvider> </div> ); diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx index a6bb090a5..973f88677 100644 --- a/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx +++ b/packages/merchant-backoffice-ui/src/components/product/ProductForm.tsx @@ -19,17 +19,17 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h } from "preact"; import { useCallback, useEffect, useState } from "preact/hooks"; import * as yup from "yup"; import { useBackendContext } from "../../context/backend.js"; import { MerchantBackend } from "../../declaration.js"; -import { useTranslator } from "../../i18n/index.js"; import { ProductCreateSchema as createSchema, ProductUpdateSchema as updateSchema, } from "../../schemas/index.js"; -import { FormProvider, FormErrors } from "../form/FormProvider.js"; +import { FormErrors, FormProvider } from "../form/FormProvider.js"; import { Input } from "../form/Input.js"; import { InputCurrency } from "../form/InputCurrency.js"; import { InputImage } from "../form/InputImage.js"; @@ -115,7 +115,7 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { }, [submit, hasErrors]); const backend = useBackendContext(); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div> @@ -129,46 +129,46 @@ export function ProductForm({ onSubscribe, initial, alreadyExist }: Props) { <InputWithAddon<Entity> name="product_id" addonBefore={`${backend.url}/product/`} - label={i18n`ID`} - tooltip={i18n`product identification to use in URLs (for internal use only)`} + label={i18n.str`ID`} + tooltip={i18n.str`product identification to use in URLs (for internal use only)`} /> )} <InputImage<Entity> name="image" - label={i18n`Image`} - tooltip={i18n`illustration of the product for customers`} + label={i18n.str`Image`} + tooltip={i18n.str`illustration of the product for customers`} /> <Input<Entity> name="description" inputType="multiline" - label={i18n`Description`} - tooltip={i18n`product description for customers`} + label={i18n.str`Description`} + tooltip={i18n.str`product description for customers`} /> <InputNumber<Entity> name="minimum_age" - label={i18n`Age restricted`} - tooltip={i18n`is this product restricted for customer below certain age?`} + label={i18n.str`Age restricted`} + tooltip={i18n.str`is this product restricted for customer below certain age?`} /> <Input<Entity> name="unit" - label={i18n`Unit`} - tooltip={i18n`unit describing quantity of product sold (e.g. 2 kilograms, 5 liters, 3 items, 5 meters) for customers`} + label={i18n.str`Unit`} + tooltip={i18n.str`unit describing quantity of product sold (e.g. 2 kilograms, 5 liters, 3 items, 5 meters) for customers`} /> <InputCurrency<Entity> name="price" - label={i18n`Price`} - tooltip={i18n`sale price for customers, including taxes, for above units of the product`} + label={i18n.str`Price`} + tooltip={i18n.str`sale price for customers, including taxes, for above units of the product`} /> <InputStock name="stock" - label={i18n`Stock`} + label={i18n.str`Stock`} alreadyExist={alreadyExist} - tooltip={i18n`product inventory for products with finite supply (for internal use only)`} + tooltip={i18n.str`product inventory for products with finite supply (for internal use only)`} /> <InputTaxes<Entity> name="taxes" - label={i18n`Taxes`} - tooltip={i18n`taxes included in the product price, exposed to customers`} + label={i18n.str`Taxes`} + tooltip={i18n.str`taxes included in the product price, exposed to customers`} /> </FormProvider> </div> diff --git a/packages/merchant-backoffice-ui/src/components/product/ProductList.tsx b/packages/merchant-backoffice-ui/src/components/product/ProductList.tsx index 774da8975..29556ea70 100644 --- a/packages/merchant-backoffice-ui/src/components/product/ProductList.tsx +++ b/packages/merchant-backoffice-ui/src/components/product/ProductList.tsx @@ -16,8 +16,8 @@ import { Amounts } from "@gnu-taler/taler-util"; import { h, VNode } from "preact"; import emptyImage from "../../assets/empty.png"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { MerchantBackend } from "../../declaration.js"; -import { Translate } from "../../i18n/index.js"; interface Props { list: MerchantBackend.Product[]; @@ -28,25 +28,26 @@ interface Props { }[]; } export function ProductList({ list, actions = [] }: Props): VNode { + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>image</Translate> + <i18n.Translate>image</i18n.Translate> </th> <th> - <Translate>description</Translate> + <i18n.Translate>description</i18n.Translate> </th> <th> - <Translate>quantity</Translate> + <i18n.Translate>quantity</i18n.Translate> </th> <th> - <Translate>unit price</Translate> + <i18n.Translate>unit price</i18n.Translate> </th> <th> - <Translate>total price</Translate> + <i18n.Translate>total price</i18n.Translate> </th> <th /> </tr> diff --git a/packages/merchant-backoffice-ui/src/context/fetch.ts b/packages/merchant-backoffice-ui/src/context/fetch.ts index 3106a6abe..88c9bc30c 100644 --- a/packages/merchant-backoffice-ui/src/context/fetch.ts +++ b/packages/merchant-backoffice-ui/src/context/fetch.ts @@ -29,7 +29,7 @@ interface Type { useSWRInfinite: typeof useSWRInfinite; } -const Context = createContext<Type>({} as any); +const Context = createContext<Type>({} as Type); export const useFetchContext = (): Type => useContext(Context); export const FetchContextProvider = ({ diff --git a/packages/merchant-backoffice-ui/src/context/listener.ts b/packages/merchant-backoffice-ui/src/context/listener.ts deleted file mode 100644 index cf51bb718..000000000 --- a/packages/merchant-backoffice-ui/src/context/listener.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2023 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 } from "preact"; -import { useContext } from "preact/hooks"; - -interface Type { - id: string; - token?: string; - admin?: boolean; - changeToken: (t?: string) => void; -} - -const Context = createContext<Type>({} as any); - -export const ListenerContextProvider = Context.Provider; -export const useListenerContext = (): Type => useContext(Context); diff --git a/packages/merchant-backoffice-ui/src/context/translation.ts b/packages/merchant-backoffice-ui/src/context/translation.ts deleted file mode 100644 index 027eac14c..000000000 --- a/packages/merchant-backoffice-ui/src/context/translation.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2023 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"; -import * as jedLib from "jed"; -import { strings } from "../i18n/strings"; - -interface Type { - lang: string; - handler: any; - changeLanguage: (l: string) => void; -} -const initial = { - lang: "en", - handler: null, - changeLanguage: () => { - // do not change anything - }, -}; -const Context = createContext<Type>(initial); - -interface Props { - initial?: string; - children: any; - forceLang?: string; -} - -export const TranslationProvider = ({ - initial, - children, - forceLang, -}: Props): VNode => { - const [lang, changeLanguage] = useLang(initial); - useEffect(() => { - if (forceLang) { - changeLanguage(forceLang); - } - }); - const handler = new jedLib.Jed(strings[lang]); - return h(Context.Provider, { - value: { lang, handler, changeLanguage }, - children, - }); -}; - -export const useTranslationContext = (): Type => useContext(Context); diff --git a/packages/merchant-backoffice-ui/src/i18n/index.tsx b/packages/merchant-backoffice-ui/src/i18n/index.tsx deleted file mode 100644 index da8d2dece..000000000 --- a/packages/merchant-backoffice-ui/src/i18n/index.tsx +++ /dev/null @@ -1,215 +0,0 @@ -/* - This file is part of GNU Taler - (C) 2021-2023 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/> - */ - -/** - * Translation helpers for React components and template literals. - */ - -/** - * Imports - */ -import { ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact"; - -import { useTranslationContext } from "../context/translation.js"; - -export type Translator = ( - stringSeq: TemplateStringsArray, - ...values: any[] -) => string; -export function useTranslator(): Translator { - const ctx = useTranslationContext(); - const jed = ctx.handler; - return function str( - stringSeq: TemplateStringsArray, - ...values: any[] - ): string { - const s = toI18nString(stringSeq); - if (!s) return s; - const tr = jed - .translate(s) - .ifPlural(1, s) - .fetch(...values); - return tr; - }; -} - -/** - * 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; -} - -interface TranslateSwitchProps { - target: number; - children: ComponentChildren; -} - -function stringifyChildren(children: ComponentChildren): string { - let n = 1; - const ss = (children instanceof Array ? children : [children]).map((c) => { - if (typeof c === "string") { - return c; - } - return `%${n++}$s`; - }); - const s = ss.join("").replace(/ +/g, " ").trim(); - return s; -} - -interface TranslateProps { - children: ComponentChildren; - /** - * Component that the translated element should be wrapped in. - * Defaults to "div". - */ - wrap?: any; - - /** - * Props to give to the wrapped component. - */ - wrapProps?: any; -} - -function getTranslatedChildren( - translation: string, - children: ComponentChildren, -): ComponentChild[] { - const tr = translation.split(/%(\d+)\$s/); - const childArray = children instanceof Array ? children : [children]; - // Merge consecutive string children. - const placeholderChildren = Array<ComponentChild>(); - 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 = Array<ComponentChild>(); - for (let i = 0; i < tr.length; i++) { - if (i % 2 == 0) { - // Text - result.push(tr[i]); - } else { - const childIdx = Number.parseInt(tr[i], 10) - 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 }: TranslateProps): VNode { - const s = stringifyChildren(children); - const ctx = useTranslationContext(); - const translation: string = ctx.handler.ngettext(s, s, 1); - const result = getTranslatedChildren(translation, children); - return <Fragment>{result}</Fragment>; -} - -/** - * 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 function TranslateSwitch({ children, target }: TranslateSwitchProps) { - let singular: VNode<TranslationPluralProps> | undefined; - let plural: VNode<TranslationPluralProps> | undefined; - // const children = this.props.children; - if (children) { - (children instanceof Array ? children : [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 = target; - plural.props.target = target; - // We're looking up the translation based on the - // singular, even if we must use the plural form. - return singular; -} - -interface TranslationPluralProps { - children: ComponentChildren; - target: number; -} - -/** - * See [[TranslateSwitch]]. - */ -export function TranslatePlural({ - children, - target, -}: TranslationPluralProps): VNode { - const s = stringifyChildren(children); - const ctx = useTranslationContext(); - const translation = ctx.handler.ngettext(s, s, 1); - const result = getTranslatedChildren(translation, children); - return <Fragment>{result}</Fragment>; -} - -/** - * See [[TranslateSwitch]]. - */ -export function TranslateSingular({ - children, - target, -}: TranslationPluralProps): VNode { - const s = stringifyChildren(children); - const ctx = useTranslationContext(); - const translation = ctx.handler.ngettext(s, s, target); - const result = getTranslatedChildren(translation, children); - return <Fragment>{result}</Fragment>; -} diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx index 052d61544..91b6b4b56 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/Create.stories.tsx @@ -20,6 +20,7 @@ */ import { h, VNode, FunctionalComponent } from "preact"; +import { ConfigContextProvider } from "../../../context/config.js"; import { CreatePage as TestedComponent } from "./CreatePage.js"; export default { @@ -35,7 +36,16 @@ function createExample<Props>( Component: FunctionalComponent<Props>, props: Partial<Props>, ) { - const r = (args: any) => <Component {...args} />; + const r = (args: any) => ( + <ConfigContextProvider + value={{ + currency: "ARS", + version: "1", + }} + > + <Component {...args} /> + </ConfigContextProvider> + ); r.args = props; return r; } diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx index 6fcabb18b..bf5f5d7c9 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/CreatePage.tsx @@ -19,20 +19,19 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import * as yup from "yup"; import { AsyncButton } from "../../../components/exception/AsyncButton.js"; import { FormErrors, FormProvider, } from "../../../components/form/FormProvider.js"; +import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js"; import { SetTokenNewInstanceModal } from "../../../components/modal/index.js"; import { MerchantBackend } from "../../../declaration.js"; -import { Translate, useTranslator } from "../../../i18n/index.js"; -import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js"; import { INSTANCE_ID_REGEX, PAYTO_REGEX } from "../../../utils/constants.js"; -import { Amounts } from "@gnu-taler/taler-util"; import { undefinedIfEmpty } from "../../../utils/table.js"; export type Entity = MerchantBackend.Instances.InstanceConfigurationMessage & { @@ -61,55 +60,57 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { const [isTokenDialogActive, updateIsTokenDialogActive] = useState<boolean>(false); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const errors: FormErrors<Entity> = { id: !value.id - ? i18n`required` + ? i18n.str`required` : !INSTANCE_ID_REGEX.test(value.id) - ? i18n`is not valid` + ? i18n.str`is not valid` : undefined, - name: !value.name ? i18n`required` : undefined, + name: !value.name ? i18n.str`required` : undefined, payto_uris: !value.payto_uris || !value.payto_uris.length - ? i18n`required` + ? i18n.str`required` : undefinedIfEmpty( value.payto_uris.map((p) => { - return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined; + return !PAYTO_REGEX.test(p) ? i18n.str`is not valid` : undefined; }), ), default_max_deposit_fee: !value.default_max_deposit_fee - ? i18n`required` + ? i18n.str`required` : !Amounts.parse(value.default_max_deposit_fee) - ? i18n`invalid format` + ? i18n.str`invalid format` : undefined, default_max_wire_fee: !value.default_max_wire_fee - ? i18n`required` + ? i18n.str`required` : !Amounts.parse(value.default_max_wire_fee) - ? i18n`invalid format` + ? i18n.str`invalid format` : undefined, default_wire_fee_amortization: value.default_wire_fee_amortization === undefined - ? i18n`required` + ? i18n.str`required` : isNaN(value.default_wire_fee_amortization) - ? i18n`is not a number` + ? i18n.str`is not a number` : value.default_wire_fee_amortization < 1 - ? i18n`must be 1 or greater` + ? i18n.str`must be 1 or greater` : undefined, - default_pay_delay: !value.default_pay_delay ? i18n`required` : undefined, + default_pay_delay: !value.default_pay_delay + ? i18n.str`required` + : undefined, default_wire_transfer_delay: !value.default_wire_transfer_delay - ? i18n`required` + ? i18n.str`required` : undefined, address: undefinedIfEmpty({ address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 - ? i18n`max 7 lines` + ? i18n.str`max 7 lines` : undefined, }), jurisdiction: undefinedIfEmpty({ address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 - ? i18n`max 7 lines` + ? i18n.str`max 7 lines` : undefined, }), }; @@ -174,14 +175,14 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { <h1 class="title"> <button class="button is-danger has-tooltip-bottom" - data-tooltip={i18n`change authorization configuration`} + data-tooltip={i18n.str`change authorization configuration`} onClick={() => updateIsTokenDialogActive(true)} > <div class="icon is-centered"> <i class="mdi mdi-lock-reset" /> </div> <span> - <Translate>Set access token</Translate> + <i18n.Translate>Set access token</i18n.Translate> </span> </button> </h1> @@ -205,7 +206,7 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <AsyncButton @@ -213,11 +214,11 @@ export function CreatePage({ onCreate, onBack, forceId }: Props): VNode { disabled={!isTokenSet || hasErrors} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields and choose authorization method` + ? i18n.str`Need to complete marked fields and choose authorization method` : "confirm operation" } > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx index ed2f3f068..4da6916a0 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/index.tsx @@ -17,12 +17,12 @@ * * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../../components/menu/index.js"; import { MerchantBackend } from "../../../declaration.js"; import { useAdminAPI } from "../../../hooks/instance.js"; -import { useTranslator } from "../../../i18n/index.js"; import { Notification } from "../../../utils/types.js"; import { CreatePage } from "./CreatePage.js"; import { InstanceCreatedSuccessfully } from "./InstanceCreatedSuccessfully.js"; @@ -38,7 +38,7 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode { const { createInstance } = useAdminAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const [createdOk, setCreatedOk] = useState<Entity | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (createdOk) { return ( @@ -62,7 +62,7 @@ export default function Create({ onBack, onConfirm, forceId }: Props): VNode { }) .catch((error) => { setNotif({ - message: i18n`Failed to create instance`, + message: i18n.str`Failed to create instance`, type: "ERROR", description: error.message, }); diff --git a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.ts b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx index 45b94ec8c..0012f9b9b 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/create/stories.ts +++ b/packages/merchant-backoffice-ui/src/paths/admin/create/stories.tsx @@ -20,6 +20,7 @@ */ import { h, VNode, FunctionalComponent } from "preact"; +import { ConfigContextProvider } from "../../../context/config.js"; import { CreatePage as TestedComponent } from "./CreatePage.js"; export default { @@ -32,17 +33,20 @@ export default { }; function createExample<Props>( - Component: FunctionalComponent<Props>, + Internal: FunctionalComponent<Props>, props: Partial<Props>, ) { - const r = (args: any) => h(Component, args); - // const r = (args: any) => <Component {...args} />; - r.args = props; - return r; + const component = (args: any) => ( + <ConfigContextProvider + value={{ + currency: "TESTKUDOS", + version: "1", + }} + > + <Internal {...(props as any)} /> + </ConfigContextProvider> + ); + return { component, props }; } export const Example = createExample(TestedComponent, {}); -// export const Example = (a: any): VNode => <CreatePage {...a} />; -// Example.args = { -// isLoading: false -// } diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx index 546f34f3a..223db2fed 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/list/TableActive.tsx @@ -19,10 +19,10 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { StateUpdater, useEffect, useState } from "preact/hooks"; import { MerchantBackend } from "../../../declaration.js"; -import { Translate, useTranslator } from "../../../i18n/index.js"; interface Props { instances: MerchantBackend.Instances.Instance[]; @@ -68,7 +68,7 @@ export function CardTable({ } }, [actionQueue, selected, onUpdate]); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="card has-table"> @@ -77,7 +77,7 @@ export function CardTable({ <span class="icon"> <i class="mdi mdi-desktop-mac" /> </span> - <Translate>Instances</Translate> + <i18n.Translate>Instances</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options"> @@ -90,11 +90,14 @@ export function CardTable({ ) } > - <Translate>Delete</Translate> + <i18n.Translate>Delete</i18n.Translate> </button> </div> <div class="card-header-icon" aria-label="more options"> - <span class="has-tooltip-left" data-tooltip={i18n`add new instance`}> + <span + class="has-tooltip-left" + data-tooltip={i18n.str`add new instance`} + > <button class="button is-info" type="button" onClick={onCreate}> <span class="icon is-small"> <i class="mdi mdi-plus mdi-36px" /> @@ -149,6 +152,7 @@ function Table({ onDelete, onPurge, }: TableProps): VNode { + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> @@ -171,10 +175,10 @@ function Table({ </label> </th> <th> - <Translate>ID</Translate> + <i18n.Translate>ID</i18n.Translate> </th> <th> - <Translate>Name</Translate> + <i18n.Translate>Name</i18n.Translate> </th> <th /> </tr> @@ -213,7 +217,7 @@ function Table({ type="button" onClick={(): void => onUpdate(i.id)} > - <Translate>Edit</Translate> + <i18n.Translate>Edit</i18n.Translate> </button> {!i.deleted && ( <button @@ -221,7 +225,7 @@ function Table({ type="button" onClick={(): void => onDelete(i)} > - <Translate>Delete</Translate> + <i18n.Translate>Delete</i18n.Translate> </button> )} {i.deleted && ( @@ -230,7 +234,7 @@ function Table({ type="button" onClick={(): void => onPurge(i)} > - <Translate>Purge</Translate> + <i18n.Translate>Purge</i18n.Translate> </button> )} </div> @@ -245,6 +249,7 @@ function Table({ } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -253,9 +258,9 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate> + <i18n.Translate> There is no instances yet, add more pressing the + sign - </Translate> + </i18n.Translate> </p> </div> ); diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/View.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/View.tsx index 5180c671c..7376a88cb 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/list/View.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/list/View.tsx @@ -19,11 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; +import { useState } from "preact/hooks"; import { MerchantBackend } from "../../../declaration.js"; import { CardTable as CardTableActive } from "./TableActive.js"; -import { useState } from "preact/hooks"; -import { Translate, useTranslator } from "../../../i18n/index.js"; interface Props { instances: MerchantBackend.Instances.Instance[]; @@ -48,7 +48,7 @@ export function View({ const showIsActive = show === "active" ? "is-active" : ""; const showIsDeleted = show === "deleted" ? "is-active" : ""; const showAll = show === null ? "is-active" : ""; - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const showingInstances = showIsDeleted ? instances.filter((i) => i.deleted) @@ -66,30 +66,30 @@ export function View({ <li class={showIsActive}> <div class="has-tooltip-right" - data-tooltip={i18n`Only show active instances`} + data-tooltip={i18n.str`Only show active instances`} > <a onClick={() => setShow("active")}> - <Translate>Active</Translate> + <i18n.Translate>Active</i18n.Translate> </a> </div> </li> <li class={showIsDeleted}> <div class="has-tooltip-right" - data-tooltip={i18n`Only show deleted instances`} + data-tooltip={i18n.str`Only show deleted instances`} > <a onClick={() => setShow("deleted")}> - <Translate>Deleted</Translate> + <i18n.Translate>Deleted</i18n.Translate> </a> </div> </li> <li class={showAll}> <div class="has-tooltip-right" - data-tooltip={i18n`Show all instances`} + data-tooltip={i18n.str`Show all instances`} > <a onClick={() => setShow(null)}> - <Translate>All</Translate> + <i18n.Translate>All</i18n.Translate> </a> </div> </li> diff --git a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx index 36d58dd41..9a81b72d4 100644 --- a/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/admin/list/index.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../components/exception/loading.js"; @@ -27,7 +28,6 @@ import { DeleteModal, PurgeModal } from "../../../components/modal/index.js"; import { MerchantBackend } from "../../../declaration.js"; import { HttpError } from "../../../hooks/backend.js"; import { useAdminAPI, useBackendInstances } from "../../../hooks/instance.js"; -import { useTranslator } from "../../../i18n/index.js"; import { Notification } from "../../../utils/types.js"; import { View } from "./View.js"; @@ -56,7 +56,7 @@ export default function Instances({ useState<MerchantBackend.Instances.Instance | null>(null); const { deleteInstance, purgeInstance } = useAdminAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isNotfound) return onNotFound(); @@ -84,12 +84,12 @@ export default function Instances({ await deleteInstance(deleting.id); // pushNotification({ message: 'delete_success', type: 'SUCCESS' }) setNotif({ - message: i18n`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`, + message: i18n.str`Instance "${deleting.name}" (ID: ${deleting.id}) has been deleted`, type: "SUCCESS", }); } catch (error) { setNotif({ - message: i18n`Failed to delete instance`, + message: i18n.str`Failed to delete instance`, type: "ERROR", description: error instanceof Error ? error.message : undefined, }); @@ -107,12 +107,12 @@ export default function Instances({ try { await purgeInstance(purging.id); setNotif({ - message: i18n`Instance "${purging.name}" (ID: ${purging.id}) has been disabled`, + message: i18n.str`Instance "${purging.name}" (ID: ${purging.id}) has been disabled`, type: "SUCCESS", }); } catch (error) { setNotif({ - message: i18n`Failed to purge instance`, + message: i18n.str`Failed to purge instance`, type: "ERROR", description: error instanceof Error ? error.message : undefined, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx index 59aa7a1d2..4ab415e85 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/details/DetailPage.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { FormProvider } from "../../../components/form/FormProvider.js"; import { Input } from "../../../components/form/Input.js"; import { MerchantBackend } from "../../../declaration.js"; -import { useTranslator } from "../../../i18n/index.js"; type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage; interface Props { @@ -49,7 +49,7 @@ function convert( export function DetailPage({ selected }: Props): VNode { const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div> @@ -73,11 +73,11 @@ export function DetailPage({ selected }: Props): VNode { <div class="column" /> <div class="column is-6"> <FormProvider<Entity> object={value} valueHandler={valueHandler}> - <Input<Entity> name="name" readonly label={i18n`Name`} /> + <Input<Entity> name="name" readonly label={i18n.str`Name`} /> <Input<Entity> name="payto_uris" readonly - label={i18n`Account address`} + label={i18n.str`Account address`} /> </FormProvider> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx index 9ac1c4381..cdbe732a4 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/details/stories.tsx @@ -20,6 +20,7 @@ */ import { h, VNode, FunctionalComponent } from "preact"; +import { ConfigContextProvider } from "../../../context/config.js"; import { DetailPage as TestedComponent } from "./DetailPage.js"; export default { @@ -32,12 +33,20 @@ export default { }; function createExample<Props>( - Component: FunctionalComponent<Props>, + Internal: FunctionalComponent<Props>, props: Partial<Props>, ) { - const r = (args: any) => <Component {...args} />; - r.args = props; - return r; + const component = (args: any) => ( + <ConfigContextProvider + value={{ + currency: "TESTKUDOS", + version: "1", + }} + > + <Internal {...(props as any)} /> + </ConfigContextProvider> + ); + return { component, props }; } export const Example = createExample(TestedComponent, { diff --git a/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts b/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts index 3670c2807..fcb75cd7c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts +++ b/packages/merchant-backoffice-ui/src/paths/instance/index.stories.ts @@ -15,4 +15,3 @@ */ export * as details from "./details/stories.js"; - diff --git a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx index c10121fb3..7966a2cdd 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/kyc/list/ListPage.tsx @@ -19,16 +19,16 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { MerchantBackend } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; export interface Props { status: MerchantBackend.Instances.AccountKycRedirects; } export function ListPage({ status }: Props): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <section class="section is-main-section"> @@ -38,7 +38,7 @@ export function ListPage({ status }: Props): VNode { <span class="icon"> <i class="mdi mdi-clock" /> </span> - <Translate>Pending KYC verification</Translate> + <i18n.Translate>Pending KYC verification</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options" /> @@ -63,7 +63,7 @@ export function ListPage({ status }: Props): VNode { <span class="icon"> <i class="mdi mdi-clock" /> </span> - <Translate>Timed out</Translate> + <i18n.Translate>Timed out</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options" /> @@ -93,19 +93,20 @@ interface TimedOutTableProps { } function PendingTable({ entries }: PendingTableProps): VNode { + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>Exchange</Translate> + <i18n.Translate>Exchange</i18n.Translate> </th> <th> - <Translate>Target account</Translate> + <i18n.Translate>Target account</i18n.Translate> </th> <th> - <Translate>KYC URL</Translate> + <i18n.Translate>KYC URL</i18n.Translate> </th> </tr> </thead> @@ -130,19 +131,20 @@ function PendingTable({ entries }: PendingTableProps): VNode { } function TimedOutTable({ entries }: TimedOutTableProps): VNode { + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>Exchange</Translate> + <i18n.Translate>Exchange</i18n.Translate> </th> <th> - <Translate>Code</Translate> + <i18n.Translate>Code</i18n.Translate> </th> <th> - <Translate>Http Status</Translate> + <i18n.Translate>Http Status</i18n.Translate> </th> </tr> </thead> @@ -163,6 +165,7 @@ function TimedOutTable({ entries }: TimedOutTableProps): VNode { } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -171,7 +174,7 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate>No pending kyc verification!</Translate> + <i18n.Translate>No pending kyc verification!</i18n.Translate> </p> </div> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx index 145df717d..0c2e92418 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/CreatePage.tsx @@ -19,29 +19,29 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { add, isAfter, isBefore, isFuture } from "date-fns"; import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; +import { add, isAfter, isBefore, isFuture } from "date-fns"; import { Fragment, h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { - FormProvider, FormErrors, + FormProvider, } from "../../../../components/form/FormProvider.js"; import { Input } from "../../../../components/form/Input.js"; +import { InputBoolean } from "../../../../components/form/InputBoolean.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputDate } from "../../../../components/form/InputDate.js"; import { InputGroup } from "../../../../components/form/InputGroup.js"; import { InputLocation } from "../../../../components/form/InputLocation.js"; +import { InputNumber } from "../../../../components/form/InputNumber.js"; +import { InventoryProductForm } from "../../../../components/product/InventoryProductForm.js"; +import { NonInventoryProductFrom } from "../../../../components/product/NonInventoryProductForm.js"; import { ProductList } from "../../../../components/product/ProductList.js"; import { useConfigContext } from "../../../../context/config.js"; import { Duration, MerchantBackend, WithId } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { OrderCreateSchema as schema } from "../../../../schemas/index.js"; import { rate } from "../../../../utils/amount.js"; -import { InventoryProductForm } from "../../../../components/product/InventoryProductForm.js"; -import { NonInventoryProductFrom } from "../../../../components/product/NonInventoryProductForm.js"; -import { InputNumber } from "../../../../components/form/InputNumber.js"; -import { InputBoolean } from "../../../../components/form/InputBoolean.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; interface Props { @@ -140,65 +140,65 @@ export function CreatePage({ const inventoryList = Object.values(value.inventoryProducts || {}); const productList = Object.values(value.products || {}); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const errors: FormErrors<Entity> = { pricing: undefinedIfEmpty({ - summary: !value.pricing?.summary ? i18n`required` : undefined, + summary: !value.pricing?.summary ? i18n.str`required` : undefined, order_price: !value.pricing?.order_price - ? i18n`required` + ? i18n.str`required` : Amounts.isZero(value.pricing.order_price) - ? i18n`must be greater than 0` + ? i18n.str`must be greater than 0` : undefined, }), extra: value.extra && !stringIsValidJSON(value.extra) - ? i18n`not a valid json` + ? i18n.str`not a valid json` : undefined, payments: undefinedIfEmpty({ refund_deadline: !value.payments?.refund_deadline ? undefined : !isFuture(value.payments.refund_deadline) - ? i18n`should be in the future` + ? i18n.str`should be in the future` : value.payments.pay_deadline && isBefore(value.payments.refund_deadline, value.payments.pay_deadline) - ? i18n`refund deadline cannot be before pay deadline` + ? i18n.str`refund deadline cannot be before pay deadline` : value.payments.wire_transfer_deadline && isBefore( value.payments.wire_transfer_deadline, value.payments.refund_deadline, ) - ? i18n`wire transfer deadline cannot be before refund deadline` + ? i18n.str`wire transfer deadline cannot be before refund deadline` : undefined, pay_deadline: !value.payments?.pay_deadline ? undefined : !isFuture(value.payments.pay_deadline) - ? i18n`should be in the future` + ? i18n.str`should be in the future` : value.payments.wire_transfer_deadline && isBefore( value.payments.wire_transfer_deadline, value.payments.pay_deadline, ) - ? i18n`wire transfer deadline cannot be before pay deadline` + ? i18n.str`wire transfer deadline cannot be before pay deadline` : undefined, auto_refund_deadline: !value.payments?.auto_refund_deadline ? undefined : !isFuture(value.payments.auto_refund_deadline) - ? i18n`should be in the future` + ? i18n.str`should be in the future` : !value.payments?.refund_deadline - ? i18n`should have a refund deadline` + ? i18n.str`should have a refund deadline` : !isAfter( value.payments.refund_deadline, value.payments.auto_refund_deadline, ) - ? i18n`auto refund cannot be after refund deadline` + ? i18n.str`auto refund cannot be after refund deadline` : undefined, }), shipping: undefinedIfEmpty({ delivery_date: !value.shipping?.delivery_date ? undefined : !isFuture(value.shipping.delivery_date) - ? i18n`should be in the future` + ? i18n.str`should be in the future` : undefined, }), }; @@ -349,7 +349,7 @@ export function CreatePage({ {/* // FIXME: translating plural singular */} <InputGroup name="inventory_products" - label={i18n`Manage products in order`} + label={i18n.str`Manage products in order`} alternative={ allProducts.length > 0 && ( <p> @@ -358,7 +358,7 @@ export function CreatePage({ </p> ) } - tooltip={i18n`Manage list of products in the order.`} + tooltip={i18n.str`Manage list of products in the order.`} > <InventoryProductForm currentProducts={value.inventoryProducts || {}} @@ -379,8 +379,8 @@ export function CreatePage({ list={allProducts} actions={[ { - name: i18n`Remove`, - tooltip: i18n`Remove this product from the order.`, + name: i18n.str`Remove`, + tooltip: i18n.str`Remove this product from the order.`, handler: (e, index) => { if (e.product_id) { removeProductFromTheInventoryList(e.product_id); @@ -404,13 +404,13 @@ export function CreatePage({ <Fragment> <InputCurrency name="pricing.products_price" - label={i18n`Total price`} + label={i18n.str`Total price`} readonly - tooltip={i18n`total product price added up`} + tooltip={i18n.str`total product price added up`} /> <InputCurrency name="pricing.order_price" - label={i18n`Total price`} + label={i18n.str`Total price`} addonAfter={ discountOrRise > 0 && (discountOrRise < 1 @@ -419,103 +419,103 @@ export function CreatePage({ )}` : `rise of %${Math.round((discountOrRise - 1) * 100)}`) } - tooltip={i18n`Amount to be paid by the customer`} + tooltip={i18n.str`Amount to be paid by the customer`} /> </Fragment> ) : ( <InputCurrency name="pricing.order_price" - label={i18n`Order price`} - tooltip={i18n`final order price`} + label={i18n.str`Order price`} + tooltip={i18n.str`final order price`} /> )} <Input name="pricing.summary" inputType="multiline" - label={i18n`Summary`} - tooltip={i18n`Title of the order to be shown to the customer`} + label={i18n.str`Summary`} + tooltip={i18n.str`Title of the order to be shown to the customer`} /> <InputGroup name="shipping" - label={i18n`Shipping and Fulfillment`} + label={i18n.str`Shipping and Fulfillment`} initialActive > <InputDate name="shipping.delivery_date" - label={i18n`Delivery date`} - tooltip={i18n`Deadline for physical delivery assured by the merchant.`} + label={i18n.str`Delivery date`} + tooltip={i18n.str`Deadline for physical delivery assured by the merchant.`} /> {value.shipping?.delivery_date && ( <InputGroup name="shipping.delivery_location" - label={i18n`Location`} - tooltip={i18n`address where the products will be delivered`} + label={i18n.str`Location`} + tooltip={i18n.str`address where the products will be delivered`} > <InputLocation name="shipping.delivery_location" /> </InputGroup> )} <Input name="shipping.fullfilment_url" - label={i18n`Fulfillment URL`} - tooltip={i18n`URL to which the user will be redirected after successful payment.`} + label={i18n.str`Fulfillment URL`} + tooltip={i18n.str`URL to which the user will be redirected after successful payment.`} /> </InputGroup> <InputGroup name="payments" - label={i18n`Taler payment options`} - tooltip={i18n`Override default Taler payment settings for this order`} + label={i18n.str`Taler payment options`} + tooltip={i18n.str`Override default Taler payment settings for this order`} > <InputDate name="payments.pay_deadline" - label={i18n`Payment deadline`} - tooltip={i18n`Deadline for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline.`} + label={i18n.str`Payment deadline`} + tooltip={i18n.str`Deadline for the customer to pay for the offer before it expires. Inventory products will be reserved until this deadline.`} /> <InputDate name="payments.refund_deadline" - label={i18n`Refund deadline`} - tooltip={i18n`Time until which the order can be refunded by the merchant.`} + label={i18n.str`Refund deadline`} + tooltip={i18n.str`Time until which the order can be refunded by the merchant.`} /> <InputDate name="payments.wire_transfer_deadline" - label={i18n`Wire transfer deadline`} - tooltip={i18n`Deadline for the exchange to make the wire transfer.`} + label={i18n.str`Wire transfer deadline`} + tooltip={i18n.str`Deadline for the exchange to make the wire transfer.`} /> <InputDate name="payments.auto_refund_deadline" - label={i18n`Auto-refund deadline`} - tooltip={i18n`Time until which the wallet will automatically check for refunds without user interaction.`} + label={i18n.str`Auto-refund deadline`} + tooltip={i18n.str`Time until which the wallet will automatically check for refunds without user interaction.`} /> <InputCurrency name="payments.max_fee" - label={i18n`Maximum deposit fee`} - tooltip={i18n`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`} + label={i18n.str`Maximum deposit fee`} + tooltip={i18n.str`Maximum deposit fees the merchant is willing to cover for this order. Higher deposit fees must be covered in full by the consumer.`} /> <InputCurrency name="payments.max_wire_fee" - label={i18n`Maximum wire fee`} - tooltip={i18n`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`} + label={i18n.str`Maximum wire fee`} + tooltip={i18n.str`Maximum aggregate wire fees the merchant is willing to cover for this order. Wire fees exceeding this amount are to be covered by the customers.`} /> <InputNumber name="payments.wire_fee_amortization" - label={i18n`Wire fee amortization`} - tooltip={i18n`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`} + label={i18n.str`Wire fee amortization`} + tooltip={i18n.str`Factor by which wire fees exceeding the above threshold are divided to determine the share of excess wire fees to be paid explicitly by the consumer.`} /> <InputBoolean name="payments.createToken" - label={i18n`Create token`} - tooltip={i18n`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`} + label={i18n.str`Create token`} + tooltip={i18n.str`Uncheck this option if the merchant backend generated an order ID with enough entropy to prevent adversarial claims.`} /> <InputNumber name="payments.minimum_age" - label={i18n`Minimum age required`} - tooltip={i18n`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`} + label={i18n.str`Minimum age required`} + tooltip={i18n.str`Any value greater than 0 will limit the coins able be used to pay this contract. If empty the age restriction will be defined by the products`} help={ minAgeByProducts > 0 - ? i18n`Min age defined by the producs is ${minAgeByProducts}` + ? i18n.str`Min age defined by the producs is ${minAgeByProducts}` : undefined } /> @@ -523,14 +523,14 @@ export function CreatePage({ <InputGroup name="extra" - label={i18n`Additional information`} - tooltip={i18n`Custom information to be included in the contract for this order.`} + label={i18n.str`Additional information`} + tooltip={i18n.str`Custom information to be included in the contract for this order.`} > <Input name="extra" inputType="multiline" label={`Value`} - tooltip={i18n`You must enter a value in JavaScript Object Notation (JSON).`} + tooltip={i18n.str`You must enter a value in JavaScript Object Notation (JSON).`} /> </InputGroup> </FormProvider> @@ -538,7 +538,7 @@ export function CreatePage({ <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <button @@ -546,7 +546,7 @@ export function CreatePage({ onClick={submit} disabled={hasErrors} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </button> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx index 6d3ac311a..88c33e2f8 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/create/OrderCreatedSuccessfully.tsx @@ -13,11 +13,11 @@ 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useEffect, useState } from "preact/hooks"; import { CreatedSuccessfully } from "../../../../components/notifications/CreatedSuccessfully.js"; import { useOrderAPI } from "../../../../hooks/order.js"; -import { Translate } from "../../../../i18n/index.js"; import { Entity } from "./index.js"; interface Props { @@ -33,7 +33,7 @@ export function OrderCreatedSuccessfully({ }: Props): VNode { const { getPaymentURL } = useOrderAPI(); const [url, setURL] = useState<string | undefined>(undefined); - + const { i18n } = useTranslationContext(); useEffect(() => { getPaymentURL(entity.response.order_id).then((response) => { setURL(response.data); @@ -48,7 +48,7 @@ export function OrderCreatedSuccessfully({ <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label"> - <Translate>Amount</Translate> + <i18n.Translate>Amount</i18n.Translate> </label> </div> <div class="field-body is-flex-grow-3"> @@ -66,7 +66,7 @@ export function OrderCreatedSuccessfully({ <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label"> - <Translate>Summary</Translate> + <i18n.Translate>Summary</i18n.Translate> </label> </div> <div class="field-body is-flex-grow-3"> @@ -84,7 +84,7 @@ export function OrderCreatedSuccessfully({ <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label"> - <Translate>Order ID</Translate> + <i18n.Translate>Order ID</i18n.Translate> </label> </div> <div class="field-body is-flex-grow-3"> @@ -98,7 +98,7 @@ export function OrderCreatedSuccessfully({ <div class="field is-horizontal"> <div class="field-label is-normal"> <label class="label"> - <Translate>Payment URL</Translate> + <i18n.Translate>Payment URL</i18n.Translate> </label> </div> <div class="field-body is-flex-grow-3"> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx index e8927dd70..1fba3c3e9 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/DetailPage.tsx @@ -20,6 +20,7 @@ */ import { AmountJson, Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -34,7 +35,6 @@ import { TextField } from "../../../../components/form/TextField.js"; import { ProductList } from "../../../../components/product/ProductList.js"; import { useBackendContext } from "../../../../context/backend.js"; import { MerchantBackend } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { mergeRefunds } from "../../../../utils/amount.js"; import { RefundModal } from "../list/Table.js"; import { Event, Timeline } from "./Timeline.js"; @@ -56,84 +56,84 @@ type Unpaid = MerchantBackend.Orders.CheckPaymentUnpaidResponse; type Claimed = MerchantBackend.Orders.CheckPaymentClaimedResponse; function ContractTerms({ value }: { value: CT }) { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( - <InputGroup name="contract_terms" label={i18n`Contract Terms`}> + <InputGroup name="contract_terms" label={i18n.str`Contract Terms`}> <FormProvider<CT> object={value} valueHandler={null}> <Input<CT> readonly name="summary" - label={i18n`Summary`} - tooltip={i18n`human-readable description of the whole purchase`} + label={i18n.str`Summary`} + tooltip={i18n.str`human-readable description of the whole purchase`} /> <InputCurrency<CT> readonly name="amount" - label={i18n`Amount`} - tooltip={i18n`total price for the transaction`} + label={i18n.str`Amount`} + tooltip={i18n.str`total price for the transaction`} /> {value.fulfillment_url && ( <Input<CT> readonly name="fulfillment_url" - label={i18n`Fulfillment URL`} - tooltip={i18n`URL for this purchase`} + label={i18n.str`Fulfillment URL`} + tooltip={i18n.str`URL for this purchase`} /> )} <Input<CT> readonly name="max_fee" - label={i18n`Max fee`} - tooltip={i18n`maximum total deposit fee accepted by the merchant for this contract`} + label={i18n.str`Max fee`} + tooltip={i18n.str`maximum total deposit fee accepted by the merchant for this contract`} /> <Input<CT> readonly name="max_wire_fee" - label={i18n`Max wire fee`} - tooltip={i18n`maximum wire fee accepted by the merchant`} + label={i18n.str`Max wire fee`} + tooltip={i18n.str`maximum wire fee accepted by the merchant`} /> <Input<CT> readonly name="wire_fee_amortization" - label={i18n`Wire fee amortization`} - tooltip={i18n`over how many customer transactions does the merchant expect to amortize wire fees on average`} + label={i18n.str`Wire fee amortization`} + tooltip={i18n.str`over how many customer transactions does the merchant expect to amortize wire fees on average`} /> <InputDate<CT> readonly name="timestamp" - label={i18n`Created at`} - tooltip={i18n`time when this contract was generated`} + label={i18n.str`Created at`} + tooltip={i18n.str`time when this contract was generated`} /> <InputDate<CT> readonly name="refund_deadline" - label={i18n`Refund deadline`} - tooltip={i18n`after this deadline has passed no refunds will be accepted`} + label={i18n.str`Refund deadline`} + tooltip={i18n.str`after this deadline has passed no refunds will be accepted`} /> <InputDate<CT> readonly name="pay_deadline" - label={i18n`Payment deadline`} - tooltip={i18n`after this deadline, the merchant won't accept payments for the contract`} + label={i18n.str`Payment deadline`} + tooltip={i18n.str`after this deadline, the merchant won't accept payments for the contract`} /> <InputDate<CT> readonly name="wire_transfer_deadline" - label={i18n`Wire transfer deadline`} - tooltip={i18n`transfer deadline for the exchange`} + label={i18n.str`Wire transfer deadline`} + tooltip={i18n.str`transfer deadline for the exchange`} /> <InputDate<CT> readonly name="delivery_date" - label={i18n`Delivery date`} - tooltip={i18n`time indicating when the order should be delivered`} + label={i18n.str`Delivery date`} + tooltip={i18n.str`time indicating when the order should be delivered`} /> {value.delivery_date && ( <InputGroup name="delivery_location" - label={i18n`Location`} - tooltip={i18n`where the order will be delivered`} + label={i18n.str`Location`} + tooltip={i18n.str`where the order will be delivered`} > <InputLocation name="payments.delivery_location" /> </InputGroup> @@ -141,14 +141,14 @@ function ContractTerms({ value }: { value: CT }) { <InputDuration<CT> readonly name="auto_refund" - label={i18n`Auto-refund delay`} - tooltip={i18n`how long the wallet should try to get an automatic refund for the purchase`} + label={i18n.str`Auto-refund delay`} + tooltip={i18n.str`how long the wallet should try to get an automatic refund for the purchase`} /> <Input<CT> readonly name="extra" - label={i18n`Extra info`} - tooltip={i18n`extra data that is only interpreted by the merchant frontend`} + label={i18n.str`Extra info`} + tooltip={i18n.str`extra data that is only interpreted by the merchant frontend`} /> </FormProvider> </InputGroup> @@ -203,7 +203,7 @@ function ClaimedPage({ } const [value, valueHandler] = useState<Partial<Claimed>>(order); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div> @@ -216,9 +216,9 @@ function ClaimedPage({ <div class="level"> <div class="level-left"> <div class="level-item"> - <Translate>Order</Translate> #{id} + <i18n.Translate>Order</i18n.Translate> #{id} <div class="tag is-info ml-4"> - <Translate>claimed</Translate> + <i18n.Translate>claimed</i18n.Translate> </div> </div> </div> @@ -244,7 +244,7 @@ function ClaimedPage({ > <p> <b> - <Translate>claimed at</Translate>: + <i18n.Translate>claimed at</i18n.Translate>: </b>{" "} {format( new Date(order.contract_terms.timestamp.t_s * 1000), @@ -262,13 +262,13 @@ function ClaimedPage({ <div class="columns"> <div class="column is-4"> <div class="title"> - <Translate>Timeline</Translate> + <i18n.Translate>Timeline</i18n.Translate> </div> <Timeline events={events} /> </div> <div class="column is-8"> <div class="title"> - <Translate>Payment details</Translate> + <i18n.Translate>Payment details</i18n.Translate> </div> <FormProvider<Claimed> object={value} @@ -278,17 +278,17 @@ function ClaimedPage({ name="contract_terms.summary" readonly inputType="multiline" - label={i18n`Summary`} + label={i18n.str`Summary`} /> <InputCurrency name="contract_terms.amount" readonly - label={i18n`Amount`} + label={i18n.str`Amount`} /> <Input<Claimed> name="order_status" readonly - label={i18n`Order status`} + label={i18n.str`Order status`} /> </FormProvider> </div> @@ -298,7 +298,7 @@ function ClaimedPage({ {order.contract_terms.products.length ? ( <Fragment> <div class="title"> - <Translate>Product list</Translate> + <i18n.Translate>Product list</i18n.Translate> </div> <ProductList list={order.contract_terms.products} /> </Fragment> @@ -426,7 +426,7 @@ function PaidPage({ const refundurl = `${proto}://refund/${refundHost}/${order.contract_terms.order_id}/`; const refundable = new Date().getTime() < order.contract_terms.refund_deadline.t_s * 1000; - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const amount = Amounts.parseOrThrow(order.contract_terms.amount); const refund_taken = order.refund_details.reduce((prev, cur) => { @@ -446,18 +446,18 @@ function PaidPage({ <div class="level"> <div class="level-left"> <div class="level-item"> - <Translate>Order</Translate> #{id} + <i18n.Translate>Order</i18n.Translate> #{id} <div class="tag is-success ml-4"> - <Translate>paid</Translate> + <i18n.Translate>paid</i18n.Translate> </div> {order.wired ? ( <div class="tag is-success ml-4"> - <Translate>wired</Translate> + <i18n.Translate>wired</i18n.Translate> </div> ) : null} {order.refunded ? ( <div class="tag is-danger ml-4"> - <Translate>refunded</Translate> + <i18n.Translate>refunded</i18n.Translate> </div> ) : null} </div> @@ -477,8 +477,8 @@ function PaidPage({ class="has-tooltip-left" data-tooltip={ refundable - ? i18n`refund order` - : i18n`not refundable` + ? i18n.str`refund order` + : i18n.str`not refundable` } > <button @@ -486,7 +486,7 @@ function PaidPage({ disabled={!refundable} onClick={() => onRefund(id)} > - <Translate>refund</Translate> + <i18n.Translate>refund</i18n.Translate> </button> </span> </div> @@ -533,41 +533,41 @@ function PaidPage({ <div class="columns"> <div class="column is-4"> <div class="title"> - <Translate>Timeline</Translate> + <i18n.Translate>Timeline</i18n.Translate> </div> <Timeline events={events} /> </div> <div class="column is-8"> <div class="title"> - <Translate>Payment details</Translate> + <i18n.Translate>Payment details</i18n.Translate> </div> <FormProvider<Paid> object={value} valueHandler={valueHandler} > - {/* <InputCurrency<Paid> name="deposit_total" readonly label={i18n`Deposit total`} /> */} + {/* <InputCurrency<Paid> name="deposit_total" readonly label={i18n.str`Deposit total`} /> */} {order.refunded && ( <InputCurrency<Paid> name="refund_amount" readonly - label={i18n`Refunded amount`} + label={i18n.str`Refunded amount`} /> )} {order.refunded && ( <InputCurrency<Paid> name="refund_taken" readonly - label={i18n`Refund taken`} + label={i18n.str`Refund taken`} /> )} <Input<Paid> name="order_status" readonly - label={i18n`Order status`} + label={i18n.str`Order status`} /> <TextField<Paid> name="order_status_url" - label={i18n`Status URL`} + label={i18n.str`Status URL`} > <a target="_blank" @@ -580,7 +580,7 @@ function PaidPage({ {order.refunded && ( <TextField<Paid> name="order_status_url" - label={i18n`Refund URI`} + label={i18n.str`Refund URI`} > <a target="_blank" rel="noreferrer" href={refundurl}> {refundurl} @@ -595,7 +595,7 @@ function PaidPage({ {order.contract_terms.products.length ? ( <Fragment> <div class="title"> - <Translate>Product list</Translate> + <i18n.Translate>Product list</i18n.Translate> </div> <ProductList list={order.contract_terms.products} /> </Fragment> @@ -620,7 +620,7 @@ function UnpaidPage({ order: MerchantBackend.Orders.CheckPaymentUnpaidResponse; }) { const [value, valueHandler] = useState<Partial<Unpaid>>(order); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div> <section class="hero is-hero-bar"> @@ -629,11 +629,11 @@ function UnpaidPage({ <div class="level-left"> <div class="level-item"> <h1 class="title"> - <Translate>Order</Translate> #{id} + <i18n.Translate>Order</i18n.Translate> #{id} </h1> </div> <div class="tag is-dark"> - <Translate>unpaid</Translate> + <i18n.Translate>unpaid</i18n.Translate> </div> </div> </div> @@ -651,7 +651,7 @@ function UnpaidPage({ > <p> <b> - <Translate>pay at</Translate>: + <i18n.Translate>pay at</i18n.Translate>: </b>{" "} <a href={order.order_status_url} @@ -663,7 +663,7 @@ function UnpaidPage({ </p> <p> <b> - <Translate>created at</Translate>: + <i18n.Translate>created at</i18n.Translate>: </b>{" "} {order.creation_time.t_s === "never" ? "never" @@ -687,26 +687,29 @@ function UnpaidPage({ <Input<Unpaid> readonly name="summary" - label={i18n`Summary`} - tooltip={i18n`human-readable description of the whole purchase`} + label={i18n.str`Summary`} + tooltip={i18n.str`human-readable description of the whole purchase`} /> <InputCurrency<Unpaid> readonly name="total_amount" - label={i18n`Amount`} - tooltip={i18n`total price for the transaction`} + label={i18n.str`Amount`} + tooltip={i18n.str`total price for the transaction`} /> <Input<Unpaid> name="order_status" readonly - label={i18n`Order status`} + label={i18n.str`Order status`} /> <Input<Unpaid> name="order_status_url" readonly - label={i18n`Order status URL`} + label={i18n.str`Order status URL`} /> - <TextField<Unpaid> name="taler_pay_uri" label={i18n`Payment URI`}> + <TextField<Unpaid> + name="taler_pay_uri" + label={i18n.str`Payment URI`} + > <a target="_blank" rel="noreferrer" href={value.taler_pay_uri}> {value.taler_pay_uri} </a> @@ -722,7 +725,7 @@ function UnpaidPage({ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode { const [showRefund, setShowRefund] = useState<string | undefined>(undefined); - + const { i18n } = useTranslationContext(); const DetailByStatus = function () { switch (selected.order_status) { case "claimed": @@ -734,10 +737,10 @@ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode { default: return ( <div> - <Translate> + <i18n.Translate> Unknown order status. This is an error, please contact the administrator. - </Translate> + </i18n.Translate> </div> ); } @@ -761,7 +764,7 @@ export function DetailPage({ id, selected, onRefund, onBack }: Props): VNode { <div class="column is-four-fifths"> <div class="buttons is-right mt-5"> <button class="button" onClick={onBack}> - <Translate>Back</Translate> + <i18n.Translate>Back</i18n.Translate> </button> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx index 4633688ba..bb0240982 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/details/index.tsx @@ -13,13 +13,13 @@ 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; import { NotificationCard } from "../../../../components/menu/index.js"; import { HttpError } from "../../../../hooks/backend.js"; -import { useOrderDetails, useOrderAPI } from "../../../../hooks/order.js"; -import { useTranslator } from "../../../../i18n/index.js"; +import { useOrderAPI, useOrderDetails } from "../../../../hooks/order.js"; import { Notification } from "../../../../utils/types.js"; import { DetailPage } from "./DetailPage.js"; @@ -43,7 +43,7 @@ export default function Update({ const result = useOrderDetails(oid); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isNotfound) return onNotFound(); @@ -61,13 +61,13 @@ export default function Update({ refundOrder(id, value) .then(() => setNotif({ - message: i18n`refund created successfully`, + message: i18n.str`refund created successfully`, type: "SUCCESS", }), ) .catch((error) => setNotif({ - message: i18n`could not create the refund`, + message: i18n.str`could not create the refund`, type: "ERROR", description: error.message, }), diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx index bca90e352..3639ae912 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/ListPage.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { DatePicker } from "../../../../components/picker/DatePicker.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { CardTable } from "./Table.js"; export interface ListPageProps { @@ -74,8 +74,8 @@ export function ListPage({ isNotWiredActive, onCreate, }: ListPageProps): VNode { - const i18n = useTranslator(); - const dateTooltip = i18n`select date to show nearby orders`; + const { i18n } = useTranslationContext(); + const dateTooltip = i18n.str`select date to show nearby orders`; const [pickDate, setPickDate] = useState(false); const [orderId, setOrderId] = useState<string>(""); @@ -91,13 +91,13 @@ export function ListPage({ type="text" value={orderId} onChange={(e) => setOrderId(e.currentTarget.value)} - placeholder={i18n`order id`} + placeholder={i18n.str`order id`} /> {errorOrderId && <p class="help is-danger">{errorOrderId}</p>} </div> <span class="has-tooltip-bottom" - data-tooltip={i18n`jump to order with the given order ID`} + data-tooltip={i18n.str`jump to order with the given order ID`} > <button class="button" @@ -119,40 +119,40 @@ export function ListPage({ <li class={isAllActive}> <div class="has-tooltip-right" - data-tooltip={i18n`remove all filters`} + data-tooltip={i18n.str`remove all filters`} > <a onClick={onShowAll}> - <Translate>All</Translate> + <i18n.Translate>All</i18n.Translate> </a> </div> </li> <li class={isPaidActive}> <div class="has-tooltip-right" - data-tooltip={i18n`only show paid orders`} + data-tooltip={i18n.str`only show paid orders`} > <a onClick={onShowPaid}> - <Translate>Paid</Translate> + <i18n.Translate>Paid</i18n.Translate> </a> </div> </li> <li class={isRefundedActive}> <div class="has-tooltip-right" - data-tooltip={i18n`only show orders with refunds`} + data-tooltip={i18n.str`only show orders with refunds`} > <a onClick={onShowRefunded}> - <Translate>Refunded</Translate> + <i18n.Translate>Refunded</i18n.Translate> </a> </div> </li> <li class={isNotWiredActive}> <div class="has-tooltip-left" - data-tooltip={i18n`only show orders where customers paid, but wire payments from payment provider are still pending`} + data-tooltip={i18n.str`only show orders where customers paid, but wire payments from payment provider are still pending`} > <a onClick={onShowNotWired}> - <Translate>Not wired</Translate> + <i18n.Translate>Not wired</i18n.Translate> </a> </div> </li> @@ -165,7 +165,10 @@ export function ListPage({ {jumpToDate && ( <div class="control"> <a class="button" onClick={() => onSelectDate(undefined)}> - <span class="icon" data-tooltip={i18n`clear date filter`}> + <span + class="icon" + data-tooltip={i18n.str`clear date filter`} + > <i class="mdi mdi-close" /> </span> </a> @@ -178,7 +181,7 @@ export function ListPage({ type="text" readonly value={!jumpToDate ? "" : format(jumpToDate, "yyyy/MM/dd")} - placeholder={i18n`date (YYYY/MM/DD)`} + placeholder={i18n.str`date (YYYY/MM/DD)`} onClick={() => { setPickDate(true); }} diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx index a1ec8d291..b0a836f81 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/Table.tsx @@ -20,6 +20,7 @@ */ import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; @@ -34,7 +35,6 @@ import { InputSelector } from "../../../../components/form/InputSelector.js"; import { ConfirmModal } from "../../../../components/modal/index.js"; import { useConfigContext } from "../../../../context/config.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { mergeRefunds } from "../../../../utils/amount.js"; type Entity = MerchantBackend.Orders.OrderHistoryEntry & WithId; @@ -63,7 +63,7 @@ export function CardTable({ }: Props): VNode { const [rowSelection, rowSelectionHandler] = useState<string[]>([]); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="card has-table"> @@ -72,13 +72,13 @@ export function CardTable({ <span class="icon"> <i class="mdi mdi-cash-register" /> </span> - <Translate>Orders</Translate> + <i18n.Translate>Orders</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options" /> <div class="card-header-icon" aria-label="more options"> - <span class="has-tooltip-left" data-tooltip={i18n`create order`}> + <span class="has-tooltip-left" data-tooltip={i18n.str`create order`}> <button class="button is-info" type="button" onClick={onCreate}> <span class="icon is-small"> <i class="mdi mdi-plus mdi-36px" /> @@ -135,6 +135,7 @@ function Table({ hasMoreAfter, hasMoreBefore, }: TableProps): VNode { + const { i18n } = useTranslationContext(); return ( <div class="table-container"> {onLoadMoreBefore && ( @@ -143,20 +144,20 @@ function Table({ disabled={!hasMoreBefore} onClick={onLoadMoreBefore} > - <Translate>load newer orders</Translate> + <i18n.Translate>load newer orders</i18n.Translate> </button> )} <table class="table is-striped is-hoverable is-fullwidth"> <thead> <tr> <th style={{ minWidth: 100 }}> - <Translate>Date</Translate> + <i18n.Translate>Date</i18n.Translate> </th> <th style={{ minWidth: 100 }}> - <Translate>Amount</Translate> + <i18n.Translate>Amount</i18n.Translate> </th> <th style={{ minWidth: 400 }}> - <Translate>Summary</Translate> + <i18n.Translate>Summary</i18n.Translate> </th> <th style={{ minWidth: 50 }} /> </tr> @@ -196,7 +197,7 @@ function Table({ type="button" onClick={(): void => onRefund(i)} > - <Translate>Refund</Translate> + <i18n.Translate>Refund</i18n.Translate> </button> )} {!i.paid && ( @@ -205,7 +206,7 @@ function Table({ type="button" onClick={(): void => onCopyURL(i)} > - <Translate>copy url</Translate> + <i18n.Translate>copy url</i18n.Translate> </button> )} </div> @@ -221,7 +222,7 @@ function Table({ disabled={!hasMoreAfter} onClick={onLoadMoreAfter} > - <Translate>load older orders</Translate> + <i18n.Translate>load older orders</i18n.Translate> </button> )} </div> @@ -229,6 +230,7 @@ function Table({ } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -237,7 +239,9 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate>No orders have been found matching your query!</Translate> + <i18n.Translate> + No orders have been found matching your query! + </i18n.Translate> </p> </div> ); @@ -256,7 +260,7 @@ export function RefundModal({ }: RefundModalProps): VNode { type State = { mainReason?: string; description?: string; refund?: string }; const [form, setValue] = useState<State>({}); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); // const [errors, setErrors] = useState<FormErrors<State>>({}); const refunds = ( @@ -281,20 +285,20 @@ export function RefundModal({ : orderPrice; const isRefundable = Amounts.isNonZero(totalRefundable); - const duplicatedText = i18n`duplicated`; + const duplicatedText = i18n.str`duplicated`; const errors: FormErrors<State> = { - mainReason: !form.mainReason ? i18n`required` : undefined, + mainReason: !form.mainReason ? i18n.str`required` : undefined, description: !form.description && form.mainReason !== duplicatedText - ? i18n`required` + ? i18n.str`required` : undefined, refund: !form.refund - ? i18n`required` + ? i18n.str`required` : !Amounts.parse(form.refund) - ? i18n`invalid format` + ? i18n.str`invalid format` : Amounts.cmp(totalRefundable, Amounts.parse(form.refund)!) === -1 - ? i18n`this value exceed the refundable amount` + ? i18n.str`this value exceed the refundable amount` : undefined, }; const hasErrors = Object.keys(errors).some( @@ -339,13 +343,13 @@ export function RefundModal({ <thead> <tr> <th> - <Translate>date</Translate> + <i18n.Translate>date</i18n.Translate> </th> <th> - <Translate>amount</Translate> + <i18n.Translate>amount</i18n.Translate> </th> <th> - <Translate>reason</Translate> + <i18n.Translate>reason</i18n.Translate> </th> </tr> </thead> @@ -381,28 +385,28 @@ export function RefundModal({ > <InputCurrency<State> name="refund" - label={i18n`Refund`} - tooltip={i18n`amount to be refunded`} + label={i18n.str`Refund`} + tooltip={i18n.str`amount to be refunded`} > - <Translate>Max refundable:</Translate>{" "} + <i18n.Translate>Max refundable:</i18n.Translate>{" "} {Amounts.stringify(totalRefundable)} </InputCurrency> <InputSelector name="mainReason" - label={i18n`Reason`} + label={i18n.str`Reason`} values={[ - i18n`Choose one...`, + i18n.str`Choose one...`, duplicatedText, - i18n`requested by the customer`, - i18n`other`, + i18n.str`requested by the customer`, + i18n.str`other`, ]} - tooltip={i18n`why this order is being refunded`} + tooltip={i18n.str`why this order is being refunded`} /> {form.mainReason && form.mainReason !== duplicatedText ? ( <Input<State> - label={i18n`Description`} + label={i18n.str`Description`} name="description" - tooltip={i18n`more information to give context`} + tooltip={i18n.str`more information to give context`} /> ) : undefined} </FormProvider> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx index 315d78c63..e29c57a7c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/orders/list/index.tsx @@ -19,7 +19,8 @@ * @author Sebastian Javier Marchano (sebasjm) */ -import { h, VNode, Fragment } from "preact"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; +import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; import { NotificationCard } from "../../../../components/menu/index.js"; @@ -31,10 +32,9 @@ import { useOrderAPI, useOrderDetails, } from "../../../../hooks/order.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; -import { RefundModal } from "./Table.js"; import { ListPage } from "./ListPage.js"; +import { RefundModal } from "./Table.js"; interface Props { onUnauthorized: () => VNode; @@ -64,7 +64,7 @@ export default function OrderList({ const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const [errorOrderId, setErrorOrderId] = useState<string | undefined>( undefined, ); @@ -86,7 +86,7 @@ export default function OrderList({ async function testIfOrderExistAndSelect(orderId: string): Promise<void> { if (!orderId) { - setErrorOrderId(i18n`Enter an order id`); + setErrorOrderId(i18n.str`Enter an order id`); return; } try { @@ -94,7 +94,7 @@ export default function OrderList({ onSelect(orderId); setErrorOrderId(undefined); } catch { - setErrorOrderId(i18n`order not found`); + setErrorOrderId(i18n.str`order not found`); } } @@ -136,13 +136,13 @@ export default function OrderList({ refundOrder(orderToBeRefunded.order_id, value) .then(() => setNotif({ - message: i18n`refund created successfully`, + message: i18n.str`refund created successfully`, type: "SUCCESS", }), ) .catch((error) => setNotif({ - message: i18n`could not create the refund`, + message: i18n.str`could not create the refund`, type: "ERROR", description: error.message, }), @@ -151,7 +151,7 @@ export default function OrderList({ } onLoadError={(error) => { setNotif({ - message: i18n`could not create the refund`, + message: i18n.str`could not create the refund`, type: "ERROR", description: error.message, }); @@ -161,7 +161,7 @@ export default function OrderList({ onUnauthorized={onUnauthorized} onNotFound={() => { setNotif({ - message: i18n`could not get the order to refund`, + message: i18n.str`could not get the order to refund`, type: "ERROR", // description: error.message }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatePage.tsx index f6d7000ef..c18646be0 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/create/CreatePage.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { ProductForm } from "../../../../components/product/ProductForm.js"; import { MerchantBackend } from "../../../../declaration.js"; import { useListener } from "../../../../hooks/listener.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; type Entity = MerchantBackend.Products.ProductAddDetail & { product_id: string; @@ -43,7 +43,7 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { }, ); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div> @@ -56,19 +56,19 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <AsyncButton onClick={submitForm} data-tooltip={ !submitForm - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } disabled={!submitForm} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx index 62ecaf512..f82d85e94 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/create/index.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../../../components/menu/index.js"; import { MerchantBackend } from "../../../../declaration.js"; import { useProductAPI } from "../../../../hooks/product.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { CreatePage } from "./CreatePage.js"; @@ -36,7 +36,7 @@ interface Props { export default function CreateProduct({ onConfirm, onBack }: Props): VNode { const { createProduct } = useProductAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <Fragment> @@ -48,7 +48,7 @@ export default function CreateProduct({ onConfirm, onBack }: Props): VNode { .then(() => onConfirm()) .catch((error) => { setNotif({ - message: i18n`could not create product`, + message: i18n.str`could not create product`, type: "ERROR", description: error.message, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx index 515b36895..97d5afee5 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/Table.tsx @@ -19,19 +19,19 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { ComponentChildren, Fragment, h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; +import emptyImage from "../../../../assets/empty.png"; import { - FormProvider, FormErrors, + FormProvider, } from "../../../../components/form/FormProvider.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputNumber } from "../../../../components/form/InputNumber.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; -import emptyImage from "../../../../assets/empty.png"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; -import { Amounts } from "@gnu-taler/taler-util"; type Entity = MerchantBackend.Products.ProductDetail & WithId; @@ -57,7 +57,7 @@ export function CardTable({ const [rowSelection, rowSelectionHandler] = useState<string | undefined>( undefined, ); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="card has-table"> <header class="card-header"> @@ -65,12 +65,12 @@ export function CardTable({ <span class="icon"> <i class="mdi mdi-shopping" /> </span> - <Translate>Products</Translate> + <i18n.Translate>Products</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options"> <span class="has-tooltip-left" - data-tooltip={i18n`add product to inventory`} + data-tooltip={i18n.str`add product to inventory`} > <button class="button is-info" type="button" onClick={onCreate}> <span class="icon is-small"> @@ -121,32 +121,32 @@ function Table({ onUpdate, onDelete, }: TableProps): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>Image</Translate> + <i18n.Translate>Image</i18n.Translate> </th> <th> - <Translate>Description</Translate> + <i18n.Translate>Description</i18n.Translate> </th> <th> - <Translate>Sell</Translate> + <i18n.Translate>Sell</i18n.Translate> </th> <th> - <Translate>Taxes</Translate> + <i18n.Translate>Taxes</i18n.Translate> </th> <th> - <Translate>Profit</Translate> + <i18n.Translate>Profit</i18n.Translate> </th> <th> - <Translate>Stock</Translate> + <i18n.Translate>Stock</i18n.Translate> </th> <th> - <Translate>Sold</Translate> + <i18n.Translate>Sold</i18n.Translate> </th> <th /> </tr> @@ -207,7 +207,7 @@ function Table({ } style={{ cursor: "pointer" }} > - {isFree ? i18n`free` : `${i.price} / ${i.unit}`} + {isFree ? i18n.str`free` : `${i.price} / ${i.unit}`} </td> <td onClick={() => @@ -245,26 +245,26 @@ function Table({ <div class="buttons is-right"> <span class="has-tooltip-bottom" - data-tooltip={i18n`go to product update page`} + data-tooltip={i18n.str`go to product update page`} > <button class="button is-small is-success " type="button" onClick={(): void => onSelect(i)} > - <Translate>Update</Translate> + <i18n.Translate>Update</i18n.Translate> </button> </span> <span class="has-tooltip-left" - data-tooltip={i18n`remove this product from the database`} + data-tooltip={i18n.str`remove this product from the database`} > <button class="button is-small is-danger" type="button" onClick={(): void => onDelete(i)} > - <Translate>Delete</Translate> + <i18n.Translate>Delete</i18n.Translate> </button> </span> </div> @@ -316,7 +316,7 @@ function FastProductWithInfiniteStockUpdateForm({ onCancel, }: FastProductUpdateFormProps) { const [value, valueHandler] = useState<UpdatePrice>({ price: product.price }); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <Fragment> @@ -327,18 +327,18 @@ function FastProductWithInfiniteStockUpdateForm({ > <InputCurrency<FastProductUpdate> name="price" - label={i18n`Price`} - tooltip={i18n`update the product with new price`} + label={i18n.str`Price`} + tooltip={i18n.str`update the product with new price`} /> </FormProvider> <div class="buttons is-right mt-5"> <button class="button" onClick={onCancel}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <span class="has-tooltip-left" - data-tooltip={i18n`update product with new price`} + data-tooltip={i18n.str`update product with new price`} > <button class="button is-info" @@ -349,7 +349,7 @@ function FastProductWithInfiniteStockUpdateForm({ }) } > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </button> </span> </div> @@ -383,7 +383,7 @@ function FastProductWithManagedStockUpdateForm({ const hasErrors = Object.keys(errors).some( (k) => (errors as any)[k] !== undefined, ); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <Fragment> @@ -395,31 +395,31 @@ function FastProductWithManagedStockUpdateForm({ > <InputNumber<FastProductUpdate> name="incoming" - label={i18n`Incoming`} - tooltip={i18n`add more elements to the inventory`} + label={i18n.str`Incoming`} + tooltip={i18n.str`add more elements to the inventory`} /> <InputNumber<FastProductUpdate> name="lost" - label={i18n`Lost`} - tooltip={i18n`report elements lost in the inventory`} + label={i18n.str`Lost`} + tooltip={i18n.str`report elements lost in the inventory`} /> <InputCurrency<FastProductUpdate> name="price" - label={i18n`Price`} - tooltip={i18n`new price for the product`} + label={i18n.str`Price`} + tooltip={i18n.str`new price for the product`} /> </FormProvider> <div class="buttons is-right mt-5"> <button class="button" onClick={onCancel}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <span class="has-tooltip-left" data-tooltip={ hasErrors - ? i18n`the are value with errors` - : i18n`update product with new stock and price` + ? i18n.str`the are value with errors` + : i18n.str`update product with new stock and price` } > <button @@ -434,7 +434,7 @@ function FastProductWithManagedStockUpdateForm({ }) } > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </button> </span> </div> @@ -451,6 +451,7 @@ function FastProductUpdateForm(props: FastProductUpdateFormProps) { } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -459,9 +460,9 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate> + <i18n.Translate> There is no products yet, add more pressing the + sign - </Translate> + </i18n.Translate> </p> </div> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx index 7e9118d24..41a07a7aa 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/list/index.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; @@ -29,7 +30,6 @@ import { useInstanceProducts, useProductAPI, } from "../../../../hooks/product.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { CardTable } from "./Table.js"; @@ -51,7 +51,7 @@ export default function ProductList({ const { deleteProduct, updateProduct } = useProductAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isNotfound) return onNotFound(); @@ -69,13 +69,13 @@ export default function ProductList({ updateProduct(id, prod) .then(() => setNotif({ - message: i18n`product updated successfully`, + message: i18n.str`product updated successfully`, type: "SUCCESS", }), ) .catch((error) => setNotif({ - message: i18n`could not update the product`, + message: i18n.str`could not update the product`, type: "ERROR", description: error.message, }), @@ -86,13 +86,13 @@ export default function ProductList({ deleteProduct(prod.id) .then(() => setNotif({ - message: i18n`product delete successfully`, + message: i18n.str`product delete successfully`, type: "SUCCESS", }), ) .catch((error) => setNotif({ - message: i18n`could not delete the product`, + message: i18n.str`could not delete the product`, type: "ERROR", description: error.message, }), diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/update/UpdatePage.tsx index 841c0222f..d179ef87c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/update/UpdatePage.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { ProductForm } from "../../../../components/product/ProductForm.js"; -import { MerchantBackend, WithId } from "../../../../declaration.js"; +import { MerchantBackend } from "../../../../declaration.js"; import { useListener } from "../../../../hooks/listener.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; type Entity = MerchantBackend.Products.ProductDetail & { product_id: string }; @@ -42,7 +42,7 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode { }, ); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div> @@ -53,7 +53,7 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode { <div class="level-left"> <div class="level-item"> <span class="is-size-4"> - <Translate>Product id:</Translate> + <i18n.Translate>Product id:</i18n.Translate> <b>{product.product_id}</b> </span> </div> @@ -75,19 +75,19 @@ export function UpdatePage({ product, onUpdate, onBack }: Props): VNode { <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <AsyncButton onClick={submitForm} data-tooltip={ !submitForm - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } disabled={!submitForm} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx index 3988fc9f0..e141dc52c 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/products/update/index.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; @@ -26,7 +27,6 @@ import { NotificationCard } from "../../../../components/menu/index.js"; import { MerchantBackend } from "../../../../declaration.js"; import { HttpError } from "../../../../hooks/backend.js"; import { useProductAPI, useProductDetails } from "../../../../hooks/product.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { UpdatePage } from "./UpdatePage.js"; @@ -51,7 +51,7 @@ export default function UpdateProduct({ const result = useProductDetails(pid); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isNotfound) return onNotFound(); @@ -69,7 +69,7 @@ export default function UpdateProduct({ .then(onConfirm) .catch((error) => { setNotif({ - message: i18n`could not create product`, + message: i18n.str`could not create product`, type: "ERROR", description: error.message, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx index 2c3e963b8..de2319636 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatePage.tsx @@ -19,24 +19,23 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { StateUpdater, useEffect, useState } from "preact/hooks"; +import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; import { FormErrors, FormProvider, } from "../../../../components/form/FormProvider.js"; import { Input } from "../../../../components/form/Input.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; +import { InputSelector } from "../../../../components/form/InputSelector.js"; import { ExchangeBackend, MerchantBackend } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; -import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; -import { canonicalizeBaseUrl, ExchangeKeysJson } from "@gnu-taler/taler-util"; +import { request } from "../../../../hooks/backend.js"; import { PAYTO_WIRE_METHOD_LOOKUP, URL_REGEX, } from "../../../../utils/constants.js"; -import { request } from "../../../../hooks/backend.js"; -import { InputSelector } from "../../../../components/form/InputSelector.js"; type Entity = MerchantBackend.Tips.ReserveCreateRequest; @@ -66,7 +65,7 @@ function ViewStep({ submitForm, setReserve, }: ViewProps): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const [wireMethods, setWireMethods] = useState<Array<string>>([]); const [exchangeQueryError, setExchangeQueryError] = useState< string | undefined @@ -82,12 +81,12 @@ function ViewStep({ initial_balance: !reserve.initial_balance ? "cannot be empty" : !(parseInt(reserve.initial_balance.split(":")[1], 10) > 0) - ? i18n`it should be greater than 0` + ? i18n.str`it should be greater than 0` : undefined, exchange_url: !reserve.exchange_url - ? i18n`cannot be empty` + ? i18n.str`cannot be empty` : !URL_REGEX.test(reserve.exchange_url) - ? i18n`must be a valid URL` + ? i18n.str`must be a valid URL` : !exchangeQueryError ? undefined : exchangeQueryError, @@ -106,20 +105,20 @@ function ViewStep({ > <InputCurrency<Entity> name="initial_balance" - label={i18n`Initial balance`} - tooltip={i18n`balance prior to deposit`} + label={i18n.str`Initial balance`} + tooltip={i18n.str`balance prior to deposit`} /> <Input<Entity> name="exchange_url" - label={i18n`Exchange URL`} - tooltip={i18n`URL of exchange`} + label={i18n.str`Exchange URL`} + tooltip={i18n.str`URL of exchange`} /> </FormProvider> <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <AsyncButton @@ -143,12 +142,12 @@ function ViewStep({ }} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } disabled={hasErrors} > - <Translate>Next</Translate> + <i18n.Translate>Next</i18n.Translate> </AsyncButton> </div> </Fragment> @@ -157,7 +156,9 @@ function ViewStep({ case Steps.WIRE_METHOD: { const errors: FormErrors<Entity> = { - wire_method: !reserve.wire_method ? i18n`cannot be empty` : undefined, + wire_method: !reserve.wire_method + ? i18n.str`cannot be empty` + : undefined, }; const hasErrors = Object.keys(errors).some( @@ -172,22 +173,22 @@ function ViewStep({ > <InputCurrency<Entity> name="initial_balance" - label={i18n`Initial balance`} - tooltip={i18n`balance prior to deposit`} + label={i18n.str`Initial balance`} + tooltip={i18n.str`balance prior to deposit`} readonly /> <Input<Entity> name="exchange_url" - label={i18n`Exchange URL`} - tooltip={i18n`URL of exchange`} + label={i18n.str`Exchange URL`} + tooltip={i18n.str`URL of exchange`} readonly /> <InputSelector<Entity> name="wire_method" - label={i18n`Wire method`} - tooltip={i18n`method to use for wire transfer`} + label={i18n.str`Wire method`} + tooltip={i18n.str`method to use for wire transfer`} values={wireMethods} - placeholder={i18n`Select one wire method`} + placeholder={i18n.str`Select one wire method`} /> </FormProvider> <div class="buttons is-right mt-5"> @@ -196,19 +197,19 @@ function ViewStep({ class="button" onClick={() => setCurrentStep(Steps.EXCHANGE)} > - <Translate>Back</Translate> + <i18n.Translate>Back</i18n.Translate> </button> )} <AsyncButton onClick={submitForm} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } disabled={hasErrors} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </Fragment> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx index 9bb228e1f..92427f3dc 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/CreatedSuccessfully.tsx @@ -14,11 +14,11 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; +import { QR } from "../../../../components/exception/QR.js"; import { CreatedSuccessfully as Template } from "../../../../components/notifications/CreatedSuccessfully.js"; import { MerchantBackend } from "../../../../declaration.js"; -import { Translate } from "../../../../i18n/index.js"; -import { QR } from "../../../../components/exception/QR.js"; type Entity = { request: MerchantBackend.Tips.ReserveCreateRequest; @@ -37,7 +37,7 @@ export function CreatedSuccessfully({ onCreateAnother, }: Props): VNode { const link = `${entity.response.payto_uri}?message=${entity.response.reserve_pub}&amount=${entity.request.initial_balance}`; - + const { i18n } = useTranslationContext(); return ( <Template onConfirm={onConfirm} onCreateAnother={onCreateAnother}> <div class="field is-horizontal"> @@ -85,16 +85,16 @@ export function CreatedSuccessfully({ </div> </div> <p class="is-size-5"> - <Translate> + <i18n.Translate> To complete the setup of the reserve, you must now initiate a wire transfer using the given wire transfer subject and crediting the specified amount to the indicated account of the exchange. - </Translate> + </i18n.Translate> </p> <p class="is-size-5"> - <Translate> + <i18n.Translate> If your system supports RFC 8905, you can do this by opening this URI: - </Translate> + </i18n.Translate> </p> <pre> <a target="_blank" rel="noreferrer" href={link}> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/index.tsx index 8b3ffb4ac..0d39fc298 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/create/index.tsx @@ -19,12 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../../../components/menu/index.js"; import { MerchantBackend } from "../../../../declaration.js"; import { useReservesAPI } from "../../../../hooks/reserves.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { CreatedSuccessfully } from "./CreatedSuccessfully.js"; import { CreatePage } from "./CreatePage.js"; @@ -35,7 +35,7 @@ interface Props { export default function CreateReserve({ onBack, onConfirm }: Props): VNode { const { createReserve } = useReservesAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const [createdOk, setCreatedOk] = useState< | { @@ -59,7 +59,7 @@ export default function CreateReserve({ onBack, onConfirm }: Props): VNode { .then((r) => setCreatedOk({ request, response: r.data })) .catch((error) => { setNotif({ - message: i18n`could not create reserve`, + message: i18n.str`could not create reserve`, type: "ERROR", description: error.message, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/DetailPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/DetailPage.tsx index b0b291859..1d54198c8 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/DetailPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/details/DetailPage.tsx @@ -20,6 +20,7 @@ */ import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; @@ -29,13 +30,9 @@ import { Input } from "../../../../components/form/Input.js"; import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputDate } from "../../../../components/form/InputDate.js"; import { TextField } from "../../../../components/form/TextField.js"; -import { - ContinueModal, - SimpleModal, -} from "../../../../components/modal/index.js"; +import { SimpleModal } from "../../../../components/modal/index.js"; import { MerchantBackend } from "../../../../declaration.js"; import { useTipDetails } from "../../../../hooks/reserves.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { TipInfo } from "./TipInfo.js"; type Entity = MerchantBackend.Tips.ReserveDetail; @@ -48,7 +45,7 @@ interface Props { } export function DetailPage({ id, selected, onBack }: Props): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const didExchangeAckTransfer = Amounts.isNonZero( Amounts.parseOrThrow(selected.exchange_initial_amount), ); @@ -62,22 +59,22 @@ export function DetailPage({ id, selected, onBack }: Props): VNode { <FormProvider object={{ ...selected, id }} valueHandler={null}> <InputDate<Entity> name="creation_time" - label={i18n`Created at`} + label={i18n.str`Created at`} readonly /> <InputDate<Entity> name="expiration_time" - label={i18n`Valid until`} + label={i18n.str`Valid until`} readonly /> <InputCurrency<Entity> name="merchant_initial_amount" - label={i18n`Created balance`} + label={i18n.str`Created balance`} readonly /> <TextField<Entity> name="exchange_url" - label={i18n`Exchange URL`} + label={i18n.str`Exchange URL`} readonly > <a target="_blank" rel="noreferrer" href={selected.exchange_url}> @@ -89,27 +86,27 @@ export function DetailPage({ id, selected, onBack }: Props): VNode { <Fragment> <InputCurrency<Entity> name="exchange_initial_amount" - label={i18n`Exchange balance`} + label={i18n.str`Exchange balance`} readonly /> <InputCurrency<Entity> name="pickup_amount" - label={i18n`Picked up`} + label={i18n.str`Picked up`} readonly /> <InputCurrency<Entity> name="committed_amount" - label={i18n`Committed`} + label={i18n.str`Committed`} readonly /> </Fragment> )} <Input<Entity> name="payto_uri" - label={i18n`Account address`} + label={i18n.str`Account address`} readonly /> - <Input name="id" label={i18n`Subject`} readonly /> + <Input name="id" label={i18n.str`Subject`} readonly /> </FormProvider> {didExchangeAckTransfer ? ( @@ -120,7 +117,7 @@ export function DetailPage({ id, selected, onBack }: Props): VNode { <span class="icon"> <i class="mdi mdi-cash-register" /> </span> - <Translate>Tips</Translate> + <i18n.Translate>Tips</i18n.Translate> </p> </header> <div class="card-content"> @@ -139,18 +136,18 @@ export function DetailPage({ id, selected, onBack }: Props): VNode { ) : ( <Fragment> <p class="is-size-5"> - <Translate> + <i18n.Translate> To complete the setup of the reserve, you must now initiate a wire transfer using the given wire transfer subject and crediting the specified amount to the indicated account of the exchange. - </Translate> + </i18n.Translate> </p> <p class="is-size-5"> - <Translate> + <i18n.Translate> If your system supports RFC 8905, you can do this by opening this URI: - </Translate> + </i18n.Translate> </p> <pre> <a target="_blank" rel="noreferrer" href={link}> @@ -163,7 +160,7 @@ export function DetailPage({ id, selected, onBack }: Props): VNode { <div class="buttons is-right mt-5"> <button class="button" onClick={onBack}> - <Translate>Back</Translate> + <i18n.Translate>Back</i18n.Translate> </button> </div> </div> @@ -174,6 +171,7 @@ export function DetailPage({ id, selected, onBack }: Props): VNode { } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -182,7 +180,9 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate>No tips has been authorized from this reserve</Translate> + <i18n.Translate> + No tips has been authorized from this reserve + </i18n.Translate> </p> </div> ); @@ -193,22 +193,23 @@ interface TableProps { } function Table({ tips }: TableProps): VNode { + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>Authorized</Translate> + <i18n.Translate>Authorized</i18n.Translate> </th> <th> - <Translate>Picked up</Translate> + <i18n.Translate>Picked up</i18n.Translate> </th> <th> - <Translate>Reason</Translate> + <i18n.Translate>Reason</i18n.Translate> </th> <th> - <Translate>Expiration</Translate> + <i18n.Translate>Expiration</i18n.Translate> </th> </tr> </thead> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/AutorizeTipModal.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/AutorizeTipModal.tsx index 5200abedf..3a591c555 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/AutorizeTipModal.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/AutorizeTipModal.tsx @@ -19,8 +19,10 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; +import * as yup from "yup"; import { FormErrors, FormProvider, @@ -32,10 +34,8 @@ import { ContinueModal, } from "../../../../components/modal/index.js"; import { MerchantBackend } from "../../../../declaration.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { AuthorizeTipSchema } from "../../../../schemas/index.js"; import { CreatedSuccessfully } from "./CreatedSuccessfully.js"; -import * as yup from "yup"; interface AuthorizeTipModalProps { onCancel: () => void; @@ -54,7 +54,7 @@ export function AuthorizeTipModal({ // const result = useOrderDetails(id) type State = MerchantBackend.Tips.TipCreateRequest; const [form, setValue] = useState<Partial<State>>({}); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); // const [errors, setErrors] = useState<FormErrors<State>>({}) let errors: FormErrors<State> = {}; @@ -104,19 +104,19 @@ export function AuthorizeTipModal({ > <InputCurrency<State> name="amount" - label={i18n`Amount`} - tooltip={i18n`amount of tip`} + label={i18n.str`Amount`} + tooltip={i18n.str`amount of tip`} /> <Input<State> name="justification" - label={i18n`Justification`} + label={i18n.str`Justification`} inputType="multiline" - tooltip={i18n`reason for the tip`} + tooltip={i18n.str`reason for the tip`} /> <Input<State> name="next_url" - label={i18n`URL after tip`} - tooltip={i18n`URL to visit after tip payment`} + label={i18n.str`URL after tip`} + tooltip={i18n.str`URL to visit after tip payment`} /> </FormProvider> </ConfirmModal> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/Table.tsx index 86b79d1dd..36768855d 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/Table.tsx @@ -19,10 +19,10 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { Fragment, h, VNode } from "preact"; import { MerchantBackend, WithId } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; type Entity = MerchantBackend.Tips.ReserveStatusEntry & WithId; @@ -51,7 +51,7 @@ export function CardTable({ return prev; }, new Array<Array<Entity>>([], [])); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <Fragment> @@ -62,7 +62,7 @@ export function CardTable({ <span class="icon"> <i class="mdi mdi-cash" /> </span> - <Translate>Reserves not yet funded</Translate> + <i18n.Translate>Reserves not yet funded</i18n.Translate> </p> </header> <div class="card-content"> @@ -86,11 +86,14 @@ export function CardTable({ <span class="icon"> <i class="mdi mdi-cash" /> </span> - <Translate>Reserves ready</Translate> + <i18n.Translate>Reserves ready</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options" /> <div class="card-header-icon" aria-label="more options"> - <span class="has-tooltip-left" data-tooltip={i18n`add new reserve`}> + <span + class="has-tooltip-left" + data-tooltip={i18n.str`add new reserve`} + > <button class="button is-info" type="button" onClick={onCreate}> <span class="icon is-small"> <i class="mdi mdi-plus mdi-36px" /> @@ -127,26 +130,26 @@ interface TableProps { } function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>Created at</Translate> + <i18n.Translate>Created at</i18n.Translate> </th> <th> - <Translate>Expires at</Translate> + <i18n.Translate>Expires at</i18n.Translate> </th> <th> - <Translate>Initial</Translate> + <i18n.Translate>Initial</i18n.Translate> </th> <th> - <Translate>Picked up</Translate> + <i18n.Translate>Picked up</i18n.Translate> </th> <th> - <Translate>Committed</Translate> + <i18n.Translate>Committed</i18n.Translate> </th> <th /> </tr> @@ -196,7 +199,7 @@ function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode { <div class="buttons is-right"> <button class="button is-small is-danger has-tooltip-left" - data-tooltip={i18n`delete selected reserve from the database`} + data-tooltip={i18n.str`delete selected reserve from the database`} type="button" onClick={(): void => onDelete(i)} > @@ -204,7 +207,7 @@ function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode { </button> <button class="button is-small is-info has-tooltip-left" - data-tooltip={i18n`authorize new tip from selected reserve`} + data-tooltip={i18n.str`authorize new tip from selected reserve`} type="button" onClick={(): void => onNewTip(i)} > @@ -222,6 +225,7 @@ function Table({ instances, onNewTip, onSelect, onDelete }: TableProps): VNode { } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -230,10 +234,10 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate> + <i18n.Translate> There is no ready reserves yet, add more pressing the + sign or fund them - </Translate> + </i18n.Translate> </p> </div> ); @@ -244,20 +248,20 @@ function TableWithoutFund({ onSelect, onDelete, }: TableProps): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="table-container"> <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>Created at</Translate> + <i18n.Translate>Created at</i18n.Translate> </th> <th> - <Translate>Expires at</Translate> + <i18n.Translate>Expires at</i18n.Translate> </th> <th> - <Translate>Expected Balance</Translate> + <i18n.Translate>Expected Balance</i18n.Translate> </th> <th /> </tr> @@ -296,7 +300,7 @@ function TableWithoutFund({ <button class="button is-small is-danger jb-modal has-tooltip-left" type="button" - data-tooltip={i18n`delete selected reserve from the database`} + data-tooltip={i18n.str`delete selected reserve from the database`} onClick={(): void => onDelete(i)} > Delete diff --git a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx index 182b3f72c..9c3255ee8 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/reserves/list/index.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; @@ -29,10 +30,9 @@ import { useInstanceReserves, useReservesAPI, } from "../../../../hooks/reserves.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; -import { CardTable } from "./Table.js"; import { AuthorizeTipModal } from "./AutorizeTipModal.js"; +import { CardTable } from "./Table.js"; interface Props { onUnauthorized: () => VNode; @@ -57,7 +57,7 @@ export default function ListTips({ const result = useInstanceReserves(); const { deleteReserve, authorizeTipReserve } = useReservesAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const [reserveForTip, setReserveForTip] = useState<string | undefined>( undefined, ); @@ -93,7 +93,7 @@ export default function ListTips({ }); } catch (error) { setNotif({ - message: i18n`could not create the tip`, + message: i18n.str`could not create the tip`, type: "ERROR", description: error instanceof Error ? error.message : undefined, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx index dba4b5d14..2a47c22a0 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/CreatePage.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; @@ -33,7 +34,6 @@ import { InputNumber } from "../../../../components/form/InputNumber.js"; import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; import { useBackendContext } from "../../../../context/backend.js"; import { MerchantBackend } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; type Entity = MerchantBackend.Template.TemplateAddDetails; @@ -44,7 +44,7 @@ interface Props { } export function CreatePage({ onCreate, onBack }: Props): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const backend = useBackendContext(); const [state, setState] = useState<Partial<Entity>>({ @@ -57,23 +57,23 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { }); const errors: FormErrors<Entity> = { - template_id: !state.template_id ? i18n`should not be empty` : undefined, + template_id: !state.template_id ? i18n.str`should not be empty` : undefined, template_description: !state.template_description - ? i18n`should not be empty` + ? i18n.str`should not be empty` : undefined, template_contract: !state.template_contract ? undefined : undefinedIfEmpty({ minimum_age: state.template_contract.minimum_age < 0 - ? i18n`should be greater that 0` + ? i18n.str`should be greater that 0` : undefined, pay_duration: !state.template_contract.pay_duration - ? i18n`can't be empty` + ? i18n.str`can't be empty` : state.template_contract.pay_duration.d_us === "forever" ? undefined : state.template_contract.pay_duration.d_us < 1000 - ? i18n`to short` + ? i18n.str`to short` : undefined, }), }; @@ -101,57 +101,57 @@ export function CreatePage({ onCreate, onBack }: Props): VNode { <InputWithAddon<Entity> name="template_id" addonBefore={`${backend.url}/instances/templates/`} - label={i18n`Identifier`} - tooltip={i18n`Name of the template in URLs.`} + label={i18n.str`Identifier`} + tooltip={i18n.str`Name of the template in URLs.`} /> <Input<Entity> name="template_description" - label={i18n`Description`} + label={i18n.str`Description`} help="" - tooltip={i18n`Describe what this template stands for`} + tooltip={i18n.str`Describe what this template stands for`} /> <Input name="template_contract.summary" inputType="multiline" - label={i18n`Order summary`} - tooltip={i18n`Title of the order to be shown to the customer`} + label={i18n.str`Order summary`} + tooltip={i18n.str`Title of the order to be shown to the customer`} /> <InputCurrency name="template_contract.amount" - label={i18n`Order price`} - tooltip={i18n`Order price`} + label={i18n.str`Order price`} + tooltip={i18n.str`Order price`} /> <InputNumber name="template_contract.minimum_age" - label={i18n`Minimum age`} + label={i18n.str`Minimum age`} help="" - tooltip={i18n`Is this contract restricted to some age?`} + tooltip={i18n.str`Is this contract restricted to some age?`} /> <InputDuration name="template_contract.pay_duration" - label={i18n`Payment timeout`} + label={i18n.str`Payment timeout`} help="" - tooltip={i18n`How much time has the customer to complete the payment once the order was created.`} + tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`} /> </FormProvider> <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <AsyncButton disabled={hasErrors} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } onClick={submitForm} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx index bfedb7369..dcbf70106 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/create/index.tsx @@ -19,14 +19,12 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../../../components/menu/index.js"; import { MerchantBackend } from "../../../../declaration.js"; -import { useInstanceDetails } from "../../../../hooks/instance.js"; import { useTemplateAPI } from "../../../../hooks/templates.js"; -import { useTransferAPI } from "../../../../hooks/transfer.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { CreatePage } from "./CreatePage.js"; @@ -39,7 +37,7 @@ interface Props { export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { const { createTemplate } = useTemplateAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <> @@ -51,7 +49,7 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { .then(() => onConfirm()) .catch((error) => { setNotif({ - message: i18n`could not inform template`, + message: i18n.str`could not inform template`, type: "ERROR", description: error.message, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx index dd983918f..9d289e957 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/ListPage.tsx @@ -21,7 +21,7 @@ import { h, VNode } from "preact"; import { MerchantBackend } from "../../../../declaration.js"; -import { useTranslator } from "../../../../i18n/index.js"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { CardTable } from "./Table.js"; export interface Props { @@ -43,7 +43,7 @@ export function ListPage({ }: Props): VNode { const form = { payto_uri: "" }; - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <section class="section is-main-section"> <CardTable diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx index bc8477039..fd6ea5f6f 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/Table.tsx @@ -19,10 +19,10 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; import { MerchantBackend } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; type Entity = MerchantBackend.Template.TemplateEntry; @@ -49,7 +49,7 @@ export function CardTable({ }: Props): VNode { const [rowSelection, rowSelectionHandler] = useState<string[]>([]); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="card has-table"> @@ -58,10 +58,13 @@ export function CardTable({ <span class="icon"> <i class="mdi mdi-newspaper" /> </span> - <Translate>Templates</Translate> + <i18n.Translate>Templates</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options"> - <span class="has-tooltip-left" data-tooltip={i18n`add new templates`}> + <span + class="has-tooltip-left" + data-tooltip={i18n.str`add new templates`} + > <button class="button is-info" type="button" onClick={onCreate}> <span class="icon is-small"> <i class="mdi mdi-plus mdi-36px" /> @@ -120,27 +123,27 @@ function Table({ hasMoreAfter, hasMoreBefore, }: TableProps): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="table-container"> {onLoadMoreBefore && ( <button class="button is-fullwidth" - data-tooltip={i18n`load more templates before the first one`} + data-tooltip={i18n.str`load more templates before the first one`} disabled={!hasMoreBefore} onClick={onLoadMoreBefore} > - <Translate>load newer templates</Translate> + <i18n.Translate>load newer templates</i18n.Translate> </button> )} <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>ID</Translate> + <i18n.Translate>ID</i18n.Translate> </th> <th> - <Translate>Description</Translate> + <i18n.Translate>Description</i18n.Translate> </th> <th /> </tr> @@ -164,7 +167,7 @@ function Table({ <td> <button class="button is-danger is-small has-tooltip-left" - data-tooltip={i18n`delete selected templates from the database`} + data-tooltip={i18n.str`delete selected templates from the database`} onClick={() => onDelete(i)} > Delete @@ -178,11 +181,11 @@ function Table({ {onLoadMoreAfter && ( <button class="button is-fullwidth" - data-tooltip={i18n`load more templates after the last one`} + data-tooltip={i18n.str`load more templates after the last one`} disabled={!hasMoreAfter} onClick={onLoadMoreAfter} > - <Translate>load older templates</Translate> + <i18n.Translate>load older templates</i18n.Translate> </button> )} </div> @@ -190,6 +193,7 @@ function Table({ } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -198,9 +202,9 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate> + <i18n.Translate> There is no templates yet, add more pressing the + sign - </Translate> + </i18n.Translate> </p> </div> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx index 0d58093d3..dcac23983 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/list/index.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; @@ -29,7 +30,6 @@ import { useInstanceTemplates, useTemplateAPI, } from "../../../../hooks/templates.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { ListPage } from "./ListPage.js"; @@ -49,7 +49,7 @@ export default function ListTemplates({ onNotFound, }: Props): VNode { const [position, setPosition] = useState<string | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); const { deleteTemplate } = useTemplateAPI(); const result = useInstanceTemplates({ position }, (id) => setPosition(id)); @@ -77,13 +77,13 @@ export default function ListTemplates({ deleteTemplate(e.template_id) .then(() => setNotif({ - message: i18n`template delete successfully`, + message: i18n.str`template delete successfully`, type: "SUCCESS", }), ) .catch((error) => setNotif({ - message: i18n`could not delete the template`, + message: i18n.str`could not delete the template`, type: "ERROR", description: error.message, }), diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx index 42d9e5825..a49e8000b 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/UpdatePage.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; @@ -31,11 +32,8 @@ import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputDuration } from "../../../../components/form/InputDuration.js"; import { InputNumber } from "../../../../components/form/InputNumber.js"; import { InputWithAddon } from "../../../../components/form/InputWithAddon.js"; -import { ProductForm } from "../../../../components/product/ProductForm.js"; import { useBackendContext } from "../../../../context/backend.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; -import { useListener } from "../../../../hooks/listener.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { undefinedIfEmpty } from "../../../../utils/table.js"; type Entity = MerchantBackend.Template.TemplatePatchDetails & WithId; @@ -47,28 +45,28 @@ interface Props { } export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const backend = useBackendContext(); const [state, setState] = useState<Partial<Entity>>(template); const errors: FormErrors<Entity> = { template_description: !state.template_description - ? i18n`should not be empty` + ? i18n.str`should not be empty` : undefined, template_contract: !state.template_contract ? undefined : undefinedIfEmpty({ minimum_age: state.template_contract.minimum_age < 0 - ? i18n`should be greater that 0` + ? i18n.str`should be greater that 0` : undefined, pay_duration: !state.template_contract.pay_duration - ? i18n`can't be empty` + ? i18n.str`can't be empty` : state.template_contract.pay_duration.d_us === "forever" ? undefined : state.template_contract.pay_duration.d_us < 1000 - ? i18n`to short` + ? i18n.str`to short` : undefined, }), }; @@ -112,57 +110,57 @@ export function UpdatePage({ template, onUpdate, onBack }: Props): VNode { name="id" addonBefore={`templates/`} readonly - label={i18n`Identifier`} - tooltip={i18n`Name of the template in URLs.`} + label={i18n.str`Identifier`} + tooltip={i18n.str`Name of the template in URLs.`} /> <Input<Entity> name="template_description" - label={i18n`Description`} + label={i18n.str`Description`} help="" - tooltip={i18n`Describe what this template stands for`} + tooltip={i18n.str`Describe what this template stands for`} /> <Input name="template_contract.summary" inputType="multiline" - label={i18n`Order summary`} - tooltip={i18n`Title of the order to be shown to the customer`} + label={i18n.str`Order summary`} + tooltip={i18n.str`Title of the order to be shown to the customer`} /> <InputCurrency name="template_contract.amount" - label={i18n`Order price`} - tooltip={i18n`total product price added up`} + label={i18n.str`Order price`} + tooltip={i18n.str`total product price added up`} /> <InputNumber name="template_contract.minimum_age" - label={i18n`Minimum age`} + label={i18n.str`Minimum age`} help="" - tooltip={i18n`Is this contract restricted to some age?`} + tooltip={i18n.str`Is this contract restricted to some age?`} /> <InputDuration name="template_contract.pay_duration" - label={i18n`Payment timeout`} + label={i18n.str`Payment timeout`} help="" - tooltip={i18n`How much time has the customer to complete the payment once the order was created.`} + tooltip={i18n.str`How much time has the customer to complete the payment once the order was created.`} /> </FormProvider> <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <AsyncButton disabled={hasErrors} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } onClick={submitForm} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx index 25dc9abdc..4a4cc4274 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/templates/update/index.tsx @@ -19,18 +19,17 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../../components/exception/loading.js"; import { NotificationCard } from "../../../../components/menu/index.js"; import { MerchantBackend, WithId } from "../../../../declaration.js"; import { HttpError } from "../../../../hooks/backend.js"; -import { useProductAPI, useProductDetails } from "../../../../hooks/product.js"; import { useTemplateAPI, useTemplateDetails, } from "../../../../hooks/templates.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { UpdatePage } from "./UpdatePage.js"; @@ -56,7 +55,7 @@ export default function UpdateTemplate({ const result = useTemplateDetails(tid); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isNotfound) return onNotFound(); @@ -74,7 +73,7 @@ export default function UpdateTemplate({ .then(onConfirm) .catch((error) => { setNotif({ - message: i18n`could not update template`, + message: i18n.str`could not update template`, type: "ERROR", description: error.message, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx index 5b041df7c..5303d14d9 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/CreatePage.tsx @@ -19,6 +19,7 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; import { AsyncButton } from "../../../../components/exception/AsyncButton.js"; @@ -31,7 +32,6 @@ import { InputCurrency } from "../../../../components/form/InputCurrency.js"; import { InputSelector } from "../../../../components/form/InputSelector.js"; import { useConfigContext } from "../../../../context/config.js"; import { MerchantBackend } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { CROCKFORD_BASE32_REGEX, URL_REGEX, @@ -46,7 +46,7 @@ interface Props { } export function CreatePage({ accounts, onCreate, onBack }: Props): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const { currency } = useConfigContext(); const [state, setState] = useState<Partial<Entity>>({ @@ -58,18 +58,18 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode { const errors: FormErrors<Entity> = { wtid: !state.wtid - ? i18n`cannot be empty` + ? i18n.str`cannot be empty` : !CROCKFORD_BASE32_REGEX.test(state.wtid) - ? i18n`check the id, does not look valid` + ? i18n.str`check the id, does not look valid` : state.wtid.length !== 52 - ? i18n`should have 52 characters, current ${state.wtid.length}` + ? i18n.str`should have 52 characters, current ${state.wtid.length}` : undefined, - payto_uri: !state.payto_uri ? i18n`cannot be empty` : undefined, - credit_amount: !state.credit_amount ? i18n`cannot be empty` : undefined, + payto_uri: !state.payto_uri ? i18n.str`cannot be empty` : undefined, + credit_amount: !state.credit_amount ? i18n.str`cannot be empty` : undefined, exchange_url: !state.exchange_url - ? i18n`cannot be empty` + ? i18n.str`cannot be empty` : !URL_REGEX.test(state.exchange_url) - ? i18n`URL doesn't have the right format` + ? i18n.str`URL doesn't have the right format` : undefined, }; @@ -95,46 +95,46 @@ export function CreatePage({ accounts, onCreate, onBack }: Props): VNode { > <InputSelector name="payto_uri" - label={i18n`Credited bank account`} + label={i18n.str`Credited bank account`} values={accounts} - placeholder={i18n`Select one account`} - tooltip={i18n`Bank account of the merchant where the payment was received`} + placeholder={i18n.str`Select one account`} + tooltip={i18n.str`Bank account of the merchant where the payment was received`} /> <Input<Entity> name="wtid" - label={i18n`Wire transfer ID`} + label={i18n.str`Wire transfer ID`} help="" - tooltip={i18n`unique identifier of the wire transfer used by the exchange, must be 52 characters long`} + tooltip={i18n.str`unique identifier of the wire transfer used by the exchange, must be 52 characters long`} /> <Input<Entity> name="exchange_url" - label={i18n`Exchange URL`} - tooltip={i18n`Base URL of the exchange that made the transfer, should have been in the wire transfer subject`} + label={i18n.str`Exchange URL`} + tooltip={i18n.str`Base URL of the exchange that made the transfer, should have been in the wire transfer subject`} help="http://exchange.taler:8081/" /> <InputCurrency<Entity> name="credit_amount" - label={i18n`Amount credited`} - tooltip={i18n`Actual amount that was wired to the merchant's bank account`} + label={i18n.str`Amount credited`} + tooltip={i18n.str`Actual amount that was wired to the merchant's bank account`} /> </FormProvider> <div class="buttons is-right mt-5"> {onBack && ( <button class="button" onClick={onBack}> - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> )} <AsyncButton disabled={hasErrors} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } onClick={submitForm} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx index db01a57b6..8f41593dd 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/create/index.tsx @@ -19,13 +19,13 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { NotificationCard } from "../../../../components/menu/index.js"; import { MerchantBackend } from "../../../../declaration.js"; import { useInstanceDetails } from "../../../../hooks/instance.js"; import { useTransferAPI } from "../../../../hooks/transfer.js"; -import { useTranslator } from "../../../../i18n/index.js"; import { Notification } from "../../../../utils/types.js"; import { CreatePage } from "./CreatePage.js"; @@ -38,7 +38,7 @@ interface Props { export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { const { informTransfer } = useTransferAPI(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const instance = useInstanceDetails(); const accounts = !instance.ok ? [] @@ -55,7 +55,7 @@ export default function CreateTransfer({ onConfirm, onBack }: Props): VNode { .then(() => onConfirm()) .catch((error) => { setNotif({ - message: i18n`could not inform transfer`, + message: i18n.str`could not inform transfer`, type: "ERROR", description: error.message, }); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx index cad989980..c77f7737b 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/ListPage.tsx @@ -19,11 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { FormProvider } from "../../../../components/form/FormProvider.js"; import { InputSelector } from "../../../../components/form/InputSelector.js"; import { MerchantBackend } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; import { CardTable } from "./Table.js"; export interface Props { @@ -61,7 +61,7 @@ export function ListPage({ }: Props): VNode { const form = { payto_uri: payTo }; - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <section class="section is-main-section"> <div class="columns"> @@ -73,10 +73,10 @@ export function ListPage({ > <InputSelector name="payto_uri" - label={i18n`Address`} + label={i18n.str`Address`} values={accounts} - placeholder={i18n`Select one account`} - tooltip={i18n`filter by account address`} + placeholder={i18n.str`Select one account`} + tooltip={i18n.str`filter by account address`} /> </FormProvider> </div> @@ -87,30 +87,30 @@ export function ListPage({ <li class={isAllTransfers ? "is-active" : ""}> <div class="has-tooltip-right" - data-tooltip={i18n`remove all filters`} + data-tooltip={i18n.str`remove all filters`} > <a onClick={onShowAll}> - <Translate>All</Translate> + <i18n.Translate>All</i18n.Translate> </a> </div> </li> <li class={isVerifiedTransfers ? "is-active" : ""}> <div class="has-tooltip-right" - data-tooltip={i18n`only show wire transfers confirmed by the merchant`} + data-tooltip={i18n.str`only show wire transfers confirmed by the merchant`} > <a onClick={onShowVerified}> - <Translate>Verified</Translate> + <i18n.Translate>Verified</i18n.Translate> </a> </div> </li> <li class={isNonVerifiedTransfers ? "is-active" : ""}> <div class="has-tooltip-right" - data-tooltip={i18n`only show wire transfers claimed by the exchange`} + data-tooltip={i18n.str`only show wire transfers claimed by the exchange`} > <a onClick={onShowUnverified}> - <Translate>Unverified</Translate> + <i18n.Translate>Unverified</i18n.Translate> </a> </div> </li> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx index 2341fb80a..e38d375f9 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/transfers/list/Table.tsx @@ -19,11 +19,11 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { format } from "date-fns"; import { h, VNode } from "preact"; import { StateUpdater, useState } from "preact/hooks"; import { MerchantBackend, WithId } from "../../../../declaration.js"; -import { Translate, useTranslator } from "../../../../i18n/index.js"; type Entity = MerchantBackend.Transfers.TransferDetails & WithId; @@ -49,7 +49,7 @@ export function CardTable({ }: Props): VNode { const [rowSelection, rowSelectionHandler] = useState<string[]>([]); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="card has-table"> @@ -58,10 +58,13 @@ export function CardTable({ <span class="icon"> <i class="mdi mdi-bank" /> </span> - <Translate>Transfers</Translate> + <i18n.Translate>Transfers</i18n.Translate> </p> <div class="card-header-icon" aria-label="more options"> - <span class="has-tooltip-left" data-tooltip={i18n`add new transfer`}> + <span + class="has-tooltip-left" + data-tooltip={i18n.str`add new transfer`} + > <button class="button is-info" type="button" onClick={onCreate}> <span class="icon is-small"> <i class="mdi mdi-plus mdi-36px" /> @@ -117,42 +120,42 @@ function Table({ hasMoreAfter, hasMoreBefore, }: TableProps): VNode { - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); return ( <div class="table-container"> {onLoadMoreBefore && ( <button class="button is-fullwidth" - data-tooltip={i18n`load more transfers before the first one`} + data-tooltip={i18n.str`load more transfers before the first one`} disabled={!hasMoreBefore} onClick={onLoadMoreBefore} > - <Translate>load newer transfers</Translate> + <i18n.Translate>load newer transfers</i18n.Translate> </button> )} <table class="table is-fullwidth is-striped is-hoverable is-fullwidth"> <thead> <tr> <th> - <Translate>ID</Translate> + <i18n.Translate>ID</i18n.Translate> </th> <th> - <Translate>Credit</Translate> + <i18n.Translate>Credit</i18n.Translate> </th> <th> - <Translate>Address</Translate> + <i18n.Translate>Address</i18n.Translate> </th> <th> - <Translate>Exchange URL</Translate> + <i18n.Translate>Exchange URL</i18n.Translate> </th> <th> - <Translate>Confirmed</Translate> + <i18n.Translate>Confirmed</i18n.Translate> </th> <th> - <Translate>Verified</Translate> + <i18n.Translate>Verified</i18n.Translate> </th> <th> - <Translate>Executed at</Translate> + <i18n.Translate>Executed at</i18n.Translate> </th> <th /> </tr> @@ -165,23 +168,23 @@ function Table({ <td>{i.credit_amount}</td> <td>{i.payto_uri}</td> <td>{i.exchange_url}</td> - <td>{i.confirmed ? i18n`yes` : i18n`no`}</td> - <td>{i.verified ? i18n`yes` : i18n`no`}</td> + <td>{i.confirmed ? i18n.str`yes` : i18n.str`no`}</td> + <td>{i.verified ? i18n.str`yes` : i18n.str`no`}</td> <td> {i.execution_time ? i.execution_time.t_s == "never" - ? i18n`never` + ? i18n.str`never` : format( i.execution_time.t_s * 1000, "yyyy/MM/dd HH:mm:ss", ) - : i18n`unknown`} + : i18n.str`unknown`} </td> <td> {i.verified === undefined ? ( <button class="button is-danger is-small has-tooltip-left" - data-tooltip={i18n`delete selected transfer from the database`} + data-tooltip={i18n.str`delete selected transfer from the database`} onClick={() => onDelete(i)} > Delete @@ -196,11 +199,11 @@ function Table({ {onLoadMoreAfter && ( <button class="button is-fullwidth" - data-tooltip={i18n`load more transfer after the last one`} + data-tooltip={i18n.str`load more transfer after the last one`} disabled={!hasMoreAfter} onClick={onLoadMoreAfter} > - <Translate>load older transfers</Translate> + <i18n.Translate>load older transfers</i18n.Translate> </button> )} </div> @@ -208,6 +211,7 @@ function Table({ } function EmptyTable(): VNode { + const { i18n } = useTranslationContext(); return ( <div class="content has-text-grey has-text-centered"> <p> @@ -216,9 +220,9 @@ function EmptyTable(): VNode { </span> </p> <p> - <Translate> + <i18n.Translate> There is no transfer yet, add more pressing the + sign - </Translate> + </i18n.Translate> </p> </div> ); diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx index d7acdf023..1365a59da 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/update/UpdatePage.tsx @@ -19,21 +19,20 @@ * @author Sebastian Javier Marchano (sebasjm) */ +import { Amounts } from "@gnu-taler/taler-util"; +import { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { h, VNode } from "preact"; import { useState } from "preact/hooks"; -import * as yup from "yup"; import { AsyncButton } from "../../../components/exception/AsyncButton.js"; import { - FormProvider, FormErrors, + FormProvider, } from "../../../components/form/FormProvider.js"; +import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js"; import { UpdateTokenModal } from "../../../components/modal/index.js"; import { useInstanceContext } from "../../../context/instance.js"; import { MerchantBackend } from "../../../declaration.js"; -import { Translate, useTranslator } from "../../../i18n/index.js"; -import { DefaultInstanceFormFields } from "../../../components/instance/DefaultInstanceFormFields.js"; import { PAYTO_REGEX } from "../../../utils/constants.js"; -import { Amounts } from "@gnu-taler/taler-util"; import { undefinedIfEmpty } from "../../../utils/table.js"; type Entity = MerchantBackend.Instances.InstanceReconfigurationMessage & { @@ -95,50 +94,52 @@ export function UpdatePage({ const [value, valueHandler] = useState<Partial<Entity>>(convert(selected)); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); const errors: FormErrors<Entity> = { - name: !value.name ? i18n`required` : undefined, + name: !value.name ? i18n.str`required` : undefined, payto_uris: !value.payto_uris || !value.payto_uris.length - ? i18n`required` + ? i18n.str`required` : undefinedIfEmpty( value.payto_uris.map((p) => { - return !PAYTO_REGEX.test(p) ? i18n`is not valid` : undefined; + return !PAYTO_REGEX.test(p) ? i18n.str`is not valid` : undefined; }), ), default_max_deposit_fee: !value.default_max_deposit_fee - ? i18n`required` + ? i18n.str`required` : !Amounts.parse(value.default_max_deposit_fee) - ? i18n`invalid format` + ? i18n.str`invalid format` : undefined, default_max_wire_fee: !value.default_max_wire_fee - ? i18n`required` + ? i18n.str`required` : !Amounts.parse(value.default_max_wire_fee) - ? i18n`invalid format` + ? i18n.str`invalid format` : undefined, default_wire_fee_amortization: value.default_wire_fee_amortization === undefined - ? i18n`required` + ? i18n.str`required` : isNaN(value.default_wire_fee_amortization) - ? i18n`is not a number` + ? i18n.str`is not a number` : value.default_wire_fee_amortization < 1 - ? i18n`must be 1 or greater` + ? i18n.str`must be 1 or greater` : undefined, - default_pay_delay: !value.default_pay_delay ? i18n`required` : undefined, + default_pay_delay: !value.default_pay_delay + ? i18n.str`required` + : undefined, default_wire_transfer_delay: !value.default_wire_transfer_delay - ? i18n`required` + ? i18n.str`required` : undefined, address: undefinedIfEmpty({ address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 - ? i18n`max 7 lines` + ? i18n.str`max 7 lines` : undefined, }), jurisdiction: undefinedIfEmpty({ address_lines: value.address?.address_lines && value.address?.address_lines.length > 7 - ? i18n`max 7 lines` + ? i18n.str`max 7 lines` : undefined, }), }; @@ -160,7 +161,7 @@ export function UpdatePage({ <div class="level-left"> <div class="level-item"> <span class="is-size-4"> - <Translate>Instance id</Translate>: <b>{id}</b> + <i18n.Translate>Instance id</i18n.Translate>: <b>{id}</b> </span> </div> </div> @@ -169,7 +170,7 @@ export function UpdatePage({ <h1 class="title"> <button class="button is-danger" - data-tooltip={i18n`Change the authorization method use for this instance.`} + data-tooltip={i18n.str`Change the authorization method use for this instance.`} onClick={(): void => { setActive(!active); }} @@ -178,7 +179,7 @@ export function UpdatePage({ <i class="mdi mdi-lock-reset" /> </div> <span> - <Translate>Manage access token</Translate> + <i18n.Translate>Manage access token</i18n.Translate> </span> </button> </h1> @@ -229,19 +230,19 @@ export function UpdatePage({ onClick={onBack} data-tooltip="cancel operation" > - <Translate>Cancel</Translate> + <i18n.Translate>Cancel</i18n.Translate> </button> <AsyncButton onClick={submit} data-tooltip={ hasErrors - ? i18n`Need to complete marked fields` + ? i18n.str`Need to complete marked fields` : "confirm operation" } disabled={hasErrors} > - <Translate>Confirm</Translate> + <i18n.Translate>Confirm</i18n.Translate> </AsyncButton> </div> </div> diff --git a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx index 480274e66..668fe9a8d 100644 --- a/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx +++ b/packages/merchant-backoffice-ui/src/paths/instance/update/index.tsx @@ -13,6 +13,7 @@ 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 { useTranslationContext } from "@gnu-taler/web-util/lib/index.browser"; import { Fragment, h, VNode } from "preact"; import { useState } from "preact/hooks"; import { Loading } from "../../../components/exception/loading.js"; @@ -26,7 +27,6 @@ import { useManagedInstanceDetails, useManagementAPI, } from "../../../hooks/instance.js"; -import { useTranslator } from "../../../i18n/index.js"; import { Notification } from "../../../utils/types.js"; import { UpdatePage } from "./UpdatePage.js"; @@ -70,7 +70,7 @@ function CommonUpdate( ): VNode { const { changeToken } = useInstanceContext(); const [notif, setNotif] = useState<Notification | undefined>(undefined); - const i18n = useTranslator(); + const { i18n } = useTranslationContext(); if (result.clientError && result.isUnauthorized) return onUnauthorized(); if (result.clientError && result.isNotfound) return onNotFound(); @@ -91,7 +91,7 @@ function CommonUpdate( .then(onConfirm) .catch((error: Error) => setNotif({ - message: i18n`Failed to create instance`, + message: i18n.str`Failed to create instance`, type: "ERROR", description: error.message, }), diff --git a/packages/merchant-backoffice-ui/src/stories.test.ts b/packages/merchant-backoffice-ui/src/stories.test.ts index 6a401dd25..e85be31b7 100644 --- a/packages/merchant-backoffice-ui/src/stories.test.ts +++ b/packages/merchant-backoffice-ui/src/stories.test.ts @@ -20,15 +20,13 @@ */ import { setupI18n } from "@gnu-taler/taler-util"; import { parseGroupImport } from "@gnu-taler/web-util/lib/index.browser"; - +import { h as create } from "preact"; +import { render as renderToString } from "preact-render-to-string"; import * as admin from "./paths/admin/index.stories.js"; import * as instance from "./paths/instance/index.stories.js"; setupI18n("en", { en: {} }); -import { h as create } from "preact" -import { render as renderToString } from "preact-render-to-string"; - describe("All the examples:", () => { const cms = parseGroupImport({ admin, instance }); cms.forEach((group) => { @@ -37,8 +35,11 @@ describe("All the examples:", () => { describe(`Component: ${component.name}`, () => { component.examples.forEach((example) => { it(`should render example: ${example.name}`, () => { - const vdom = create(example.render.component, example.render.props) - const html = renderToString(vdom) + const vdom = create( + example.render.component, + example.render.props, + ); + const html = renderToString(vdom); // console.log(html) }); }); diff --git a/packages/merchant-backoffice-ui/tests/functions/regex.test.ts b/packages/merchant-backoffice-ui/tests/functions/regex.test.ts index 12a318c3d..d866a13a0 100644 --- a/packages/merchant-backoffice-ui/tests/functions/regex.test.ts +++ b/packages/merchant-backoffice-ui/tests/functions/regex.test.ts @@ -14,10 +14,10 @@ GNU Taler; see the file COPYING. If not, see <http://www.gnu.org/licenses/> */ - /** - * - * @author Sebastian Javier Marchano (sebasjm) - */ +/** +* +* @author Sebastian Javier Marchano (sebasjm) +*/ import { AMOUNT_REGEX, PAYTO_REGEX } from "../../src/utils/constants.js"; @@ -29,11 +29,11 @@ describe('payto uri format', () => { 'payto://void/?amount=EUR:10.5', 'payto://ilp/g.acme.bob' ] - - test('should be valid', () => { + + it('should be valid', () => { valids.forEach(v => expect(v).toMatch(PAYTO_REGEX)) }); - + const invalids = [ // has two question marks 'payto://iban/DE75?512108001245126199?amount=EUR:200.0&message=hello', @@ -46,10 +46,10 @@ describe('payto uri format', () => { // payto:// is incomplete 'payto: //ilp/g.acme.bob' ] - - test('should not be valid', () => { + + it('should not be valid', () => { invalids.forEach(v => expect(v).not.toMatch(PAYTO_REGEX)) - }); + }); }) describe('amount format', () => { @@ -62,11 +62,11 @@ describe('amount format', () => { 'ARSCOL:10', 'THISISTHEMOTHERCOIN:1,000,000.123,123', ] - - test('should be valid', () => { + + it('should be valid', () => { valids.forEach(v => expect(v).toMatch(AMOUNT_REGEX)) }); - + const invalids = [ //no currency name ':10', @@ -79,9 +79,9 @@ describe('amount format', () => { //missing value 'USD:', ] - - test('should not be valid', () => { + + it('should not be valid', () => { invalids.forEach(v => expect(v).not.toMatch(AMOUNT_REGEX)) - }); + }); })
\ No newline at end of file diff --git a/packages/merchant-backoffice-ui/tests/header.test.tsx b/packages/merchant-backoffice-ui/tests/header.test.tsx index c917366e7..1cf2b7e6c 100644 --- a/packages/merchant-backoffice-ui/tests/header.test.tsx +++ b/packages/merchant-backoffice-ui/tests/header.test.tsx @@ -23,23 +23,23 @@ import { h } from "preact"; import { ProductList } from "../src/components/product/ProductList.js"; // See: https://github.com/preactjs/enzyme-adapter-preact-pure // import { shallow } from 'enzyme'; +import { render } from "@testing-library/preact"; import * as backend from "../src/context/config.js"; -import { render, findAllByText } from "@testing-library/preact"; -import * as i18n from "../src/context/translation.js"; +// import * as i18n from "../src/context/translation.js"; -import * as jedLib from "jed"; -const handler = new jedLib.Jed("en"); +// import * as jedLib from "jed"; +// const handler = new jedLib.Jed("en"); describe("Initial Test of the Sidebar", () => { beforeEach(() => { jest .spyOn(backend, "useConfigContext") .mockImplementation(() => ({ version: "", currency: "" })); - jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({ - changeLanguage: () => null, - handler, - lang: "en", - })); + // jest.spyOn(i18n, "useTranslationContext").mockImplementation(() => ({ + // changeLanguage: () => null, + // handler, + // lang: "en", + // })); }); test("Product list renders a table", () => { const context = render( diff --git a/packages/web-util/src/tests/hook.ts b/packages/web-util/src/tests/hook.ts index 0ec47f252..ddc96eb0e 100644 --- a/packages/web-util/src/tests/hook.ts +++ b/packages/web-util/src/tests/hook.ts @@ -120,6 +120,7 @@ interface Mounted<T> { * * @returns testing API */ +// eslint-disable-next-line @typescript-eslint/ban-types export function mountHook<T extends object>( hookToBeTested: () => RecursiveState<T>, Context?: ({ children }: { children: any }) => VNode | null, @@ -242,6 +243,7 @@ interface HookTestResultError { * * @returns testing result, should also be checked to be "ok" */ +// eslint-disable-next-line @typescript-eslint/ban-types export async function hookBehaveLikeThis<T extends object, PropsType>( hookFunction: (p: PropsType) => RecursiveState<T>, props: PropsType, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cec9f0f64..9460653d4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -374,7 +374,7 @@ importers: jest: ^26.6.3 jest-preset-preact: ^4.0.2 mocha: ^9.2.0 - preact: 10.6.5 + preact: 10.11.3 preact-render-to-string: ^5.2.6 preact-router: 3.2.1 qrcode-generator: 1.4.4 @@ -391,16 +391,16 @@ importers: date-fns: 2.29.3 history: 4.10.1 jed: 1.1.1 - preact: 10.6.5 - preact-router: 3.2.1_preact@10.6.5 + preact: 10.11.3 + preact-router: 3.2.1_preact@10.11.3 qrcode-generator: 1.4.4 swr: 1.3.0 yup: 0.32.11 devDependencies: '@creativebulma/bulma-tooltip': 1.2.0 '@gnu-taler/pogen': link:../pogen - '@testing-library/preact': 2.0.1_preact@10.6.5 - '@testing-library/preact-hooks': 1.1.0_vfcmu6iy7nffpurikpgxo6gwxi + '@testing-library/preact': 2.0.1_preact@10.11.3 + '@testing-library/preact-hooks': 1.1.0_eng4adldpgibddgycwaukopxga '@types/history': 4.7.11 '@types/jest': 26.0.24 '@types/mocha': 8.2.3 @@ -424,9 +424,9 @@ importers: html-webpack-skip-assets-plugin: 1.0.3 inline-chunk-html-plugin: 1.1.1 jest: 26.6.3 - jest-preset-preact: 4.0.5_moqeqtbsr7edkxzj3jgnhqkxsm + jest-preset-preact: 4.0.5_w5bq6jgm3cbfmbu2zwqko4iate mocha: 9.2.2 - preact-render-to-string: 5.2.6_preact@10.6.5 + preact-render-to-string: 5.2.6_preact@10.11.3 rimraf: 3.0.2 sass: 1.56.1 typedoc: 0.20.37_typescript@4.8.4 @@ -5795,14 +5795,14 @@ packages: preact: 10.11.2 dev: true - /@testing-library/preact-hooks/1.1.0_vfcmu6iy7nffpurikpgxo6gwxi: + /@testing-library/preact-hooks/1.1.0_eng4adldpgibddgycwaukopxga: resolution: {integrity: sha512-+JIor+NsOHkK3oIrwMDGKGHXTN0JJi462dBJlj4FNbGaDPTlctE6eu2ranWQirh7/FJMkWfzQCP+tk7jmY8ZrQ==} peerDependencies: '@testing-library/preact': ^2.0.0 preact: ^10.4.8 dependencies: - '@testing-library/preact': 2.0.1_preact@10.6.5 - preact: 10.6.5 + '@testing-library/preact': 2.0.1_preact@10.11.3 + preact: 10.11.3 dev: true /@testing-library/preact/2.0.1_preact@10.11.2: @@ -5815,14 +5815,14 @@ packages: preact: 10.11.2 dev: true - /@testing-library/preact/2.0.1_preact@10.6.5: + /@testing-library/preact/2.0.1_preact@10.11.3: resolution: {integrity: sha512-79kwVOY+3caoLgaPbiPzikjgY0Aya7Fc7TvGtR1upCnz2wrtmPDnN2t9vO7I7vDP2zoA+feSwOH5Q0BFErhaaQ==} engines: {node: '>= 10'} peerDependencies: preact: '>=10 || ^10.0.0-alpha.0 || ^10.0.0-beta.0' dependencies: '@testing-library/dom': 7.31.2 - preact: 10.6.5 + preact: 10.11.3 dev: true /@tootallnate/once/1.1.2: @@ -14567,7 +14567,7 @@ packages: - supports-color dev: true - /jest-preset-preact/4.0.5_moqeqtbsr7edkxzj3jgnhqkxsm: + /jest-preset-preact/4.0.5_w5bq6jgm3cbfmbu2zwqko4iate: resolution: {integrity: sha512-MnU7mfpnwopJkdx0WoEyRmrNDIvRN+w6sOur0zEhaRYYMo0gJM7UdZHWTV8k6uo0+ypY+m0kQW6kMukUx4v8JQ==} peerDependencies: jest: 26.x || 27.x @@ -14584,8 +14584,8 @@ packages: isomorphic-unfetch: 3.1.0 jest: 26.6.3 jest-watch-typeahead: 0.6.5_jest@26.6.3 - preact: 10.6.5 - preact-render-to-string: 5.2.6_preact@10.6.5 + preact: 10.11.3 + preact-render-to-string: 5.2.6_preact@10.11.3 transitivePeerDependencies: - encoding - supports-color @@ -17955,15 +17955,6 @@ packages: preact: 10.11.3 pretty-format: 3.8.0 - /preact-render-to-string/5.2.6_preact@10.6.5: - resolution: {integrity: sha512-JyhErpYOvBV1hEPwIxc/fHWXPfnEGdRKxc8gFdAZ7XV4tlzyzG847XAyEZqoDnynP88akM4eaHcSOzNcLWFguw==} - peerDependencies: - preact: '>=10' - dependencies: - preact: 10.6.5 - pretty-format: 3.8.0 - dev: true - /preact-router/3.2.1_preact@10.11.2: resolution: {integrity: sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg==} peerDependencies: @@ -17980,23 +17971,12 @@ packages: preact: 10.11.3 dev: false - /preact-router/3.2.1_preact@10.6.5: - resolution: {integrity: sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg==} - peerDependencies: - preact: '>=10' - dependencies: - preact: 10.6.5 - dev: false - /preact/10.11.2: resolution: {integrity: sha512-skAwGDFmgxhq1DCBHke/9e12ewkhc7WYwjuhHB8HHS8zkdtITXLRmUMTeol2ldxvLwYtwbFeifZ9uDDWuyL4Iw==} /preact/10.11.3: resolution: {integrity: sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==} - /preact/10.6.5: - resolution: {integrity: sha512-i+LXM6JiVjQXSt2jG2vZZFapGpCuk1fl8o6ii3G84MA3xgj686FKjs4JFDkmUVhtxyq21+4ay74zqPykz9hU6w==} - /prelude-ls/1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} |