gnunet-svn
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[taler-wallet-core] 03/06: using web-utils in demobank


From: gnunet
Subject: [taler-wallet-core] 03/06: using web-utils in demobank
Date: Tue, 06 Dec 2022 16:13:38 +0100

This is an automated email from the git hooks/post-receive script.

sebasjm pushed a commit to branch master
in repository wallet-core.

commit 5969a44391f32d931d0b26416fb3e1528f4a32a2
Author: Sebastian <sebasjm@gmail.com>
AuthorDate: Tue Dec 6 11:25:05 2022 -0300

    using web-utils in demobank
---
 packages/demobank-ui/build.mjs                     |  43 +++-
 packages/demobank-ui/dev.mjs                       |  30 +++
 packages/demobank-ui/package.json                  |   7 +-
 .../demobank-ui/src/components/menu/SideBar.tsx    |   9 +-
 .../src/components/picker/DurationPicker.tsx       |  12 +-
 packages/demobank-ui/src/context/translation.ts    |  52 +++--
 packages/demobank-ui/src/hooks/index.ts            |  79 +------
 packages/demobank-ui/src/hooks/useLang.ts          |  30 +++
 packages/demobank-ui/src/hooks/useLocalStorage.ts  |  80 +++++++
 packages/demobank-ui/src/i18n/index.tsx            | 201 ------------------
 packages/demobank-ui/src/index.html                |  15 +-
 packages/demobank-ui/src/index.tsx                 |   1 +
 .../src/pages/home/QrCodeSection.stories.tsx       |  33 +++
 .../demobank-ui/src/pages/home/QrCodeSection.tsx   |  55 +++++
 .../demobank-ui/src/pages/home/index.stories.tsx   |   1 +
 packages/demobank-ui/src/pages/home/index.tsx      | 234 +++++++++------------
 packages/demobank-ui/src/scss/main.scss            |   8 +-
 packages/demobank-ui/src/stories.tsx               |  46 ++++
 packages/demobank-ui/static/index.html             |  15 --
 19 files changed, 477 insertions(+), 474 deletions(-)

diff --git a/packages/demobank-ui/build.mjs b/packages/demobank-ui/build.mjs
index 63ddc1f25..c93b4eb67 100755
--- a/packages/demobank-ui/build.mjs
+++ b/packages/demobank-ui/build.mjs
@@ -18,9 +18,9 @@
 import esbuild from "esbuild";
 import path from "path";
 import fs from "fs";
-import crypto from "crypto";
-import { sassPlugin } from "esbuild-sass-plugin";
+import sass from "sass";
 
+// eslint-disable-next-line no-undef
 const BASE = process.cwd();
 
 const preact = path.join(
@@ -44,14 +44,16 @@ const preactCompatPlugin = {
   },
 };
 
-const entryPoints = ["src/index.tsx"];
+const entryPoints = ["src/index.tsx", "src/stories.tsx"];
 
 let GIT_ROOT = BASE;
 while (!fs.existsSync(path.join(GIT_ROOT, ".git")) && GIT_ROOT !== "/") {
   GIT_ROOT = path.join(GIT_ROOT, "../");
 }
 if (GIT_ROOT === "/") {
+  // eslint-disable-next-line no-undef
   console.log("not found");
+  // eslint-disable-next-line no-undef
   process.exit(1);
 }
 const GIT_HASH = GIT_ROOT === "/" ? undefined : git_hash();
@@ -86,6 +88,26 @@ function copyFilesPlugin(options) {
   };
 }
 
+const DEFAULT_SASS_FILTER = /\.(s[ac]ss|css)$/
+
+const buildSassPlugin = {
+  name: "custom-build-sass",
+  setup(build) {
+
+    build.onLoad({ filter: DEFAULT_SASS_FILTER }, ({ path: file }) => {
+      const resolveDir = path.dirname(file)
+      const { css: contents } = sass.compile(file, { loadPaths: ["./"] })
+
+      return {
+        resolveDir,
+        loader: 'css',
+        contents
+      }
+    });
+
+  },
+};
+
 export const buildConfig = {
   entryPoints: [...entryPoints],
   bundle: true,
@@ -95,6 +117,10 @@ export const buildConfig = {
     ".svg": "file",
     ".png": "dataurl",
     ".jpeg": "dataurl",
+    '.ttf': 'file',
+    '.woff': 'file',
+    '.woff2': 'file',
+    '.eot': 'file',
   },
   target: ["es6"],
   format: "esm",
@@ -108,17 +134,14 @@ export const buildConfig = {
   },
   plugins: [
     preactCompatPlugin,
-    sassPlugin(),
     copyFilesPlugin([
       {
-        src: "static/index.html",
-        dest: "dist/index.html",
+        src: "./src/index.html",
+        dest: "./dist/index.html",
       },
     ]),
+    buildSassPlugin
   ],
 };
 
-esbuild.build(buildConfig).catch((e) => {
-  console.log(e);
-  process.exit(1);
-});
+await esbuild.build(buildConfig)
diff --git a/packages/demobank-ui/dev.mjs b/packages/demobank-ui/dev.mjs
new file mode 100755
index 000000000..35a9fa16c
--- /dev/null
+++ b/packages/demobank-ui/dev.mjs
@@ -0,0 +1,30 @@
+#!/usr/bin/env node
+/*
+ This file is part of GNU Taler
+ (C) 2022 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/>
+ */
+
+import { serve } from "@gnu-taler/web-util/lib/index.node";
+import esbuild from "esbuild";
+import { buildConfig } from "./build.mjs";
+
+buildConfig.inject = ['./node_modules/@gnu-taler/web-util/lib/live-reload.mjs']
+
+serve({
+  folder: './dist',
+  port: 8080,
+  source: './src',
+  development: true,
+  onUpdate: async () => esbuild.build(buildConfig)
+})
diff --git a/packages/demobank-ui/package.json 
b/packages/demobank-ui/package.json
index cc8048a0f..41031977f 100644
--- a/packages/demobank-ui/package.json
+++ b/packages/demobank-ui/package.json
@@ -11,13 +11,13 @@
   },
   "dependencies": {
     "@gnu-taler/taler-util": "workspace:*",
+    "@gnu-taler/web-util": "workspace:*",
     "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",
-    "react": "npm:@preact/compat@^17.1.2",
     "swr": "1.3.0"
   },
   "devDependencies": {
@@ -30,11 +30,10 @@
     "bulma-checkbox": "^1.1.1",
     "bulma-radio": "^1.1.1",
     "esbuild": "^0.15.12",
-    "esbuild-sass-plugin": "^2.4.0",
     "eslint": "^8.26.0",
     "eslint-config-preact": "^1.2.0",
     "po2json": "^0.4.5",
-    "sass": "1.32.13",
+    "sass": "1.56.1",
     "typescript": "^4.4.4"
   }
 }
diff --git a/packages/demobank-ui/src/components/menu/SideBar.tsx 
b/packages/demobank-ui/src/components/menu/SideBar.tsx
index d7833df5a..7bfba2a75 100644
--- a/packages/demobank-ui/src/components/menu/SideBar.tsx
+++ b/packages/demobank-ui/src/components/menu/SideBar.tsx
@@ -20,7 +20,7 @@
  */
 
 import { h, VNode } from "preact";
-import { Translate } from "../../i18n";
+import { useTranslationContext } from "../../context/translation.js";
 
 interface Props {
   mobile?: boolean;
@@ -31,6 +31,7 @@ export function Sidebar({ mobile }: Props): VNode {
   const config = { version: "none" };
   // FIXME: add replacement for __VERSION__ with the current version
   const process = { env: { __VERSION__: "0.0.0" } };
+  const { i18n } = useTranslationContext();
 
   return (
     <aside class="aside is-placed-left is-expanded">
@@ -49,20 +50,20 @@ export function Sidebar({ mobile }: Props): VNode {
       </div>
       <div class="menu is-menu-main">
         <p class="menu-label">
-          <Translate>Bank menu</Translate>
+          <i18n.Translate>Bank menu</i18n.Translate>
         </p>
         <ul class="menu-list">
           <li>
             <div class="ml-4">
               <span class="menu-item-label">
-                <Translate>Select option1</Translate>
+                <i18n.Translate>Select option1</i18n.Translate>
               </span>
             </div>
           </li>
           <li>
             <div class="ml-4">
               <span class="menu-item-label">
-                <Translate>Select option2</Translate>
+                <i18n.Translate>Select option2</i18n.Translate>
               </span>
             </div>
           </li>
diff --git a/packages/demobank-ui/src/components/picker/DurationPicker.tsx 
b/packages/demobank-ui/src/components/picker/DurationPicker.tsx
index 94f2326bc..b8a7671c3 100644
--- a/packages/demobank-ui/src/components/picker/DurationPicker.tsx
+++ b/packages/demobank-ui/src/components/picker/DurationPicker.tsx
@@ -21,7 +21,7 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { useTranslator } from "../../i18n";
+import { useTranslationContext } from "../../context/translation.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/demobank-ui/src/context/translation.ts 
b/packages/demobank-ui/src/context/translation.ts
index a411ecb16..a50f81b86 100644
--- a/packages/demobank-ui/src/context/translation.ts
+++ b/packages/demobank-ui/src/context/translation.ts
@@ -19,27 +19,42 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
+import { i18n, setupI18n } from "@gnu-taler/taler-util";
 import { createContext, h, VNode } from "preact";
 import { useContext, useEffect } from "preact/hooks";
-import { useLang } from "../hooks/index.js";
-import * as jedLib from "jed";
+import { useLang } from "../hooks/useLang.js";
 import { strings } from "../i18n/strings.js";
 
 interface Type {
   lang: string;
-  handler: any;
+  supportedLang: { [id in keyof typeof supportedLang]: string };
   changeLanguage: (l: string) => void;
+  i18n: typeof i18n;
+  isSaved: boolean;
 }
+
+const supportedLang = {
+  es: "Español [es]",
+  ja: "日本語 [ja]",
+  en: "English [en]",
+  fr: "Français [fr]",
+  de: "Deutsch [de]",
+  sv: "Svenska [sv]",
+  it: "Italiano [it]",
+  // ko: "한국어 [ko]",
+  // ru: "Ру́сский язы́к [ru]",
+  tr: "Türk [tr]",
+  navigator: "Defined by navigator",
+};
+
 const initial = {
   lang: "en",
-  handler: null,
+  supportedLang,
   changeLanguage: () => {
-    /**
-     * This function will be replaced by one with
-     * the same signature _but_ coming from the state.
-     * FIXME: clarify this design.
-     */
+    // do not change anything
   },
+  i18n,
+  isSaved: false,
 };
 const Context = createContext<Type>(initial);
 
@@ -55,14 +70,23 @@ export const TranslationProvider = ({
   children,
   forceLang,
 }: Props): VNode => {
-  const [lang, changeLanguage] = useLang(initial);
+  const [lang, changeLanguage, isSaved] = useLang(initial);
   useEffect(() => {
-    if (forceLang) changeLanguage(forceLang);
+    if (forceLang) {
+      changeLanguage(forceLang);
+    }
   });
-  console.log("lang store", strings);
-  const handler = new jedLib.Jed(strings[lang] || strings["en"]);
+  useEffect(() => {
+    setupI18n(lang, strings);
+  }, [lang]);
+  if (forceLang) {
+    setupI18n(forceLang, strings);
+  } else {
+    setupI18n(lang, strings);
+  }
+
   return h(Context.Provider, {
-    value: { lang, handler, changeLanguage },
+    value: { lang, changeLanguage, supportedLang, i18n, isSaved },
     children,
   });
 };
diff --git a/packages/demobank-ui/src/hooks/index.ts 
b/packages/demobank-ui/src/hooks/index.ts
index 94e66e5e3..b4191d182 100644
--- a/packages/demobank-ui/src/hooks/index.ts
+++ b/packages/demobank-ui/src/hooks/index.ts
@@ -19,7 +19,8 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-import { StateUpdater, useState } from "preact/hooks";
+import { StateUpdater } from "preact/hooks";
+import { useLocalStorage, useNotNullLocalStorage } from "./useLocalStorage.js";
 export type ValueOrFunction<T> = T | ((p: T) => T);
 
 const calculateRootPath = () => {
@@ -68,79 +69,3 @@ export function useBackendInstanceToken(
 
   return [token, setToken];
 }
-
-export function useLang(initial?: string): [string, StateUpdater<string>] {
-  const browserLang =
-    typeof window !== "undefined"
-      ? navigator.language || (navigator as any).userLanguage
-      : undefined;
-  const defaultLang = (browserLang || initial || "en").substring(0, 2);
-  const [value, setValue] = useNotNullLocalStorage(
-    "lang-preference",
-    defaultLang,
-  );
-  function updateValue(newValue: string | ((v: string) => string)) {
-    if (document.body.parentElement) {
-      const htmlElement = document.body.parentElement;
-      if (typeof newValue === "string") {
-        htmlElement.lang = newValue;
-        setValue(newValue);
-      } else if (typeof newValue === "function")
-        setValue((old) => {
-          const nv = newValue(old);
-          htmlElement.lang = nv;
-          return nv;
-        });
-    } else setValue(newValue);
-  }
-  return [value, updateValue];
-}
-
-export function useLocalStorage(
-  key: string,
-  initialValue?: string,
-): [string | undefined, StateUpdater<string | undefined>] {
-  const [storedValue, setStoredValue] = useState<string | undefined>(
-    (): string | undefined => {
-      return typeof window !== "undefined"
-        ? window.localStorage.getItem(key) || initialValue
-        : initialValue;
-    },
-  );
-
-  const setValue = (
-    value?: string | ((val?: string) => string | undefined),
-  ) => {
-    setStoredValue((p) => {
-      const toStore = value instanceof Function ? value(p) : value;
-      if (typeof window !== "undefined")
-        if (!toStore) window.localStorage.removeItem(key);
-        else window.localStorage.setItem(key, toStore);
-
-      return toStore;
-    });
-  };
-
-  return [storedValue, setValue];
-}
-
-export function useNotNullLocalStorage(
-  key: string,
-  initialValue: string,
-): [string, StateUpdater<string>] {
-  const [storedValue, setStoredValue] = useState<string>((): string => {
-    return typeof window !== "undefined"
-      ? window.localStorage.getItem(key) || initialValue
-      : initialValue;
-  });
-
-  const setValue = (value: string | ((val: string) => string)) => {
-    const valueToStore = value instanceof Function ? value(storedValue) : 
value;
-    setStoredValue(valueToStore);
-    if (typeof window !== "undefined")
-      if (!valueToStore) window.localStorage.removeItem(key);
-      else window.localStorage.setItem(key, valueToStore);
-  };
-
-  return [storedValue, setValue];
-}
diff --git a/packages/demobank-ui/src/hooks/useLang.ts 
b/packages/demobank-ui/src/hooks/useLang.ts
new file mode 100644
index 000000000..5b02c5255
--- /dev/null
+++ b/packages/demobank-ui/src/hooks/useLang.ts
@@ -0,0 +1,30 @@
+/*
+ This file is part of GNU Anastasis
+ (C) 2021-2022 Anastasis SARL
+
+ GNU Anastasis is free software; you can redistribute it and/or modify it 
under the
+ terms of the GNU Affero General Public License as published by the Free 
Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Anastasis 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 Affero General Public License for more 
details.
+
+ You should have received a copy of the GNU Affero General Public License 
along with
+ GNU Anastasis; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+ */
+
+import { useNotNullLocalStorage } from "./useLocalStorage.js";
+
+function getBrowserLang(): string | undefined {
+  if (window.navigator.languages) return window.navigator.languages[0];
+  if (window.navigator.language) return window.navigator.language;
+  return undefined;
+}
+
+export function useLang(
+  initial?: string,
+): [string, (s: string) => void, boolean] {
+  const defaultLang = (getBrowserLang() || initial || "en").substring(0, 2);
+  return useNotNullLocalStorage("lang-preference", defaultLang);
+}
diff --git a/packages/demobank-ui/src/hooks/useLocalStorage.ts 
b/packages/demobank-ui/src/hooks/useLocalStorage.ts
new file mode 100644
index 000000000..ed5b491f2
--- /dev/null
+++ b/packages/demobank-ui/src/hooks/useLocalStorage.ts
@@ -0,0 +1,80 @@
+/*
+ This file is part of GNU Anastasis
+ (C) 2021-2022 Anastasis SARL
+
+ GNU Anastasis is free software; you can redistribute it and/or modify it 
under the
+ terms of the GNU Affero General Public License as published by the Free 
Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Anastasis 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 Affero General Public License for more 
details.
+
+ You should have received a copy of the GNU Affero General Public License 
along with
+ GNU Anastasis; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+
+import { StateUpdater, useState } from "preact/hooks";
+
+export function useLocalStorage(
+  key: string,
+  initialValue?: string,
+): [string | undefined, StateUpdater<string | undefined>] {
+  const [storedValue, setStoredValue] = useState<string | undefined>(
+    (): string | undefined => {
+      return typeof window !== "undefined"
+        ? window.localStorage.getItem(key) || initialValue
+        : initialValue;
+    },
+  );
+
+  const setValue = (
+    value?: string | ((val?: string) => string | undefined),
+  ): void => {
+    setStoredValue((p) => {
+      const toStore = value instanceof Function ? value(p) : value;
+      if (typeof window !== "undefined") {
+        if (!toStore) {
+          window.localStorage.removeItem(key);
+        } else {
+          window.localStorage.setItem(key, toStore);
+        }
+      }
+      return toStore;
+    });
+  };
+
+  return [storedValue, setValue];
+}
+
+//TODO: merge with the above function
+export function useNotNullLocalStorage(
+  key: string,
+  initialValue: string,
+): [string, StateUpdater<string>, boolean] {
+  const [storedValue, setStoredValue] = useState<string>((): string => {
+    return typeof window !== "undefined"
+      ? window.localStorage.getItem(key) || initialValue
+      : initialValue;
+  });
+
+  const setValue = (value: string | ((val: string) => string)): void => {
+    const valueToStore = value instanceof Function ? value(storedValue) : 
value;
+    setStoredValue(valueToStore);
+    if (typeof window !== "undefined") {
+      if (!valueToStore) {
+        window.localStorage.removeItem(key);
+      } else {
+        window.localStorage.setItem(key, valueToStore);
+      }
+    }
+  };
+
+  const isSaved = window.localStorage.getItem(key) !== null;
+  return [storedValue, setValue, isSaved];
+}
diff --git a/packages/demobank-ui/src/i18n/index.tsx 
b/packages/demobank-ui/src/i18n/index.tsx
deleted file mode 100644
index 2489184b2..000000000
--- a/packages/demobank-ui/src/i18n/index.tsx
+++ /dev/null
@@ -1,201 +0,0 @@
-/*
- This file is part of GNU Taler
- (C) 2021 Taler Systems S.A.
-
- GNU Taler is free software; you can redistribute it and/or modify it under the
- terms of the GNU General Public License as published by the Free Software
- Foundation; either version 3, or (at your option) any later version.
-
- GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
- WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
- A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
-
- You should have received a copy of the GNU General Public License along with
- GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
- */
-
-/**
- * Translation helpers for React components and template literals.
- */
-
-/**
- * Imports
- */
-import { ComponentChild, ComponentChildren, h, Fragment, VNode } from "preact";
-
-import { useTranslationContext } from "../context/translation";
-
-export function useTranslator() {
-  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/demobank-ui/src/index.html 
b/packages/demobank-ui/src/index.html
index a2154429b..4b3c89a66 100644
--- a/packages/demobank-ui/src/index.html
+++ b/packages/demobank-ui/src/index.html
@@ -16,25 +16,26 @@
  @author Sebastian Javier Marchano
 -->
 <!DOCTYPE html>
-<html
-  lang="en"
-  class="has-aside-left has-aside-mobile-transition has-navbar-fixed-top 
has-aside-expanded"
->
+<html lang="en">
   <head>
+    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
     <meta charset="utf-8" />
     <meta name="viewport" content="width=device-width,initial-scale=1" />
     <meta name="mobile-web-app-capable" content="yes" />
     <meta name="apple-mobile-web-app-capable" content="yes" />
-
     <link
       rel="icon"
       
href="data:;base64,AAABAAEAEBAAAAEAIABoBAAAFgAAACgAAAAQAAAAIAAAAAEAIAAAAAAAAAQAABILAAASCwAAAAAAAAAAAAD///////////////////////////////////////////////////////////////////////////////////////////////////7//v38//78/P/+/fz//vz7///+/v/+/f3//vz7///+/v/+/fz//v38///////////////////////+/v3///7+/////////////////////////////////////////////////////////v3//v79///////+/v3///////r28v/ct5//06SG/9Gffv/Xqo7/7N/V/9e2nf/bsJb/6uDW/9Sskf/euKH/+/j2///////+/v3//////+3azv+/eE3/2rWd/9Kkhv/Vr5T/48i2/8J+VP/
 [...]
     />
     <link rel="shortcut icon" href="data:image/x-icon;," type="image/x-icon" />
+    <title>Demobank</title>
+    <!-- Optional customization script.  -->
+    <script src="demobank-ui-settings.js"></script>
+    <!-- Entry point for the demobank SPA. -->
+    <script type="module" src="index.js"></script>
+    <link rel="stylesheet" href="index.css" />
   </head>
-
   <body>
     <div id="app"></div>
-    <script type="module" src="index.tsx"></script>
   </body>
 </html>
diff --git a/packages/demobank-ui/src/index.tsx 
b/packages/demobank-ui/src/index.tsx
index 4302bb33b..0b88b0393 100644
--- a/packages/demobank-ui/src/index.tsx
+++ b/packages/demobank-ui/src/index.tsx
@@ -1,6 +1,7 @@
 import App from "./components/app.js";
 export default App;
 import { render, h } from "preact";
+import "./scss/main.scss";
 
 const app = document.getElementById("app");
 
diff --git a/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx 
b/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx
new file mode 100644
index 000000000..521d4255e
--- /dev/null
+++ b/packages/demobank-ui/src/pages/home/QrCodeSection.stories.tsx
@@ -0,0 +1,33 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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 { QrCodeSection } from "./QrCodeSection.js";
+
+export default {
+  title: "Qr Code Selection",
+};
+
+export const SimpleExample = {
+  component: QrCodeSection,
+  props: {
+    talerWithdrawUri: "taler://withdraw/asdasdasd",
+  },
+};
diff --git a/packages/demobank-ui/src/pages/home/QrCodeSection.tsx 
b/packages/demobank-ui/src/pages/home/QrCodeSection.tsx
new file mode 100644
index 000000000..1d7b3db10
--- /dev/null
+++ b/packages/demobank-ui/src/pages/home/QrCodeSection.tsx
@@ -0,0 +1,55 @@
+/*
+ This file is part of GNU Taler
+ (C) 2022 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/>
+ */
+
+import { h, VNode } from "preact";
+import { useEffect } from "preact/hooks";
+import { QR } from "../../components/QR.js";
+import { useTranslationContext } from "../../context/translation.js";
+
+export function QrCodeSection({
+  talerWithdrawUri,
+  abortButton,
+}: {
+  talerWithdrawUri: string;
+  abortButton: h.JSX.Element;
+}): VNode {
+  const { i18n } = useTranslationContext();
+  useEffect(() => {
+    //Taler Wallet WebExtension is listening to headers response and tab 
updates.
+    //In the SPA there is no header response with the Taler URI so
+    //this hack manually triggers the tab update after the QR is in the DOM.
+    window.location.hash = `/account/${new Date().getTime()}`;
+  }, []);
+
+  return (
+    <section id="main" class="content">
+      <h1 class="nav">{i18n.str`Transfer to Taler Wallet`}</h1>
+      <article>
+        <div class="qr-div">
+          <p>{i18n.str`Use this QR code to withdraw to your mobile 
wallet:`}</p>
+          {QR({ text: talerWithdrawUri })}
+          <p>
+            Click{" "}
+            <a id="linkqr" href={talerWithdrawUri}>{i18n.str`this link`}</a> to
+            open your Taler wallet!
+          </p>
+          <br />
+          {abortButton}
+        </div>
+      </article>
+    </section>
+  );
+}
diff --git a/packages/demobank-ui/src/pages/home/index.stories.tsx 
b/packages/demobank-ui/src/pages/home/index.stories.tsx
new file mode 100644
index 000000000..e9ac00a76
--- /dev/null
+++ b/packages/demobank-ui/src/pages/home/index.stories.tsx
@@ -0,0 +1 @@
+export * as qr from "./QrCodeSection.stories.js";
diff --git a/packages/demobank-ui/src/pages/home/index.tsx 
b/packages/demobank-ui/src/pages/home/index.tsx
index 8f522c07c..8b2ffefac 100644
--- a/packages/demobank-ui/src/pages/home/index.tsx
+++ b/packages/demobank-ui/src/pages/home/index.tsx
@@ -27,13 +27,16 @@ import {
 } from "preact/hooks";
 import talerLogo from "../../assets/logo-white.svg";
 import { LangSelectorLikePy as LangSelector } from 
"../../components/menu/LangSelector.js";
-import { QR } from "../../components/QR.js";
-import { useLocalStorage, useNotNullLocalStorage } from "../../hooks/index.js";
-import { Translate, useTranslator } from "../../i18n/index.js";
-import "../../scss/main.scss";
+import {
+  useLocalStorage,
+  useNotNullLocalStorage,
+} from "../../hooks/useLocalStorage.js";
+// import { Translate, useTranslator } from "../../i18n/index.js";
+import { useTranslationContext } from "../../context/translation.js";
 import { Amounts, HttpStatusCode, parsePaytoUri } from "@gnu-taler/taler-util";
 import { createHashHistory } from "history";
 import Router, { Route, route } from "preact-router";
+import { QrCodeSection } from "./QrCodeSection.js";
 
 interface BankUiSettings {
   allowRegistrations: boolean;
@@ -987,7 +990,7 @@ async function registrationCall(
 
 function ErrorBanner(Props: any): VNode | null {
   const [pageState, pageStateSetter] = Props.pageState;
-  // const i18n = useTranslator();
+  // const { i18n } = useTranslationContext();
   if (!pageState.error) return null;
 
   const rval = (
@@ -1041,7 +1044,7 @@ function StatusBanner(Props: any): VNode | null {
 }
 
 function BankFrame(Props: any): VNode {
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   const [pageState, pageStateSetter] = useContext(PageContext);
   console.log("BankFrame state", pageState);
   const logOut = (
@@ -1062,7 +1065,7 @@ function BankFrame(Props: any): VNode {
             };
           });
         }}
-      >{i18n`Logout`}</a>
+      >{i18n.str`Logout`}</a>
     </div>
   );
 
@@ -1080,7 +1083,7 @@ function BankFrame(Props: any): VNode {
         class="demobar"
         style="display: flex; flex-direction: row; justify-content: 
space-between;"
       >
-        <a href="#main" class="skip">{i18n`Skip to main content`}</a>
+        <a href="#main" class="skip">{i18n.str`Skip to main content`}</a>
         <div style="max-width: 50em; margin-left: 2em;">
           <h1>
             <span class="it">
@@ -1089,7 +1092,7 @@ function BankFrame(Props: any): VNode {
           </h1>
           {maybeDemoContent(
             <p>
-              <Translate>
+              <i18n.Translate>
                 This part of the demo shows how a bank that supports Taler
                 directly would work. In addition to using your own bank 
account,
                 you can also see the transaction history of some{" "}
@@ -1100,14 +1103,14 @@ function BankFrame(Props: any): VNode {
                   Public Accounts
                 </a>
                 .
-              </Translate>
+              </i18n.Translate>
             </p>,
           )}
         </div>
         <a href="https://taler.net/";>
           <img
             src={talerLogo}
-            alt={i18n`Taler logo`}
+            alt={i18n.str`Taler logo`}
             height="100"
             width="224"
             style="margin: 2em 2em"
@@ -1168,7 +1171,7 @@ function PaytoWireTransfer(Props: any): VNode {
   const [rawPaytoInput, rawPaytoInputSetter] = useState<string | undefined>(
     undefined,
   );
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   const { focus, backendState } = Props;
   const amountRegex = "^[0-9]+(.[0-9]+)?$";
   const ibanRegex = "^[A-Z][A-Z][0-9]+$";
@@ -1193,17 +1196,17 @@ function PaytoWireTransfer(Props: any): VNode {
     ? undefined
     : undefinedIfEmpty({
         iban: !submitData.iban
-          ? i18n`Missing IBAN`
+          ? i18n.str`Missing IBAN`
           : !/^[A-Z0-9]*$/.test(submitData.iban)
-          ? i18n`IBAN should have just uppercased letters and numbers`
+          ? i18n.str`IBAN should have just uppercased letters and numbers`
           : undefined,
-        subject: !submitData.subject ? i18n`Missing subject` : undefined,
+        subject: !submitData.subject ? i18n.str`Missing subject` : undefined,
         amount: !submitData.amount
-          ? i18n`Missing amount`
+          ? i18n.str`Missing amount`
           : !(parsedAmount = Amounts.parse(`${currency}:${submitData.amount}`))
-          ? i18n`Amount is not valid`
+          ? i18n.str`Amount is not valid`
           : Amounts.isZero(parsedAmount)
-          ? i18n`Should be greater than 0`
+          ? i18n.str`Should be greater than 0`
           : undefined,
       });
 
@@ -1212,7 +1215,7 @@ function PaytoWireTransfer(Props: any): VNode {
       <div>
         <div class="pure-form" name="wire-transfer-form">
           <p>
-            <label for="iban">{i18n`Receiver IBAN:`}</label>&nbsp;
+            <label for="iban">{i18n.str`Receiver IBAN:`}</label>&nbsp;
             <input
               ref={ref}
               type="text"
@@ -1235,7 +1238,7 @@ function PaytoWireTransfer(Props: any): VNode {
               isDirty={submitData?.iban !== undefined}
             />
             <br />
-            <label for="subject">{i18n`Transfer subject:`}</label>&nbsp;
+            <label for="subject">{i18n.str`Transfer subject:`}</label>&nbsp;
             <input
               type="text"
               name="subject"
@@ -1256,7 +1259,7 @@ function PaytoWireTransfer(Props: any): VNode {
               isDirty={submitData?.subject !== undefined}
             />
             <br />
-            <label for="amount">{i18n`Amount:`}</label>&nbsp;
+            <label for="amount">{i18n.str`Amount:`}</label>&nbsp;
             <input
               type="number"
               name="amount"
@@ -1309,7 +1312,7 @@ function PaytoWireTransfer(Props: any): VNode {
                     ...prevState,
 
                     error: {
-                      title: i18n`Field(s) missing.`,
+                      title: i18n.str`Field(s) missing.`,
                     },
                   }));
                   return;
@@ -1358,7 +1361,7 @@ function PaytoWireTransfer(Props: any): VNode {
               }));
             }}
           >
-            {i18n`Want to try the raw payto://-format?`}
+            {i18n.str`Want to try the raw payto://-format?`}
           </a>
         </p>
       </div>
@@ -1366,18 +1369,18 @@ function PaytoWireTransfer(Props: any): VNode {
 
   const errorsPayto = undefinedIfEmpty({
     rawPaytoInput: !rawPaytoInput
-      ? i18n`Missing payto address`
+      ? i18n.str`Missing payto address`
       : !parsePaytoUri(rawPaytoInput)
-      ? i18n`Payto does not follow the pattern`
+      ? i18n.str`Payto does not follow the pattern`
       : undefined,
   });
 
   return (
     <div>
-      <p>{i18n`Transfer money to account identified by payto:// URI:`}</p>
+      <p>{i18n.str`Transfer money to account identified by payto:// URI:`}</p>
       <div class="pure-form" name="payto-form">
         <p>
-          <label for="address">{i18n`payto URI:`}</label>&nbsp;
+          <label for="address">{i18n.str`payto URI:`}</label>&nbsp;
           <input
             name="address"
             type="text"
@@ -1386,7 +1389,7 @@ function PaytoWireTransfer(Props: any): VNode {
             id="address"
             value={rawPaytoInput ?? ""}
             required
-            placeholder={i18n`payto address`}
+            placeholder={i18n.str`payto address`}
             // pattern={`payto://iban/[A-Z][A-Z][0-9]+?message=[a-zA-Z0-9 
]+&amount=${currency}:[0-9]+(.[0-9]+)?`}
             onInput={(e): void => {
               rawPaytoInputSetter(e.currentTarget.value);
@@ -1410,7 +1413,7 @@ function PaytoWireTransfer(Props: any): VNode {
             class="pure-button pure-button-primary"
             type="submit"
             disabled={!!errorsPayto}
-            value={i18n`Send`}
+            value={i18n.str`Send`}
             onClick={async () => {
               // empty string evaluates to false.
               if (!rawPaytoInput) {
@@ -1444,7 +1447,7 @@ function PaytoWireTransfer(Props: any): VNode {
               }));
             }}
           >
-            {i18n`Use wire-transfer form?`}
+            {i18n.str`Use wire-transfer form?`}
           </a>
         </p>
       </div>
@@ -1459,7 +1462,7 @@ function PaytoWireTransfer(Props: any): VNode {
 function TalerWithdrawalConfirmationQuestion(Props: any): VNode {
   const [pageState, pageStateSetter] = useContext(PageContext);
   const { backendState } = Props;
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   const captchaNumbers = {
     a: Math.floor(Math.random() * 10),
     b: Math.floor(Math.random() * 10),
@@ -1468,15 +1471,15 @@ function TalerWithdrawalConfirmationQuestion(Props: 
any): VNode {
 
   return (
     <Fragment>
-      <h1 class="nav">{i18n`Confirm Withdrawal`}</h1>
+      <h1 class="nav">{i18n.str`Confirm Withdrawal`}</h1>
       <article>
         <div class="challenge-div">
           <form class="challenge-form">
             <div class="pure-form" id="captcha" name="capcha-form">
-              <h2>{i18n`Authorize withdrawal by solving challenge`}</h2>
+              <h2>{i18n.str`Authorize withdrawal by solving challenge`}</h2>
               <p>
                 <label for="answer">
-                  {i18n`What is`}&nbsp;
+                  {i18n.str`What is`}&nbsp;
                   <em>
                     {captchaNumbers.a}&nbsp;+&nbsp;{captchaNumbers.b}
                   </em>
@@ -1514,12 +1517,12 @@ function TalerWithdrawalConfirmationQuestion(Props: 
any): VNode {
                       ...prevState,
 
                       error: {
-                        title: i18n`Answer is wrong.`,
+                        title: i18n.str`Answer is wrong.`,
                       },
                     }));
                   }}
                 >
-                  {i18n`Confirm`}
+                  {i18n.str`Confirm`}
                 </button>
                 &nbsp;
                 <button
@@ -1532,18 +1535,18 @@ function TalerWithdrawalConfirmationQuestion(Props: 
any): VNode {
                     )
                   }
                 >
-                  {i18n`Cancel`}
+                  {i18n.str`Cancel`}
                 </button>
               </p>
             </div>
           </form>
           <div class="hint">
             <p>
-              <Translate>
+              <i18n.Translate>
                 A this point, a <b>real</b> bank would ask for an additional
                 authentication proof (PIN/TAN, one time password, ..), instead
                 of a simple calculation.
-              </Translate>
+              </i18n.Translate>
             </p>
           </div>
         </div>
@@ -1552,40 +1555,6 @@ function TalerWithdrawalConfirmationQuestion(Props: 
any): VNode {
   );
 }
 
-function QrCodeSection({
-  talerWithdrawUri,
-  abortButton,
-}: {
-  talerWithdrawUri: string;
-  abortButton: h.JSX.Element;
-}): VNode {
-  const i18n = useTranslator();
-  useEffect(() => {
-    //Taler Wallet WebExtension is listening to headers response and tab 
updates.
-    //In the SPA there is no header response with the Taler URI so
-    //this hack manually triggers the tab update after the QR is in the DOM.
-    window.location.hash = `/account/${new Date().getTime()}`;
-  }, []);
-
-  return (
-    <section id="main" class="content">
-      <h1 class="nav">{i18n`Transfer to Taler Wallet`}</h1>
-      <article>
-        <div class="qr-div">
-          <p>{i18n`Use this QR code to withdraw to your mobile wallet:`}</p>
-          {QR({ text: talerWithdrawUri })}
-          <p>
-            Click <a id="linkqr" href={talerWithdrawUri}>{i18n`this 
link`}</a>{" "}
-            to open your Taler wallet!
-          </p>
-          <br />
-          {abortButton}
-        </div>
-      </article>
-    </section>
-  );
-}
-
 /**
  * Offer the QR code (and a clickable taler://-link) to
  * permit the passing of exchange and reserve details to
@@ -1595,7 +1564,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
   // turns true when the wallet POSTed the reserve details:
   const [pageState, pageStateSetter] = useContext(PageContext);
   const { withdrawalId, talerWithdrawUri, accountLabel, backendState } = Props;
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   const abortButton = (
     <a
       class="pure-button btn-cancel"
@@ -1609,7 +1578,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
           };
         });
       }}
-    >{i18n`Abort`}</a>
+    >{i18n.str`Abort`}</a>
   );
 
   console.log(`Showing withdraw URI: ${talerWithdrawUri}`);
@@ -1629,7 +1598,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
       ...prevState,
 
       error: {
-        title: i18n`withdrawal (${withdrawalId}) was never (correctly) created 
at the bank...`,
+        title: i18n.str`withdrawal (${withdrawalId}) was never (correctly) 
created at the bank...`,
       },
     }));
     return (
@@ -1643,7 +1612,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
 
   // data didn't arrive yet and wallet didn't communicate:
   if (typeof data === "undefined")
-    return <p>{i18n`Waiting the bank to create the operation...`}</p>;
+    return <p>{i18n.str`Waiting the bank to create the operation...`}</p>;
 
   /**
    * Wallet didn't communicate withdrawal details yet:
@@ -1657,7 +1626,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
         withdrawalInProgress: false,
 
         error: {
-          title: i18n`This withdrawal was aborted!`,
+          title: i18n.str`This withdrawal was aborted!`,
         },
       };
     });
@@ -1680,7 +1649,7 @@ function TalerWithdrawalQRCode(Props: any): VNode {
 function WalletWithdraw(Props: any): VNode {
   const { backendState, pageStateSetter, focus } = Props;
   const currency = useContext(CurrencyContext);
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   let submitAmount = "5.00";
   const amountRegex = "^[0-9]+(.[0-9]+)?$";
 
@@ -1691,7 +1660,8 @@ function WalletWithdraw(Props: any): VNode {
   return (
     <div id="reserve-form" class="pure-form" name="tform">
       <p>
-        <label for="withdraw-amount">{i18n`Amount to withdraw:`}</label>&nbsp;
+        <label for="withdraw-amount">{i18n.str`Amount to withdraw:`}</label>
+        &nbsp;
         <input
           type="number"
           ref={ref}
@@ -1724,7 +1694,7 @@ function WalletWithdraw(Props: any): VNode {
             id="select-exchange"
             class="pure-button pure-button-primary"
             type="submit"
-            value={i18n`Withdraw`}
+            value={i18n.str`Withdraw`}
             onClick={() => {
               submitAmount = validateAmount(submitAmount);
               /**
@@ -1753,7 +1723,7 @@ function WalletWithdraw(Props: any): VNode {
 function PaymentOptions(Props: any): VNode {
   const { backendState, pageStateSetter, focus } = Props;
   const currency = useContext(CurrencyContext);
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
 
   const [tab, setTab] = useState<"charge-wallet" | "wire-transfer">(
     "charge-wallet",
@@ -1769,7 +1739,7 @@ function PaymentOptions(Props: any): VNode {
               setTab("charge-wallet");
             }}
           >
-            {i18n`Obtain digital cash`}
+            {i18n.str`Obtain digital cash`}
           </button>
           <button
             class={tab === "wire-transfer" ? "tablinks active" : "tablinks"}
@@ -1777,12 +1747,12 @@ function PaymentOptions(Props: any): VNode {
               setTab("wire-transfer");
             }}
           >
-            {i18n`Transfer to bank account`}
+            {i18n.str`Transfer to bank account`}
           </button>
         </div>
         {tab === "charge-wallet" && (
           <div id="charge-wallet" class="tabcontent active">
-            <h3>{i18n`Obtain digital cash`}</h3>
+            <h3>{i18n.str`Obtain digital cash`}</h3>
             <WalletWithdraw
               backendState={backendState}
               focus
@@ -1792,7 +1762,7 @@ function PaymentOptions(Props: any): VNode {
         )}
         {tab === "wire-transfer" && (
           <div id="wire-transfer" class="tabcontent active">
-            <h3>{i18n`Transfer to bank account`}</h3>
+            <h3>{i18n.str`Transfer to bank account`}</h3>
             <PaytoWireTransfer
               backendState={backendState}
               focus
@@ -1807,7 +1777,7 @@ function PaymentOptions(Props: any): VNode {
 
 function RegistrationButton(Props: any): VNode {
   const { backendStateSetter, pageStateSetter } = Props;
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   if (bankUiSettings.allowRegistrations)
     return (
       <button
@@ -1816,7 +1786,7 @@ function RegistrationButton(Props: any): VNode {
           route("/register");
         }}
       >
-        {i18n`Register`}
+        {i18n.str`Register`}
       </button>
     );
 
@@ -1834,7 +1804,7 @@ function undefinedIfEmpty<T extends object>(obj: T): T | 
undefined {
 function LoginForm(Props: any): VNode {
   const { backendStateSetter, pageStateSetter } = Props;
   const [submitData, submitDataSetter] = useCredentialsRequestType();
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   const ref = useRef<HTMLInputElement>(null);
   useEffect(() => {
     ref.current?.focus();
@@ -1843,17 +1813,17 @@ function LoginForm(Props: any): VNode {
   const errors = !submitData
     ? undefined
     : undefinedIfEmpty({
-        username: !submitData.username ? i18n`Missing username` : undefined,
-        password: !submitData.password ? i18n`Missing password` : undefined,
+        username: !submitData.username ? i18n.str`Missing username` : 
undefined,
+        password: !submitData.password ? i18n.str`Missing password` : 
undefined,
       });
 
   return (
     <div class="login-div">
       <form action="javascript:void(0);" class="login-form">
         <div class="pure-form">
-          <h2>{i18n`Please login!`}</h2>
+          <h2>{i18n.str`Please login!`}</h2>
           <p class="unameFieldLabel loginFieldLabel formFieldLabel">
-            <label for="username">{i18n`Username:`}</label>
+            <label for="username">{i18n.str`Username:`}</label>
           </p>
           <input
             ref={ref}
@@ -1872,7 +1842,7 @@ function LoginForm(Props: any): VNode {
             }}
           />
           <p class="passFieldLabel loginFieldLabel formFieldLabel">
-            <label for="password">{i18n`Password:`}</label>
+            <label for="password">{i18n.str`Password:`}</label>
           </p>
           <input
             type="password"
@@ -1919,7 +1889,7 @@ function LoginForm(Props: any): VNode {
               });
             }}
           >
-            {i18n`Login`}
+            {i18n.str`Login`}
           </button>
           {RegistrationButton(Props)}
         </div>
@@ -1935,30 +1905,30 @@ function RegistrationForm(Props: any): VNode {
   // eslint-disable-next-line @typescript-eslint/no-unused-vars
   const [pageState, pageStateSetter] = useContext(PageContext);
   const [submitData, submitDataSetter] = useCredentialsRequestType();
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
 
   const errors = !submitData
     ? undefined
     : undefinedIfEmpty({
-        username: !submitData.username ? i18n`Missing username` : undefined,
-        password: !submitData.password ? i18n`Missing password` : undefined,
+        username: !submitData.username ? i18n.str`Missing username` : 
undefined,
+        password: !submitData.password ? i18n.str`Missing password` : 
undefined,
         repeatPassword: !submitData.repeatPassword
-          ? i18n`Missing password`
+          ? i18n.str`Missing password`
           : submitData.repeatPassword !== submitData.password
-          ? i18n`Password don't match`
+          ? i18n.str`Password don't match`
           : undefined,
       });
 
   return (
     <Fragment>
-      <h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1>
+      <h1 class="nav">{i18n.str`Welcome to ${bankUiSettings.bankName}!`}</h1>
       <article>
         <div class="register-div">
           <form action="javascript:void(0);" class="register-form">
             <div class="pure-form">
-              <h2>{i18n`Please register!`}</h2>
+              <h2>{i18n.str`Please register!`}</h2>
               <p class="unameFieldLabel registerFieldLabel formFieldLabel">
-                <label for="register-un">{i18n`Username:`}</label>
+                <label for="register-un">{i18n.str`Username:`}</label>
               </p>
               <input
                 id="register-un"
@@ -1976,7 +1946,7 @@ function RegistrationForm(Props: any): VNode {
               />
               <br />
               <p class="unameFieldLabel registerFieldLabel formFieldLabel">
-                <label for="register-pw">{i18n`Password:`}</label>
+                <label for="register-pw">{i18n.str`Password:`}</label>
               </p>
               <input
                 type="password"
@@ -1993,7 +1963,7 @@ function RegistrationForm(Props: any): VNode {
                 }}
               />
               <p class="unameFieldLabel registerFieldLabel formFieldLabel">
-                <label for="register-repeat">{i18n`Repeat Password:`}</label>
+                <label for="register-repeat">{i18n.str`Repeat 
Password:`}</label>
               </p>
               <input
                 type="password"
@@ -2012,7 +1982,7 @@ function RegistrationForm(Props: any): VNode {
               />
               <br />
               {/*
-              <label for="phone">{i18n`Phone number:`}</label>
+              <label for="phone">{i18n.str`Phone number:`}</label>
               // FIXME: add input validation (must start with +, otherwise 
only numbers)
               <input
                 name="phone"
@@ -2054,7 +2024,7 @@ function RegistrationForm(Props: any): VNode {
                   });
                 }}
               >
-                {i18n`Register`}
+                {i18n.str`Register`}
               </button>
               {/* FIXME: should use a different color */}
               <button
@@ -2068,7 +2038,7 @@ function RegistrationForm(Props: any): VNode {
                   route("/account");
                 }}
               >
-                {i18n`Cancel`}
+                {i18n.str`Cancel`}
               </button>
             </div>
           </form>
@@ -2083,7 +2053,7 @@ function RegistrationForm(Props: any): VNode {
  */
 function Transactions(Props: any): VNode {
   const { pageNumber, accountLabel, balanceValue } = Props;
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   const { data, error, mutate } = useSWR(
     `access-api/accounts/${accountLabel}/transactions?page=${pageNumber}`,
   );
@@ -2114,10 +2084,10 @@ function Transactions(Props: any): VNode {
       <table class="pure-table pure-table-striped">
         <thead>
           <tr>
-            <th>{i18n`Date`}</th>
-            <th>{i18n`Amount`}</th>
-            <th>{i18n`Counterpart`}</th>
-            <th>{i18n`Subject`}</th>
+            <th>{i18n.str`Date`}</th>
+            <th>{i18n.str`Amount`}</th>
+            <th>{i18n.str`Counterpart`}</th>
+            <th>{i18n.str`Subject`}</th>
           </tr>
         </thead>
         <tbody>
@@ -2178,7 +2148,7 @@ function Account(Props: any): VNode {
     talerWithdrawUri,
     timestamp,
   } = pageState;
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   useEffect(() => {
     mutate();
   }, [timestamp]);
@@ -2206,7 +2176,7 @@ function Account(Props: any): VNode {
 
           isLoggedIn: false,
           error: {
-            title: i18n`Username or account label '${accountLabel}' not found. 
 Won't login.`,
+            title: i18n.str`Username or account label '${accountLabel}' not 
found.  Won't login.`,
           },
         }));
 
@@ -2233,7 +2203,7 @@ function Account(Props: any): VNode {
 
           isLoggedIn: false,
           error: {
-            title: i18n`Wrong credentials given.`,
+            title: i18n.str`Wrong credentials given.`,
           },
         }));
         return <p>Wrong credentials...</p>;
@@ -2244,7 +2214,7 @@ function Account(Props: any): VNode {
 
           isLoggedIn: false,
           error: {
-            title: i18n`Account information could not be retrieved.`,
+            title: i18n.str`Account information could not be retrieved.`,
             debug: JSON.stringify(error),
           },
         }));
@@ -2287,14 +2257,14 @@ function Account(Props: any): VNode {
     <BankFrame>
       <div>
         <h1 class="nav welcome-text">
-          <Translate>
+          <i18n.Translate>
             Welcome, {accountLabel} ({getIbanFromPayto(data.paytoUri)})!
-          </Translate>
+          </i18n.Translate>
         </h1>
       </div>
       <section id="assets">
         <div class="asset-summary">
-          <h2>{i18n`Bank account balance`}</h2>
+          <h2>{i18n.str`Bank account balance`}</h2>
           <div class="large-amount amount">
             {data.balance.credit_debit_indicator == "debit" ? <b>-</b> : null}
             <span class="value">{`${balanceValue}`}</span>&nbsp;
@@ -2304,7 +2274,7 @@ function Account(Props: any): VNode {
       </section>
       <section id="payments">
         <div class="payments">
-          <h2>{i18n`Payments`}</h2>
+          <h2>{i18n.str`Payments`}</h2>
           {/* FIXME: turn into button! */}
           <CurrencyContext.Provider value={balance.currency}>
             {Props.children}
@@ -2317,7 +2287,7 @@ function Account(Props: any): VNode {
       </section>
       <section id="main">
         <article>
-          <h2>{i18n`Latest transactions:`}</h2>
+          <h2>{i18n.str`Latest transactions:`}</h2>
           <Transactions
             balanceValue={balanceValue}
             pageNumber="0"
@@ -2379,7 +2349,7 @@ function SWRWithoutCredentials(Props: any): VNode {
 function PublicHistories(Props: any): VNode {
   const [showAccount, setShowAccount] = useShowPublicAccount();
   const { data, error } = useSWR("access-api/public-accounts");
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
 
   if (typeof error !== "undefined") {
     console.log("account error", error);
@@ -2391,7 +2361,7 @@ function PublicHistories(Props: any): VNode {
 
           showPublicHistories: false,
           error: {
-            title: i18n`List of public accounts was not found.`,
+            title: i18n.str`List of public accounts was not found.`,
             debug: JSON.stringify(error),
           },
         }));
@@ -2403,7 +2373,7 @@ function PublicHistories(Props: any): VNode {
 
           showPublicHistories: false,
           error: {
-            title: i18n`List of public accounts could not be retrieved.`,
+            title: i18n.str`List of public accounts could not be retrieved.`,
             debug: JSON.stringify(error),
           },
         }));
@@ -2450,7 +2420,7 @@ function PublicHistories(Props: any): VNode {
 
   return (
     <Fragment>
-      <h1 class="nav">{i18n`History of public accounts`}</h1>
+      <h1 class="nav">{i18n.str`History of public accounts`}</h1>
       <section id="main">
         <article>
           <div class="pure-menu pure-menu-horizontal" name="accountMenu">
@@ -2471,7 +2441,7 @@ function PublicHistories(Props: any): VNode {
 function PublicHistoriesPage(): VNode {
   // const [backendState, backendStateSetter] = useBackendState();
   const [pageState, pageStateSetter] = usePageState();
-  // const i18n = useTranslator();
+  // const { i18n } = useTranslationContext();
   return (
     <SWRWithoutCredentials baseUrl={getBankBackendBaseUrl()}>
       <PageContext.Provider value={[pageState, pageStateSetter]}>
@@ -2499,12 +2469,12 @@ function PublicHistoriesPage(): VNode {
 function RegistrationPage(): VNode {
   const [backendState, backendStateSetter] = useBackendState();
   const [pageState, pageStateSetter] = usePageState();
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
   if (!bankUiSettings.allowRegistrations) {
     return (
       <PageContext.Provider value={[pageState, pageStateSetter]}>
         <BankFrame>
-          <p>{i18n`Currently, the bank is not accepting new 
registrations!`}</p>
+          <p>{i18n.str`Currently, the bank is not accepting new 
registrations!`}</p>
         </BankFrame>
       </PageContext.Provider>
     );
@@ -2521,13 +2491,13 @@ function RegistrationPage(): VNode {
 function AccountPage(): VNode {
   const [backendState, backendStateSetter] = useBackendState();
   const [pageState, pageStateSetter] = usePageState();
-  const i18n = useTranslator();
+  const { i18n } = useTranslationContext();
 
   if (!pageState.isLoggedIn) {
     return (
       <PageContext.Provider value={[pageState, pageStateSetter]}>
         <BankFrame>
-          <h1 class="nav">{i18n`Welcome to ${bankUiSettings.bankName}!`}</h1>
+          <h1 class="nav">{i18n.str`Welcome to 
${bankUiSettings.bankName}!`}</h1>
           <LoginForm
             pageStateSetter={pageStateSetter}
             backendStateSetter={backendStateSetter}
@@ -2543,7 +2513,7 @@ function AccountPage(): VNode {
 
       isLoggedIn: false,
       error: {
-        title: i18n`Page has a problem: logged in but backend state is lost.`,
+        title: i18n.str`Page has a problem: logged in but backend state is 
lost.`,
       },
     }));
     return <p>Error: waiting for details...</p>;
diff --git a/packages/demobank-ui/src/scss/main.scss 
b/packages/demobank-ui/src/scss/main.scss
index ebe36b9b4..b92260af0 100644
--- a/packages/demobank-ui/src/scss/main.scss
+++ b/packages/demobank-ui/src/scss/main.scss
@@ -1,4 +1,4 @@
-@import "pure";
-@import "bank";
-@import "demo";
-@import "colors-bank";
+@use "pure";
+@use "bank";
+@use "demo";
+@use "colors-bank";
diff --git a/packages/demobank-ui/src/stories.tsx 
b/packages/demobank-ui/src/stories.tsx
new file mode 100644
index 000000000..52d42577d
--- /dev/null
+++ b/packages/demobank-ui/src/stories.tsx
@@ -0,0 +1,46 @@
+/*
+ This file is part of GNU Anastasis
+ (C) 2021-2022 Anastasis SARL
+
+ GNU Anastasis is free software; you can redistribute it and/or modify it 
under the
+ terms of the GNU Affero General Public License as published by the Free 
Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Anastasis 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 Affero General Public License for more 
details.
+
+ You should have received a copy of the GNU Affero General Public License 
along with
+ GNU Anastasis; see the file COPYING.  If not, see 
<http://www.gnu.org/licenses/>
+ */
+
+/**
+ *
+ * @author Sebastian Javier Marchano (sebasjm)
+ */
+import { strings } from "./i18n/strings.js";
+
+import * as pages from "./pages/home/index.stories.js";
+
+import { renderStories } from "@gnu-taler/web-util/lib/index.browser";
+
+import "./scss/main.scss";
+
+function SortStories(a: any, b: any): number {
+  return (a?.order ?? 0) - (b?.order ?? 0);
+}
+
+function main(): void {
+  renderStories(
+    { pages },
+    {
+      strings,
+    },
+  );
+}
+
+if (document.readyState === "loading") {
+  document.addEventListener("DOMContentLoaded", main);
+} else {
+  main();
+}
diff --git a/packages/demobank-ui/static/index.html 
b/packages/demobank-ui/static/index.html
deleted file mode 100644
index 0fa5215d3..000000000
--- a/packages/demobank-ui/static/index.html
+++ /dev/null
@@ -1,15 +0,0 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
-    <title>Demobank</title>
-    <!-- Optional customization script.  -->
-    <script src="demobank-ui-settings.js"></script>
-    <!-- Entry point for the demobank SPA. -->
-    <script type="module" src="index.js"></script>
-    <link rel="stylesheet" href="index.css" />
-  </head>
-  <body>
-    <div id="app"></div>
-  </body>
-</html>

-- 
To stop receiving notification emails like this one, please contact
gnunet@gnunet.org.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]