gnunet-svn
[Top][All Lists]
Advanced

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

[taler-merchant-backoffice] branch master updated (dd2bfd6 -> ff30289)


From: gnunet
Subject: [taler-merchant-backoffice] branch master updated (dd2bfd6 -> ff30289)
Date: Mon, 22 Feb 2021 23:09:12 +0100

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

sebasjm pushed a change to branch master
in repository merchant-backoffice.

    from dd2bfd6  updated changelog
     new 59e4d7e  refactor router
     new a4a3aa5  login with message and instanceContext
     new ff30289  refactor i18n to support weblate

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 CHANGELOG.md                                       |   5 +-
 packages/frontend/.storybook/main.js               |  23 +-
 packages/frontend/.storybook/preview.js            |  10 +-
 packages/frontend/package.json                     |   5 +-
 packages/frontend/preact.config.js                 |  22 +-
 packages/frontend/src/components/auth/index.tsx    |  45 ++--
 packages/frontend/src/components/footer/index.tsx  |  49 ----
 packages/frontend/src/components/modal/index.tsx   |   8 +-
 packages/frontend/src/components/navbar/index.tsx  |  18 +-
 .../notifications/Notifications.stories.tsx        |  62 ++---
 .../src/components/notifications/index.tsx         |  22 +-
 packages/frontend/src/components/yup/YupField.tsx  |  96 ++++---
 packages/frontend/src/context/backend.ts           |  41 ++-
 packages/frontend/src/custom.d.ts                  |   8 +
 packages/frontend/src/declaration.d.ts             |  16 +-
 packages/frontend/src/hooks/backend.ts             | 130 ++++++----
 packages/frontend/src/hooks/index.ts               |  51 ++--
 packages/frontend/src/hooks/notifications.ts       |  24 +-
 packages/frontend/src/i18n/index.ts                | 279 ---------------------
 packages/frontend/src/index.tsx                    | 250 +++++++++++++++---
 packages/frontend/src/messages/en.po               | 155 ++++++++++++
 packages/frontend/src/messages/es.po               |  22 ++
 packages/frontend/src/messages/index.ts            |   3 +
 .../instances/{ => create}/Create.stories.tsx      |   0
 .../routes/instances/{ => create}/CreatePage.tsx   |  24 +-
 .../frontend/src/routes/instances/create/index.tsx |  21 ++
 .../{UpdatePage.tsx => details/DetailPage.tsx}     |  42 ++--
 .../src/routes/instances/details/index.tsx         |  53 ++++
 packages/frontend/src/routes/instances/index.tsx   |  97 -------
 .../src/routes/instances/{ => list}/CardTable.tsx  |  23 +-
 .../routes/instances/{ => list}/DeleteModal.tsx    |   9 +-
 .../src/routes/instances/{ => list}/EmptyTable.tsx |   5 +-
 .../src/routes/instances/{ => list}/Table.tsx      |  21 +-
 .../routes/instances/{ => list}/View.stories.tsx   |   0
 .../src/routes/instances/{ => list}/View.tsx       |  40 +--
 .../frontend/src/routes/instances/list/index.tsx   |  71 ++++++
 .../routes/instances/{ => update}/UpdatePage.tsx   |  30 +--
 .../frontend/src/routes/instances/update/index.tsx |  36 +++
 packages/frontend/src/routes/login/index.tsx       |  11 +
 packages/frontend/src/schemas/index.ts             |  32 +--
 packages/frontend/src/scss/main.scss               |  11 +-
 packages/frontend/tests/header.test.tsx            |  11 +-
 packages/frontend/tests/hooks/notification.test.ts |   4 +-
 packages/preact-message/.gitignore                 |   2 +
 packages/preact-message/CHANGELOG.md               |  16 ++
 packages/preact-message/LICENSE                    |  20 ++
 packages/preact-message/README.md                  | 126 ++++++++++
 packages/preact-message/package.json               |  39 +++
 packages/preact-message/src/MessageProvider.ts     | 183 ++++++++++++++
 packages/preact-message/src/declarations.d.ts      |   3 +
 packages/preact-message/src/get-message.ts         |  87 +++++++
 packages/preact-message/src/index.ts               |  32 +++
 packages/preact-message/src/message-context.ts     |  72 ++++++
 packages/preact-message/src/message-error.ts       |  22 ++
 packages/preact-message/src/message.ts             |  89 +++++++
 packages/preact-message/src/use-locales.ts         |  44 ++++
 packages/preact-message/src/use-message-getter.ts  |  47 ++++
 .../preact-message/src/use-message-template.ts     |  31 +++
 packages/preact-message/src/use-message.ts         |  58 +++++
 packages/preact-message/tsconfig.json              |  60 +++++
 pnpm-lock.yaml                                     | 198 +++++++++------
 61 files changed, 2113 insertions(+), 901 deletions(-)
 delete mode 100644 packages/frontend/src/components/footer/index.tsx
 create mode 100644 packages/frontend/src/custom.d.ts
 delete mode 100644 packages/frontend/src/i18n/index.ts
 create mode 100644 packages/frontend/src/messages/en.po
 create mode 100644 packages/frontend/src/messages/es.po
 create mode 100644 packages/frontend/src/messages/index.ts
 rename packages/frontend/src/routes/instances/{ => create}/Create.stories.tsx 
(100%)
 rename packages/frontend/src/routes/instances/{ => create}/CreatePage.tsx (84%)
 create mode 100644 packages/frontend/src/routes/instances/create/index.tsx
 copy packages/frontend/src/routes/instances/{UpdatePage.tsx => 
details/DetailPage.tsx} (66%)
 create mode 100644 packages/frontend/src/routes/instances/details/index.tsx
 delete mode 100644 packages/frontend/src/routes/instances/index.tsx
 rename packages/frontend/src/routes/instances/{ => list}/CardTable.tsx (80%)
 rename packages/frontend/src/routes/instances/{ => list}/DeleteModal.tsx (76%)
 rename packages/frontend/src/routes/instances/{ => list}/EmptyTable.tsx (84%)
 rename packages/frontend/src/routes/instances/{ => list}/Table.tsx (82%)
 rename packages/frontend/src/routes/instances/{ => list}/View.stories.tsx 
(100%)
 rename packages/frontend/src/routes/instances/{ => list}/View.tsx (58%)
 create mode 100644 packages/frontend/src/routes/instances/list/index.tsx
 rename packages/frontend/src/routes/instances/{ => update}/UpdatePage.tsx (80%)
 create mode 100644 packages/frontend/src/routes/instances/update/index.tsx
 create mode 100644 packages/frontend/src/routes/login/index.tsx
 create mode 100644 packages/preact-message/.gitignore
 create mode 100644 packages/preact-message/CHANGELOG.md
 create mode 100644 packages/preact-message/LICENSE
 create mode 100644 packages/preact-message/README.md
 create mode 100644 packages/preact-message/package.json
 create mode 100644 packages/preact-message/src/MessageProvider.ts
 create mode 100644 packages/preact-message/src/declarations.d.ts
 create mode 100644 packages/preact-message/src/get-message.ts
 create mode 100644 packages/preact-message/src/index.ts
 create mode 100644 packages/preact-message/src/message-context.ts
 create mode 100644 packages/preact-message/src/message-error.ts
 create mode 100644 packages/preact-message/src/message.ts
 create mode 100644 packages/preact-message/src/use-locales.ts
 create mode 100644 packages/preact-message/src/use-message-getter.ts
 create mode 100644 packages/preact-message/src/use-message-template.ts
 create mode 100644 packages/preact-message/src/use-message.ts
 create mode 100644 packages/preact-message/tsconfig.json

diff --git a/CHANGELOG.md b/CHANGELOG.md
index e093a1f..9aedc98 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -27,14 +27,11 @@ and this project adheres to [Semantic 
Versioning](https://semver.org/spec/v2.0.0
  - remove checkbox from auth token, use button (manage auth)
  - prepend payto:// to account
  - validate on change everything
- - remove footer
+ <!-- - remove footer -->
 
  - implement proper error handling
- - PATCH payto uri not working as expeced: re-enable, creating with multiple 
uris
  - replace Yup and type definition with a taler-library for the purpose (first 
wait Florian to refactor wallet core)
  - add more doc style comments 
- - check the field names in forms dont break spaces
- - update spanish lang
  - save every auth token of different instances
  - configure eslint
  - configure prettier
diff --git a/packages/frontend/.storybook/main.js 
b/packages/frontend/.storybook/main.js
index 4b3f58f..7dc5cc2 100644
--- a/packages/frontend/.storybook/main.js
+++ b/packages/frontend/.storybook/main.js
@@ -29,5 +29,26 @@ module.exports = {
     "@storybook/preset-scss",
     // "@storybook/addon-a11y",
     "@storybook/addon-essentials" //docs, control, actions, viewpot, toolbar, 
background
-  ]
+  ],
+  webpackFinal: async (config, { configType }) => {
+    // `configType` has a value of 'DEVELOPMENT' or 'PRODUCTION'
+    // You can change the configuration based on that.
+    // 'PRODUCTION' is used when building the static version of storybook.
+
+    // Make whatever fine-grained changes you need
+    config.module.rules.push({
+      test: [/\.pot?$/, /\.mo$/],
+      loader: require.resolve('messageformat-po-loader'),
+      options: {
+        biDiSupport: false,
+        defaultCharset: null,
+        defaultLocale: 'en',
+        forceContext: false,
+        pluralFunction: null,
+        verbose: false
+      }
+    });
+    // Return the altered config
+    return config;
+  },
 }
\ No newline at end of file
diff --git a/packages/frontend/.storybook/preview.js 
b/packages/frontend/.storybook/preview.js
index 2c732ec..48c87c7 100644
--- a/packages/frontend/.storybook/preview.js
+++ b/packages/frontend/.storybook/preview.js
@@ -1,8 +1,8 @@
 import "../src/scss/main.scss"
-import { IntlProvider } from 'preact-i18n';
-import { h } from "preact";
-import { translations } from '../src/i18n'
+import { MessageProvider } from "preact-messages";
 import { ConfigContext } from '../src/context/backend'
+import * as messages from '../src/messages'
+import { h } from 'preact';
 
 const mockConfig = {
   backendURL: 'http://demo.taler.net',
@@ -31,9 +31,9 @@ export const globalTypes = {
 
 export const decorators = [
   (Story, { globals }) => {
-    return <IntlProvider definition={translations[globals.locale]} mark>
+    return <MessageProvider locale={globals.locale} onError="warn" 
messages={messages[globals.locale]} >
       <Story />
-    </IntlProvider>
+    </MessageProvider>
   },
   (Story) => <ConfigContext.Provider value={mockConfig}> <Story /> 
</ConfigContext.Provider>
 ];
diff --git a/packages/frontend/package.json b/packages/frontend/package.json
index 78ed6ee..07a7191 100644
--- a/packages/frontend/package.json
+++ b/packages/frontend/package.json
@@ -31,9 +31,10 @@
   "dependencies": {
     "axios": "^0.21.1",
     "date-fns": "^2.17.0",
+    "messageformat": "^2.3.0",
     "preact": "^10.3.1",
-    "preact-i18n": "2.3.1-preactx",
     "preact-router": "^3.2.1",
+    "preact-messages": "workspace:*",
     "swr": "^0.4.1",
     "yup": "^0.32.8"
   },
@@ -51,7 +52,6 @@
     "@testing-library/preact-hooks": "^1.1.0",
     "@types/enzyme": "^3.10.5",
     "@types/jest": "^26.0.8",
-    "@types/preact-i18n": "^2.3.0",
     "@typescript-eslint/eslint-plugin": "^4.15.1",
     "@typescript-eslint/parser": "^4.15.1",
     "ava": "^3.15.0",
@@ -69,6 +69,7 @@
     "eslint-config-preact": "^1.1.1",
     "jest": "^26.2.2",
     "jest-preset-preact": "^4.0.2",
+    "messageformat-po-loader": "^0.3.0",
     "node-sass": "^5.0.0",
     "preact-cli": "^3.0.5",
     "preact-render-to-string": "^5.1.4",
diff --git a/packages/frontend/preact.config.js 
b/packages/frontend/preact.config.js
index de2debe..5e42bc6 100644
--- a/packages/frontend/preact.config.js
+++ b/packages/frontend/preact.config.js
@@ -26,10 +26,22 @@
 export default {
     webpack(config, env, helpers, options) {
         config.node.process = 'mock'
-        // config.plugins.push(
-        // new DefinePlugin({
-        //     // 'process.env.BACKEND_ENDPOINT': 
JSON.stringify(parsed['BACKEND_ENDPOINT']),
-        // }),
-        // );
+        config.resolve.extensions.push('.po');
+        config.module.rules.push({
+            enforce: 'pre',
+            test: /\.po$/,
+            use: [{
+                loader: 'messageformat-po-loader',
+                options: {
+                    biDiSupport: false,
+                    defaultCharset: null,
+                    defaultLocale: 'en',
+                    forceContext: false,
+                    pluralFunction: null,
+                    verbose: false
+                }
+            }],
+        });
+          
     }
 }
diff --git a/packages/frontend/src/components/auth/index.tsx 
b/packages/frontend/src/components/auth/index.tsx
index 6ec69c0..2688586 100644
--- a/packages/frontend/src/components/auth/index.tsx
+++ b/packages/frontend/src/components/auth/index.tsx
@@ -20,40 +20,54 @@
 */
 
 import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { useBackend } from "../../hooks";
+import { Message } from "preact-messages";
+import { useContext, useState } from "preact/hooks";
+import { BackendContext } from "../../context/backend";
+import { Notification } from "../../declaration";
 
 interface Props {
-  onConfirm?: () => void;
+  withMessage?: Notification;
+  onConfirm: (backend: string, token?: string) => void;
 }
 
-export function LoginPage({ onConfirm }: Props): VNode {
-  const [backend, setBackend] = useBackend()
-  const [url, setUrl] = useState(backend.url)
-  const [token, setToken] = useState(backend.token)
-  const [changeToken, setChangeToken] = useState(false)
+export function LoginModal({ onConfirm, withMessage }: Props): VNode {
+  const backend = useContext(BackendContext)
+  const [updatingToken, setUpdatingToken] = useState(false)
+  const [token, setToken] = useState(backend.token || '')
+  const [url, setURL] = useState(backend.url)
+  const toggleUpdatingToken = (): void => setUpdatingToken(v => !v)
 
   return <div class="modal is-active is-clipped">
     <div class="modal-background" />
     <div class="modal-card">
+      {withMessage && <div class={withMessage.type === 'ERROR' ? "notification 
is-danger" : "notification is-info"}>
+        <div class="columns is-vcentered">
+          <div class="column is-12">
+              <div>
+                <p>{withMessage.message}</p>
+                {withMessage.description}
+              </div>
+            </div>
+        </div>
+      </div>}
       <header class="modal-card-head">
-        <p class="modal-card-title">Authentication required</p>
+        <p class="modal-card-title">Login required</p>
       </header>
       <section class="modal-card-body">
         Please enter your auth token. Token should have "secret-token:" and 
start with Bearer or ApiKey
         <div class="field is-horizontal">
           <div class="field-label is-normal">
-            <label class="label">Change Token</label>
+            <label class="label">Updte token</label>
           </div>
           <div class="field-body">
             <div class="field has-addons">
               <label class="b-checkbox checkbox">
-                <input type="checkbox" checked={changeToken} onClick={(): void 
=> setChangeToken(!changeToken)} />
+                <input type="checkbox" checked={updatingToken} 
onClick={toggleUpdatingToken} />
                 <span class="check" />
               </label>
 
               <p class="control is-expanded">
-                <input class="input" type="text" placeholder={changeToken ? 
"set new token" : "hidden token value"} disabled={!changeToken} name="id" 
value={token} onInput={(e): void => setToken(e?.currentTarget.value)} />
+                <input class="input" type="text" placeholder={updatingToken ? 
"set new token" : "hidden token value"} disabled={!updatingToken} name="id" 
value={token} onInput={(e): void => setToken(e?.currentTarget.value)} />
               </p>
             </div>
           </div>
@@ -65,16 +79,15 @@ export function LoginPage({ onConfirm }: Props): VNode {
           <div class="field-body">
             <div class="field">
               <p class="control is-expanded">
-                <input class="input" type="text" placeholder="set new url" 
name="id" value={url} onInput={(e): void => setUrl(e?.currentTarget.value)} />
+                <input class="input" type="text" placeholder="set new url" 
name="id" value={url} onInput={(e): void => setURL(e?.currentTarget.value)} />
               </p>
             </div>
           </div>
         </div>
       </section>
-      <footer class="modal-card-foot">
+      <footer class="modal-card-foot " style={{ justifyContent: 'flex-end' }}>
         <button class="button is-info" onClick={(): void => {
-          setBackend({ token, url });
-          onConfirm && onConfirm();
+          onConfirm(url, updatingToken && token ? token : undefined);
         }} >Confirm</button>
       </footer>
     </div>
diff --git a/packages/frontend/src/components/footer/index.tsx 
b/packages/frontend/src/components/footer/index.tsx
deleted file mode 100644
index 6a31e88..0000000
--- a/packages/frontend/src/components/footer/index.tsx
+++ /dev/null
@@ -1,49 +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/>
- */
-
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-import { h, VNode } from 'preact';
-
-export function Footer(): VNode {
-  return (
-    <footer class="footer">
-      <div class="container-fluid">
-        <div class="level">
-          <div class="level-left">
-            <div class="level-item">copyleft</div>
-            <div class="level-item">
-              <a href="https://taler.net/"; style="height: 20px">
-                Taler
-              </a>
-            </div>
-          </div>
-          <div class="level-right">
-            <div class="level-item">
-              <div class="logo">
-                sebasjm
-              </div>
-            </div>
-          </div>
-        </div>
-      </div>
-    </footer>
-  )
-}
-
diff --git a/packages/frontend/src/components/modal/index.tsx 
b/packages/frontend/src/components/modal/index.tsx
index ec6d681..6ba0101 100644
--- a/packages/frontend/src/components/modal/index.tsx
+++ b/packages/frontend/src/components/modal/index.tsx
@@ -20,7 +20,7 @@
  */
 
 import { h, VNode } from "preact";
-import { Text } from "preact-i18n";
+import { Message } from "preact-messages";
 
 interface Props {
   active?: boolean;
@@ -36,15 +36,15 @@ export function ConfirmModal({ active, description, 
onCancel, onConfirm, childre
     <div class="modal-background " onClick={onCancel} />
     <div class="modal-card">
       <header class="modal-card-head">
-        <p class="modal-card-title"> <Text id="confirm_modal.title" /> { 
!description ? null : <Text id={`confirm_modal.${description}`} /> }</p>
+        <p class="modal-card-title"> <Message id="confirm_modal.title" /> { 
!description ? null : <Message id={`confirm_modal.${description}`} /> }</p>
         <button class="delete " aria-label="close" onClick={onCancel} />
       </header>
       <section class="modal-card-body">
         {children}
       </section>
       <footer class="modal-card-foot">
-        <button class="button " onClick={onCancel} ><Text id="cancel" 
/></button>
-        <button class={danger ? "button is-danger " : "button is-info "} 
onClick={onConfirm} ><Text id="confirm" /></button>
+        <button class="button " onClick={onCancel} ><Message id="Cancel" 
/></button>
+        <button class={danger ? "button is-danger " : "button is-info "} 
onClick={onConfirm} ><Message id="Confirm" /></button>
       </footer>
     </div>
     <button class="modal-close is-large " aria-label="close" 
onClick={onCancel} />
diff --git a/packages/frontend/src/components/navbar/index.tsx 
b/packages/frontend/src/components/navbar/index.tsx
index 626f471..08ac6d0 100644
--- a/packages/frontend/src/components/navbar/index.tsx
+++ b/packages/frontend/src/components/navbar/index.tsx
@@ -20,23 +20,20 @@
 */
 
 import { h, VNode } from 'preact';
-import { useState } from 'preact/hooks';
-import { LoginPage } from '../auth';
-import { translations } from '../../i18n'
-// TODO: Fix compilation problem
-// import * as logo from '../../assets/logo.jpeg';
+import * as messages from '../../messages'
+import logo from '../../assets/logo.jpeg';
 
 interface Props {
   lang: string;
   setLang: (l: string) => void;
+  onLogout: () => void;
 }
 
-export function NavigationBar({ lang, setLang }: Props): VNode {
-  const [showLogin, setShowLogin] = useState(false)
+export function NavigationBar({ lang, setLang, onLogout }: Props): VNode {
   return (<nav class="navbar is-fixed-top" role="navigation" aria-label="main 
navigation">
     <div class="navbar-brand">
       <a class="navbar-item" href="https://taler.net";>
-        <img src="https://taler.net/static/images/logo-2020.jpg"; style={{ 
height: 50, maxHeight: 50 }} />
+        <img src={logo} style={{ height: 50, maxHeight: 50 }} />
       </a>
 
       <a role="button" class="navbar-burger" aria-label="menu" 
aria-expanded="false" data-target="navbarBasicExample">
@@ -53,7 +50,7 @@ export function NavigationBar({ lang, setLang }: Props): 
VNode {
           <div class="control has-icons-left">
             <div class="select">
               <select onChange={(e): void => setLang(e.currentTarget.value)}>
-                {Object.keys(translations).map(l => <option selected={lang === 
l} value={l}>{l}</option>)}
+                {Object.keys(messages).map(l => <option selected={lang === l} 
value={l}>{l}</option>)}
               </select>
             </div>
             <div class="icon is-small is-left">
@@ -62,11 +59,10 @@ export function NavigationBar({ lang, setLang }: Props): 
VNode {
           </div>
         </div>
         <div class="navbar-item">
-          <button class="button is-primary" onClick={(): void => 
setShowLogin(true)}>Change access</button>
+          <button class="button is-primary" onClick={(): void => 
onLogout()}>Log out</button>
         </div>
       </div>
     </div>
-    {showLogin && <LoginPage onConfirm={(): void => setShowLogin(false)} />}
   </nav>
   );
 }
\ No newline at end of file
diff --git 
a/packages/frontend/src/components/notifications/Notifications.stories.tsx 
b/packages/frontend/src/components/notifications/Notifications.stories.tsx
index 88549fe..17043bf 100644
--- a/packages/frontend/src/components/notifications/Notifications.stories.tsx
+++ b/packages/frontend/src/components/notifications/Notifications.stories.tsx
@@ -26,40 +26,32 @@ import { Notifications } from './index'
 export default {
   title: 'Components/Notification',
   component: Notifications,
+  argTypes: {
+    removeNotification: { action: 'removeNotification' },
+  },
 };
 
-export const NotificationInfo = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'INFO',
-    }]} />
-  </div>
-};
-
-export const NotificationWarn = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'WARN',
-    }]} />
-  </div>
-};
-
-export const NotificationError = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'ERROR',
-    }]} />
-  </div>
-};
-
-export const NotificationSuccess = (): VNode => {
-  return <div>
-    <Notifications notifications={[{
-      messageId: 'unauthorized',
-      type: 'SUCCESS',
-    }]} />
-  </div>
-};
+export const Info = (a: any) => <Notifications {...a} />;
+Info.args = {
+  notifications: [{
+    message: 'Title',
+    description: 'Some large description',
+    type: 'INFO',
+  }]
+}
+export const Warn = (a: any) => <Notifications {...a} />;
+Warn.args = {
+  notifications: [{
+    message: 'Title',
+    description: 'Some large description',
+    type: 'WARN',
+  }]
+}
+export const Error = (a: any) => <Notifications {...a} />;
+Error.args = {
+  notifications: [{
+    message: 'Title',
+    description: 'Some large description',
+    type: 'ERROR',
+  }]
+}
diff --git a/packages/frontend/src/components/notifications/index.tsx 
b/packages/frontend/src/components/notifications/index.tsx
index 7c32ed2..045b8e0 100644
--- a/packages/frontend/src/components/notifications/index.tsx
+++ b/packages/frontend/src/components/notifications/index.tsx
@@ -14,17 +14,18 @@
  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 { h, VNode } from "preact";
-import { Text } from "preact-i18n";
+import { Message } from "preact-messages";
 import { MessageType, Notification } from "../../declaration";
 
 interface Props {
   notifications: Notification[];
+  removeNotification?: (n: Notification) => void;
 }
 
 function messageStyle(type: MessageType): string {
@@ -37,15 +38,16 @@ function messageStyle(type: MessageType): string {
   }
 }
 
-export function Notifications({ notifications }: Props): VNode {
+export function Notifications({ notifications, removeNotification }: Props): 
VNode {
   return <div class="toast">
     {notifications.map(n => <article class={messageStyle(n.type)}>
       <div class="message-header">
-        <p><Text id={`notification.${n.messageId}.title`} /> </p>
-      </div>
-      <div class="message-body">
-        <Text id={`notification.${n.messageId}.description`} fields={n.params} 
/>
+        <p>{n.message}</p>
+        <button class="delete" onClick={() => removeNotification && 
removeNotification(n)} />
       </div>
+      {n.description && <div class="message-body">
+        {n.description}
+      </div>}
     </article>)}
   </div>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/components/yup/YupField.tsx 
b/packages/frontend/src/components/yup/YupField.tsx
index 5bbcf33..49a8e30 100644
--- a/packages/frontend/src/components/yup/YupField.tsx
+++ b/packages/frontend/src/components/yup/YupField.tsx
@@ -20,11 +20,13 @@
 */
 
 import { h, VNode } from "preact";
-import { Text, useText } from "preact-i18n";
+import { Message, useMessage } from "preact-messages";
 import { StateUpdater, useContext, useState } from "preact/hooks";
 import { intervalToDuration, formatDuration } from 'date-fns'
+import { BackendContext, ConfigContext } from '../../context/backend';
 
-function readableDuration(duration: number): string {
+function readableDuration(duration?: number): string {
+  if (!duration) return ""
   return formatDuration(intervalToDuration({ start: 0, end: duration }))
 }
 
@@ -55,8 +57,6 @@ interface Props {
   valueHandler: StateUpdater<any>;
   info: any;
 }
-import { ConfigContext } from '../../context/backend';
-
 export function YupField({ name, field, errors, object, valueHandler, info }: 
Props): VNode {
   const updateField = (f: string) => (v: string): void => valueHandler((prev: 
any) => ({ ...prev, [f]: v }))
   const values = {
@@ -65,24 +65,27 @@ export function YupField({ name, field, errors, object, 
valueHandler, info }: Pr
     value: object && object[field],
     onChange: updateField(field)
   }
+  const backend = useContext(BackendContext)
   const config = useContext(ConfigContext)
 
   switch (info.meta?.type) {
-    case 'group': return <YupObjectInput name={name}
-      info={info} errors={errors}
-      value={object && object[field]}
-      onChange={(updater: any): void => valueHandler((prev: any) => ({ 
...prev, [field]: updater(prev[field]) }))}
-    />
+    case 'group': {
+      return <YupObjectInput name={name}
+        info={info} errors={errors}
+        value={object && object[field]}
+        onChange={(updater: any): void => valueHandler((prev: any) => ({ 
...prev, [field]: updater(prev[field]) }))}
+      />
+    }
     case 'array': return <YupInputArray {...values} />;
     case 'amount': {
       if (config.currency) {
         return <YupInputWithAddon {...values} addon={config.currency} 
onChange={(v: string): void => values.onChange(`${config.currency}:${v}`)} 
value={values.value?.split(':')[1]} />
-      } 
-      return <YupInput {...values} />;      
+      }
+      return <YupInput {...values} />;
     }
-    case 'url': return <YupInputWithAddon {...values} 
addon={`${config.backendURL}/private/instances/`} />;
+    case 'url': return <YupInputWithAddon {...values} 
addon={`${backend.url}/private/instances/`} />;
     case 'secured': return <YupInputSecured {...values} />;
-    case 'duration': return <YupInputWithAddon 
addon={readableDuration(values.value?.d_ms)} atTheEnd {...values} 
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void => 
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
+    case 'duration': return <YupInputWithAddon {...values} 
addon={readableDuration(values.value?.d_ms)} atTheEnd 
value={`${values.value?.d_ms / 1000 || ''}`} onChange={(v: string): void => 
values.onChange({ d_ms: (parseInt(v, 10) * 1000) || undefined } as any)} />;
     default: return <YupInput {...values} />;
 
   }
@@ -93,7 +96,7 @@ function YupObjectInput({ name, info, value, errors, onChange 
}: PropsObject): V
   return <div class="card">
     <header class="card-header">
       <p class="card-header-title">
-        <Text id={`fields.instance.${name}.label`} />
+        <Message id={`fields.instance.${name}.label`} />
       </p>
       <button class="card-header-icon" aria-label="more options" onClick={(): 
void => setActive(!active)}>
         <span class="icon">
@@ -115,16 +118,14 @@ function YupObjectInput({ name, info, value, errors, 
onChange }: PropsObject): V
 }
 
 function YupInput({ name, readonly, value, errors, onChange }: 
PropsInputInternal): VNode {
-  const dict = useText({
-    placeholder: `fields.instance.${name}.placeholder`,
-    tooltip: `fields.instance.${name}.tooltip`,
-  })
+  const placeholder = useMessage(`fields.instance.${name}.placeholder`)
+  const tooltip = useMessage(`fields.instance.${name}.tooltip`)
 
   return <div class="field is-horizontal">
     <div class="field-label is-normal">
       <label class="label">
-        <Text id={`fields.instance.${name}.label`} />
-        {dict.tooltip && <span class="icon" data-tooltip={dict.tooltip}>
+        <Message id={`fields.instance.${name}.label`} />
+        {tooltip && <span class="icon" data-tooltip={tooltip}>
           <i class="mdi mdi-information" />
         </span>}
       </label>
@@ -133,13 +134,13 @@ function YupInput({ name, readonly, value, errors, 
onChange }: PropsInputInterna
       <div class="field">
         <p class="control">
           <input class={errors[name] ? "input is-danger" : "input"} type="text"
-            placeholder={dict.placeholder} readonly={readonly}
+            placeholder={placeholder} readonly={readonly}
             name={name} value={value}
             onChange={(e): void => onChange(e.currentTarget.value)} />
-          <Text id={`fields.instance.${name}.help`} />
+          <Message id={`fields.instance.${name}.help`} > </Message>
         </p>
         {errors[name] ? <p class="help is-danger">
-          <Text id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Text>
+          <Message id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message} </Message>
         </p> : null}
       </div>
     </div>
@@ -147,18 +148,17 @@ function YupInput({ name, readonly, value, errors, 
onChange }: PropsInputInterna
 }
 
 function YupInputArray({ name, readonly, value, errors, onChange }: 
PropsInputInternal): VNode {
-  const dict = useText({
-    placeholder: `fields.instance.${name}.placeholder`,
-    tooltip: `fields.instance.${name}.tooltip`,
-  })
+  const placeholder = useMessage(`fields.instance.${name}.placeholder`)
+  const tooltip = useMessage(`fields.instance.${name}.tooltip`)
+
   const array = value as unknown as string[] || []
   const [currentValue, setCurrentValue] = useState('')
 
   return <div class="field is-horizontal">
     <div class="field-label is-normal">
       <label class="label">
-        <Text id={`fields.instance.${name}.label`} />
-        {dict.tooltip && <span class="icon" data-tooltip={dict.tooltip}>
+        <Message id={`fields.instance.${name}.label`} />
+        {tooltip && <span class="icon" data-tooltip={tooltip}>
           <i class="mdi mdi-information" />
         </span>}
       </label>
@@ -174,14 +174,14 @@ function YupInputArray({ name, readonly, value, errors, 
onChange }: PropsInputIn
           </p>
           <p class="control">
             <input class={errors[name] ? "input is-danger" : "input"} 
type="text"
-              placeholder={dict.placeholder} readonly={readonly}
+              placeholder={placeholder} readonly={readonly}
               name={name} value={currentValue}
               onChange={(e): void => setCurrentValue(e.currentTarget.value)} />
-            <Text id={`fields.instance.${name}.help`} />
+            <Message id={`fields.instance.${name}.help`} > </Message>
           </p>
         </div>
         {errors[name] ? <p class="help is-danger">
-          <Text id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Text>
+          <Message id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Message>
         </p> : null}
         {array.map(v => <div class="tags has-addons">
           <span class="tag is-medium is-info">{v}</span>
@@ -198,16 +198,14 @@ function YupInputArray({ name, readonly, value, errors, 
onChange }: PropsInputIn
 }
 
 function YupInputWithAddon({ name, readonly, value, errors, onChange, addon, 
atTheEnd }: PropsInputInternal & { addon: string; atTheEnd?: boolean }): VNode {
-  const dict = useText({
-    placeholder: `fields.instance.${name}.placeholder`,
-    tooltip: `fields.instance.${name}.tooltip`,
-  })
+  const placeholder = useMessage(`fields.instance.${name}.placeholder`)
+  const tooltip = useMessage(`fields.instance.${name}.tooltip`)
 
   return <div class="field is-horizontal">
     <div class="field-label is-normal">
       <label class="label">
-        <Text id={`fields.instance.${name}.label`} />
-        {dict.tooltip && <span class="icon" data-tooltip={dict.tooltip}>
+        <Message id={`fields.instance.${name}.label`} />
+        {tooltip && <span class="icon" data-tooltip={tooltip}>
           <i class="mdi mdi-information" />
         </span>}
       </label>
@@ -220,34 +218,32 @@ function YupInputWithAddon({ name, readonly, value, 
errors, onChange, addon, atT
           </div>}
           <p class="control is-expanded">
             <input class={errors[name] ? "input is-danger" : "input"} 
type="text"
-              placeholder={dict.placeholder} readonly={readonly}
+              placeholder={placeholder} readonly={readonly}
               name={name} value={value}
               onChange={(e): void => onChange(e.currentTarget.value)} />
-            <Text id={`fields.instance.${name}.help`} />
+            <Message id={`fields.instance.${name}.help`} > </Message>
           </p>
           {atTheEnd && <div class="control">
             <a class="button is-static">{addon}</a>
           </div>}
         </div>
-        {errors[name] ? <p class="help is-danger"><Text 
id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Text></p> : null}
+        {errors[name] ? <p class="help is-danger"><Message 
id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Message></p> : null}
       </div>
     </div>
   </div>
 }
 
 function YupInputSecured({ name, readonly, value, errors, onChange }: 
PropsInputInternal): VNode {
-  const dict = useText({
-    placeholder: `fields.instance.${name}.placeholder`,
-    tooltip: `fields.instance.${name}.tooltip`,
-  })
+  const placeholder = useMessage(`fields.instance.${name}.placeholder`, {})
+  const tooltip = useMessage(`fields.instance.${name}.tooltip`, {})
 
   const [active, setActive] = useState(false)
 
   return <div class="field is-horizontal">
     <div class="field-label is-normal">
       <label class="label">
-        <Text id={`fields.instance.${name}.label`} />
-        {dict.tooltip && <span class="icon" data-tooltip={dict.tooltip}>
+        <Message id={`fields.instance.${name}.label`} />
+        {tooltip && <span class="icon" data-tooltip={tooltip}>
           <i class="mdi mdi-information" />
         </span>}
       </label>
@@ -261,14 +257,14 @@ function YupInputSecured({ name, readonly, value, errors, 
onChange }: PropsInput
           </label>
           <p class="control">
             <input class="input" type="text"
-              placeholder={dict.placeholder} readonly={readonly || !active}
+              placeholder={placeholder} readonly={readonly || !active}
               disabled={readonly || !active}
               name={name} value={value}
               onChange={(e): void => onChange(e.currentTarget.value)} />
-            <Text id={`fields.instance.${name}.help`} />
+            <Message id={`fields.instance.${name}.help`}> </Message>
           </p>
         </div>
-        {errors[name] ? <p class="help is-danger"><Text 
id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Text></p> : null}
+        {errors[name] ? <p class="help is-danger"><Message 
id={`validation.${errors[name].type}`} 
fields={errors[name].params}>{errors[name].message}</Message></p> : null}
       </div>
     </div>
   </div>
diff --git a/packages/frontend/src/context/backend.ts 
b/packages/frontend/src/context/backend.ts
index beb6a1b..f48b2e3 100644
--- a/packages/frontend/src/context/backend.ts
+++ b/packages/frontend/src/context/backend.ts
@@ -1,11 +1,42 @@
 import { createContext } from 'preact'
+import { StateUpdater } from 'preact/hooks'
 
-interface GlobalContext {
-  backendURL: string;
+export interface BackendContextType {
+  url: string;
+  token?: string;
+  changeBackend: (url: string) => void;
+  // clearTokens: () => void;
+  // addTokenCleaner: (c: StateUpdater<string | undefined>) => void;
+  updateToken: (token?:string) => void;
+  lang: string;
+  setLang: (lang: string) => void;
+}
+
+export interface ConfigContextType {
   currency?: string;
 }
 
-export const ConfigContext = createContext<GlobalContext>({
-  backendURL: '',
-  currency: '',
+export interface InstanceContextType {
+  id: string;
+  token?: string;
+}
+
+export const BackendContext = createContext<BackendContextType>({
+  url: '',
+  lang: 'en',
+  token: undefined,
+  changeBackend: () => null,
+  // clearTokens: () => null,
+  // addTokenCleaner: () => null,
+  updateToken: () => null,
+  setLang: () => null,
 })
+
+export const ConfigContext = createContext<ConfigContextType>({
+  currency: undefined,
+})
+
+export const InstanceContext = createContext<InstanceContextType>({
+  id: '',
+  token: undefined,
+})
\ No newline at end of file
diff --git a/packages/frontend/src/custom.d.ts 
b/packages/frontend/src/custom.d.ts
new file mode 100644
index 0000000..bdf59ac
--- /dev/null
+++ b/packages/frontend/src/custom.d.ts
@@ -0,0 +1,8 @@
+declare module '*.po' {
+  const content: any;
+  export default content;
+}
+declare module "*.jpeg" {
+  const content: any;
+  export default content;
+}
diff --git a/packages/frontend/src/declaration.d.ts 
b/packages/frontend/src/declaration.d.ts
index faa6cd2..a81260a 100644
--- a/packages/frontend/src/declaration.d.ts
+++ b/packages/frontend/src/declaration.d.ts
@@ -19,26 +19,14 @@
  * @author Sebastian Javier Marchano (sebasjm)
  */
 
-declare module "*.css" {
-    const mapping: Record<string, string>;
-    export default mapping;
-}
-declare module "*.jpeg" {
-    const mapping: Record<string, string>;
-    export default mapping;
-}
-
-declare module "*.scss" {
-    const mapping: Record<string, string>;
-    export default mapping;
-}
 
 interface KeyValue {
     [key: string]: string;
 }
 
 interface Notification {
-    messageId: string;
+    message: string;
+    description?: string;
     type: MessageType;
     params?: any;
 }
diff --git a/packages/frontend/src/hooks/backend.ts 
b/packages/frontend/src/hooks/backend.ts
index 0804c7b..e2b696b 100644
--- a/packages/frontend/src/hooks/backend.ts
+++ b/packages/frontend/src/hooks/backend.ts
@@ -14,98 +14,142 @@
  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 useSWR, { mutate } from 'swr';
 import axios from 'axios'
 import { MerchantBackend } from '../declaration';
+import { useContext } from 'preact/hooks';
+import { BackendContext, InstanceContext } from '../context/backend';
 
-type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError<T>;
+type HttpResponse<T> = HttpResponseOk<T> | HttpResponseError;
 
 interface HttpResponseOk<T> {
   data: T;
 }
-interface HttpResponseError<T> {
+
+export interface SwrError {
+  info: any,
+  status: number,
+  message: string,
+  backend: string,
+  hasToken: boolean,
+}
+interface HttpResponseError {
   data: undefined;
-  needsAuth: boolean;
-  error: Error;
+  unauthorized: boolean;
+  error?: SwrError;
 }
 
 
 type Methods = 'get' | 'post' | 'patch' | 'delete' | 'put';
 
-async function request(url: string, method?: Methods, data?: any): 
Promise<any> {
-  const backend = localStorage.getItem('backend-url')
-  const token = localStorage.getItem('backend-token')
-  const headers = token ? { Authorization: `${token}` } : undefined
+interface RequestOptions {
+  method?: Methods;
+  token?: string;
+  data?: any;
+}
+
+
+
+async function request(url: string, options: RequestOptions = {}): 
Promise<any> {
+  const headers = options.token ? { Authorization: `${options.token}` } : 
undefined
 
   try {
     const res = await axios({
-      method: method || 'get',
-      url: `${backend}${url}`,
+      method: options.method || 'get',
+      url,
       responseType: 'json',
       headers,
-      data
+      data: options.data
     })
     return res.data
   } catch (e) {
     const info = e.response?.data
     const status = e.response?.status
-    throw { info, status, error:e, backend, hasToken: !!token }
+    throw { info, status, message: e.message, backend: url, hasToken: 
!!options.token }
   }
 
 }
 
-async function fetcher(url: string): Promise<any> {
-  return request(url, 'get')
+function fetcher(url: string, token: string, backend: string) {
+  return request(`${backend}${url}`, { token })
 }
 
-interface WithCreate<T> {
-  create: (data: T) => Promise<void>;
+interface BackendMutateAPI {
+  createInstance: (data: 
MerchantBackend.Instances.InstanceConfigurationMessage) => Promise<void>;
 }
-interface WithUpdate<T> {
-  update: (id: string, data: T) => Promise<void>;
-}
-interface WithDelete {
-  delete: (id: string) => Promise<void>;
+interface BackendInstaceMutateAPI {
+  updateInstance: (data: 
MerchantBackend.Instances.InstanceReconfigurationMessage) => Promise<void>;
+  deleteInstance: () => Promise<void>;
 }
 
-export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> & 
WithCreate<MerchantBackend.Instances.InstanceConfigurationMessage> {
-  const { data, error } = 
useSWR<MerchantBackend.Instances.InstancesResponse>('/private/instances', 
fetcher)
+export function useBackendMutateAPI(): BackendMutateAPI {
+  const { url, token } = useContext(BackendContext)
 
-  const create = async (instance: 
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
-    await request('/private/instances', 'post', instance)
+  const createInstance = async (instance: 
MerchantBackend.Instances.InstanceConfigurationMessage): Promise<void> => {
+    await request(`${url}/private/instances`, {
+      method: 'post',
+      token,
+      data: instance
+    })
 
     mutate('/private/instances')
   }
-
-  return { data, needsAuth: error?.status === 401, error, create }
+  return { createInstance }
 }
 
-export function useBackendInstance(id: string | null): 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> & 
WithUpdate<MerchantBackend.Instances.InstanceReconfigurationMessage> & 
WithDelete {
-  const { data, error } = 
useSWR<MerchantBackend.Instances.QueryInstancesResponse>(id ? 
`/private/instances/${id}` : null, fetcher)
+export function useBackendInstanceMutateAPI(): BackendInstaceMutateAPI {
+  const { url } = useContext(BackendContext)
+  const { id, token } = useContext(InstanceContext)
 
-  const update = async (updateId: string, instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
-    await request(`/private/instances/${updateId}`, 'patch', instance)
+  const updateInstance = async (instance: 
MerchantBackend.Instances.InstanceReconfigurationMessage): Promise<void> => {
+    await request(`${url}/private/instances/${id}`, {
+      method: 'patch',
+      token,
+      data: instance
+    })
 
     mutate('/private/instances', null)
-    mutate(`/private/instances/${updateId}`, null)
+    mutate(`/private/instances/${id}`, null)
   };
-  const _delete = async (deleteId: string): Promise<void> => {
-    await request(`/private/instances/${deleteId}`, 'delete')
+  
+  const deleteInstance = async (): Promise<void> => {
+    await request(`${url}/private/instances/${id}`, {
+      method: 'delete',
+      token,
+    })
 
     mutate('/private/instances', null)
-    mutate(`/private/instances/${deleteId}`, null)
+    mutate(`/private/instances/${id}`, null)
   }
 
-  return { data, needsAuth: error?.status === 401, error, update, delete: 
_delete }
+  return { updateInstance, deleteInstance }
+}
+
+export function useBackendInstances(): 
HttpResponse<MerchantBackend.Instances.InstancesResponse> {
+  const { url, token } = useContext(BackendContext)
+  const { data, error } = useSWR<MerchantBackend.Instances.InstancesResponse, 
SwrError>(['/private/instances', token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, error }
+}
+
+export function useBackendInstance(): 
HttpResponse<MerchantBackend.Instances.QueryInstancesResponse> {
+  const { url } = useContext(BackendContext);
+  const { id, token } = useContext(InstanceContext);
+  const { data, error } = 
useSWR<MerchantBackend.Instances.QueryInstancesResponse, 
SwrError>([`/private/instances/${id}`, token, url], fetcher)
+
+  return { data, unauthorized: error?.status === 401, error }
 }
 
-export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse>  {
-  const { data, error } = useSWR<MerchantBackend.VersionResponse>(`/config`, 
fetcher)
+export function useBackendConfig(): 
HttpResponse<MerchantBackend.VersionResponse> {
+  const { url, token } = useContext(BackendContext)
+  const { data, error } = useSWR<MerchantBackend.VersionResponse, 
SwrError>(['/config', token, url], fetcher, {
+    shouldRetryOnError: false
+  })
 
-  return { data, needsAuth: error?.status === 401, error }
+  return { data, unauthorized: error?.status === 401, error }
 }
diff --git a/packages/frontend/src/hooks/index.ts 
b/packages/frontend/src/hooks/index.ts
index 267b765..69a5e8e 100644
--- a/packages/frontend/src/hooks/index.ts
+++ b/packages/frontend/src/hooks/index.ts
@@ -19,29 +19,52 @@
 * @author Sebastian Javier Marchano (sebasjm)
 */
 
-import { StateUpdater, useState } from "preact/hooks";
+import { StateUpdater, useEffect, useState } from "preact/hooks";
 import { mutate } from 'swr';
 
-interface State {
-  token?: string;
-  url: string;
+export function useBackendURL(): [string, StateUpdater<string>] {
+  return useNotNullLocalStorage('backend-url', typeof window !== 'undefined' ? 
window.location.origin : '')
+}
+export function useBackendDefaultToken(): [string | undefined, 
StateUpdater<string | undefined>] {
+  return useLocalStorage('backend-token')
 }
 
-export function useBackend(): [State, StateUpdater<State>] {
-  const [url, setUrl] = useLocalStorage('backend-url', window.location.origin)
-  const [token, setToken] = useLocalStorage('backend-token')
-
-  const updater: StateUpdater<State> = (value: State | ((value: State) => 
State)) => {
-    const valueToStore = value instanceof Function ? value({ token, url: url 
|| window.location.origin }) : value;
-    setUrl(valueToStore.url)
-    setToken(valueToStore.token)
+export function useBackendInstanceToken(id: string): [string | undefined, 
StateUpdater<string | undefined>, VoidFunction] {
+  const [token, setToken] = useLocalStorage(`backend-token-${id}`)
+  const [ids, setIds] = useLocalStorage(`backend-token-ids`)
 
-    mutate('/private/instances', null)
+  function clearAllTokens() {
+    // TODO: refactor this
+    ids?.split(',').map(i => localStorage.removeItem(`backend-token-${i}`))
   }
 
-  return [{ token, url: url || window.location.origin }, updater]
+  useEffect(() => {
+    setIds((ids: string | undefined): string | undefined => {
+      if (!ids) return ids
+      const all = ids.split(',')
+      if (all.includes(id)) return ids
+      return all.concat(id).filter(Boolean).join(',')
+    })
+  }, [id, setIds])
+  
+  return [token, setToken, clearAllTokens]
 }
 
+// export function useBackend(): [State, StateUpdater<State>] {
+//   const [url, setUrl] = useLocalStorage('backend-url', 
window.location.origin)
+//   const [token, setToken] = useLocalStorage('backend-token')
+
+//   const updater: StateUpdater<State> = (value: State | ((value: State) => 
State)) => {
+//     const valueToStore = value instanceof Function ? value({ token, url: 
url || window.location.origin }) : value;
+//     setUrl(valueToStore.url)
+//     setToken(valueToStore.token)
+
+//     mutate('/private/instances', null)
+//   }
+
+//   return [{ token, url: url || window.location.origin }, updater]
+// }
+
 export function useLang(): [string, StateUpdater<string>] {
   return useNotNullLocalStorage('lang-preference', typeof window !== 
"undefined" ? navigator.language || (navigator as any).userLanguage : 'en')
 }
diff --git a/packages/frontend/src/hooks/notifications.ts 
b/packages/frontend/src/hooks/notifications.ts
index 986ddaf..c66594c 100644
--- a/packages/frontend/src/hooks/notifications.ts
+++ b/packages/frontend/src/hooks/notifications.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 { useState } from "preact/hooks";
 import { Notification } from '../declaration';
@@ -25,16 +25,24 @@ import { Notification } from '../declaration';
 interface Result {
   notifications: Notification[];
   pushNotification: (n: Notification) => void;
+  removeNotification: (n: Notification) => void;
 }
 
-export function useNotifications(timeout = 3000): Result {
-  const [notifications, setNotifications] = useState<(Notification & { since: 
Date })[]>([])
+type NotificationWithDate = Notification & { since: Date }
+
+export function useNotifications(initial: Notification[] = [], timeout = 
3000): Result {
+  const [notifications, setNotifications] = 
useState<(NotificationWithDate)[]>(initial.map(i => ({...i, since: new Date() 
})))
+
   const pushNotification = (n: Notification): void => {
     const entry = { ...n, since: new Date() }
     setNotifications(ns => [...ns, entry])
-    setTimeout(() => {
+    if (n.type !== 'ERROR') setTimeout(() => {
       setNotifications(ns => ns.filter(x => x.since !== entry.since))
     }, timeout)
   }
-  return { notifications, pushNotification }
+
+  const removeNotification = (notif: Notification) => {
+    setNotifications((ns: NotificationWithDate[]) => ns.filter(n => n !== 
notif))
+  }
+  return { notifications, pushNotification, removeNotification }
 }
diff --git a/packages/frontend/src/i18n/index.ts 
b/packages/frontend/src/i18n/index.ts
deleted file mode 100644
index 27b9f72..0000000
--- a/packages/frontend/src/i18n/index.ts
+++ /dev/null
@@ -1,279 +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/>
- */
-
- /**
- *
- * @author Sebastian Javier Marchano (sebasjm)
- */
-
-export const translations = {
-  es: {
-    confirm_modal: {
-      title: 'confirmar accion',
-      create_instance: 'crear instancia',
-      delete_instance: 'borrar instancia',
-      update_instance: 'actualizar instancia',
-    },
-    notification: {
-      unauthorized: {
-        title: 'acceso no autorizado',
-        description: 'el servidor a denegado el acceso'
-      },
-      create_error: {
-        title: 'error creando',
-        description: 'la creación no se efectuó correctamente. el servidor 
dice: {{message}}'
-      },
-      create_success: {
-        title: 'creación correcta',
-        description: 'la creación se efectuó correctamente'
-      },
-      update_error: {
-        title: 'error actualizando',
-        description: 'la actualizacion no se efectuó correctamente. el 
servidor dice: {{message}}'
-      },
-      update_success: {
-        title: 'actualización correcta',
-        description: 'la actualizacion se efectuó correctamente'
-      },
-      delete_error: {
-        title: 'error eliminando',
-        description: 'la eliminación no se efectuó correctamente. el servidor 
dice: {{message}}'
-      },
-      delete_success: {
-        title: 'eliminación correcta',
-        description: 'la eliminación se efectuó correctamente'
-      },
-    },
-    cancel: 'cancelar',
-    confirm: 'confirmar',
-    fields: {
-      instance: {
-        id: {
-          label: 'Id',
-        },
-        merchant_pub: {
-          label: 'Clave pública'
-        },
-        payment_targets: {
-          label: 'Dirección de pago',
-        },
-        name: {
-          label: 'Nombre',
-        },
-        payto_uris: {
-          label: 'PaytTO URI',
-          placeholder: 'valores separados por coma',
-          help: 'example: payto://<authority>/<path>/<name>',
-        },
-        default_max_deposit_fee: {
-          label: 'Máximo pago por depósito',
-        },
-        default_max_wire_fee: {
-          label: 'Máximo pago por transferencia bancaria',
-        },
-        default_wire_fee_amortization: {
-          label: 'Amortización de pago',
-        },
-        default_pay_delay: {
-          label: 'Tiempo de espera de pago'
-        },
-        default_wire_transfer_delay: {
-          label: 'Tiempo de espera de transferencia bancaria'
-        },
-      },
-    },
-    validation: {
-      required: '{{label}} es obligatorio',
-      typeError: '{{label}}',
-      payto: 'la dirección de pago no es valida',
-    },
-    text: {
-      instances: 'Instancias',
-      merchant: 'Merchant',
-      list_of_configured_instances: 'Lista de instancias configuradas',
-      instance: {
-        empty_list: 'No hay instancias configuradas, puede crear una usando el 
boton + ',
-      }
-    }
-  },
-  en: {
-    confirm_modal: {
-      title: 'confirm action',
-      create_instance: 'create instance',
-      delete_instance: 'delete instance',
-      update_instance: 'update instance',
-    },
-    notification: {
-      unauthorized: {
-        title: 'unauthorized access',
-        description: 'backend has denied access'
-      },
-      error: {
-        title: 'Error query the backend',
-        description: 'Got message: "{{error.message}}" from: {{backend}} 
(hasToken: {{hasToken}})'
-      },
-      create_error: {
-        title: 'create error',
-        description: 'the create process went wrong, server says: 
{{info.hint}}'
-      },
-      create_success: {
-        title: 'create success',
-        description: 'the create process completed'
-      },
-      update_error: {
-        title: 'update error',
-        description: 'the update process went wrong, server says: 
{{info.hint}}'
-      },
-      update_success: {
-        title: 'update success',
-        description: 'the update process completed'
-      },
-      delete_error: {
-        title: 'delete error',
-        description: 'the delete process went wrong, server says: 
{{info.hint}}'
-      },
-      delete_success: {
-        title: 'delete success',
-        description: 'the delete process completed'
-      },
-    },
-    cancel: 'cancel',
-    confirm: 'confirm',
-    fields: {
-      instance: {
-        id: {
-          label: 'Id',
-        },
-        auth_token: {
-          label: 'Auth Token',
-        },
-        merchant_pub: {
-          label: 'Public Key'
-        },
-        payment_targets: {
-          label: 'Payment targets',
-        },
-        name: {
-          label: 'Business Name',
-          tooltip: 'the name of the merchant instance'
-        },
-        payto_uris: {
-          label: 'Bank accounts',
-          tooltip: 'Bank account URI',
-          help: 'payto://x-taler-bank/bank.taler:5882/blogger',
-        },
-        default_max_deposit_fee: {
-          label: 'Max deposit fee',
-        },
-        default_max_wire_fee: {
-          label: 'Max wire fee',
-        },
-        default_wire_fee_amortization: {
-          label: 'Max fee amortization',
-        },
-        default_pay_delay: {
-          label: 'Pay delay',
-          tooltip: 'value expressed in seconds',
-        },
-        default_wire_transfer_delay: {
-          label: 'Wire transfer delay',
-          tooltip: 'value expressed in seconds',
-        },
-        address: {
-          label: 'Address',
-          country: {
-            label: 'Country',
-          },
-          country_subdivision: {
-            label: 'Country subdivision',
-          },
-          town: {
-            label: 'Town',
-          },
-          district: {
-            label: 'District',
-          },
-          town_location: {
-            label: 'Town Location',
-          },
-          post_code: {
-            label: 'Post code',
-          },
-          street: {
-            label: 'Street',
-          },
-          building_name: {
-            label: 'Building name',
-          },
-          building_number: {
-            label: 'Building number',
-          },
-          address_lines: {
-            label: 'Address lines',
-          }
-        },
-        jurisdiction: {
-          label: 'Jurisdiction',
-          country: {
-            label: 'Country',
-          },
-          country_subdivision: {
-            label: 'Country subdivision',
-          },
-          town: {
-            label: 'Town',
-          },
-          district: {
-            label: 'District',
-          },
-          town_location: {
-            label: 'Town Location',
-          },
-          post_code: {
-            label: 'Post code',
-          },
-          street: {
-            label: 'Street',
-          },
-          building_name: {
-            label: 'Building name',
-          },
-          building_number: {
-            label: 'Building number',
-          },
-          address_lines: {
-            label: 'Address lines',
-          }
-        }
-      }
-    },
-    validation: {
-      required: '{{label}} is required',
-      typeError: '{{label}}',
-      payto: 'the pay address is not valid',
-    },
-    text: {
-      instances: 'Instances',
-      merchant: 'Merchant',
-      list_of_configured_instances: 'List of configured instances',
-      create_new_instance: 'Create new instance',
-      
-      instance: {
-        empty_list: 'No instance configured yet, setup one pressing the + 
button',
-      }
-    },
-  },
-}
\ No newline at end of file
diff --git a/packages/frontend/src/index.tsx b/packages/frontend/src/index.tsx
index c613514..ea2ef54 100644
--- a/packages/frontend/src/index.tsx
+++ b/packages/frontend/src/index.tsx
@@ -22,43 +22,237 @@
 import "./scss/main.scss"
 
 import { h, VNode } from 'preact';
-import { Route, Router } from 'preact-router';
-import { IntlProvider } from 'preact-i18n';
+import { useCallback, useContext, useEffect, useState } from "preact/hooks";
+import { Route, Router, route } from 'preact-router';
+import { MessageError, MessageProvider, useMessageTemplate } from 
'preact-messages';
 
-import { Footer } from './components/footer';
+import { Notification } from "./declaration";
 import { Sidebar } from './components/sidebar';
 import { NavigationBar } from './components/navbar';
 import { Notifications } from './components/notifications';
-import { translations } from './i18n';
-import { useBackend, useLang } from './hooks';
+import * as messages from './messages'
+import { useBackendURL, useBackendDefaultToken, useLang, 
useBackendInstanceToken } from './hooks';
+import { useNotifications } from "./hooks/notifications";
+import { BackendContext, ConfigContext, InstanceContext } from 
'./context/backend';
+import { SwrError, useBackendConfig } from "./hooks/backend";
 
 import NotFoundPage from './routes/notfound';
-import Instances from './routes/instances';
-import { useNotifications } from "./hooks/notifications";
-import { ConfigContext } from './context/backend';
-import { useBackendConfig } from "./hooks/backend";
+import Login from './routes/login';
+import Instances from './routes/instances/list';
+import Create from "./routes/instances/create";
+import Details from "./routes/instances/details";
+import Update from "./routes/instances/update";
+
+enum RootPages {
+  root = '/',
+  instances = '/instances',
+  new = '/new',
+  instance_id_route = '/instance/:id/:rest*',
+}
+
+enum InstancePages {
+  details = '/instance/:id/',
+  update = '/instance/:id/update',
+}
+
+function Redirect({ to }: { to: string }): null {
+  useEffect(() => {
+    route(to, true)
+  })
+  return null
+}
+
+
+
+function AppRouting(): VNode {
+  const { notifications, pushNotification, removeNotification } = 
useNotifications()
+  const { lang, setLang, changeBackend, updateToken } = 
useContext(BackendContext)
+  const backendConfig = useBackendConfig();
+  const i18n = useMessageTemplate('')
+
+  const LoginWithError = () => <Login
+    withMessage={{
+      message: i18n`Couldnt access the server`,
+      type: 'ERROR',
+      description: !backendConfig.data && backendConfig.error ? i18n`Got 
message: ${backendConfig.error.message} from: ${backendConfig.error.backend} 
(hasToken: ${backendConfig.error.hasToken})` : undefined,
+    }}
+    onConfirm={(url: string, token?: string) => {
+      changeBackend(url)
+      if (token) updateToken(token)
+      route(RootPages.instances)
+    }}
+  />
+
+  const cleaner = useCallback(() =>{updateToken(undefined)},[])
+
+  const [cleaners, setCleaners] = useState([cleaner])
+  const addTokenCleaner = (c:() => void) => setCleaners(cs => [...cs,c])
+  const addTokenCleanerNemo = useCallback((c:() => void) 
=>{addTokenCleaner(c)},[cleaner])
+
+  return <div id="app">
+    <ConfigContext.Provider value={backendConfig.data || {}}>
+      <NavigationBar lang={lang} setLang={setLang} onLogout={() => { 
cleaners.forEach( c => c() ) }} />
+      <Sidebar />
+      <Notifications notifications={notifications} 
removeNotification={removeNotification} />
+      {!backendConfig.data ?
+        <Route default component={LoginWithError} /> :
+        <Route default component={AppReady} 
pushNotification={pushNotification} addTokenCleaner={addTokenCleanerNemo} />
+      }
+    </ConfigContext.Provider>
+  </div>
+}
+
+function AppReady({ pushNotification,addTokenCleaner }: { pushNotification: 
(n: Notification) => void, addTokenCleaner: any }): VNode {
+  const { changeBackend, updateToken } = useContext(BackendContext)
+
+  const updateLoginStatus = (url: string, token?: string) => {
+    changeBackend(url)
+    if (token) updateToken(token)
+  }
+  const i18n = useMessageTemplate('')
+
+  return <Router>
+    <Route path={RootPages.root} component={Redirect} to={RootPages.instances} 
/>
+
+    <Route path={RootPages.instances}
+      component={Instances}
+
+      onCreate={() => {
+        route(RootPages.new)
+      }}
+
+      onUpdate={(id: string): void => {
+        route(`/instance/${id}/update`)
+      }}
+
+      onUnauthorized={() => <Login
+        withMessage={{ message: i18n`unauthorized`, type: 'ERROR', }}
+        onConfirm={updateLoginStatus}
+      />}
+
+      onError={(error: SwrError) => {
+        pushNotification({ message: i18n`error`, params: error, type: 'ERROR' 
})
+        return <div />
+    }}
+    />
 
-export default function App(): VNode {
-  const { notifications, pushNotification } = useNotifications()
+    <Route path={RootPages.new}
+      component={Create}
+      onBack={() => route(RootPages.instances)}
+
+      onConfirm={() => {
+        pushNotification({ message: i18n`create_success`, type: 'SUCCESS' })
+        route(RootPages.instances)
+      }}
+
+      onError={(error: any) => {
+        pushNotification({ message: i18n`create_error`, type: 'ERROR', params: 
error })
+      }}
+    />
+
+    <Route path={RootPages.instance_id_route} component={SubPages} 
pushNotification={pushNotification} addTokenCleaner={addTokenCleaner} />
+
+    <Route default component={NotFoundPage} />
+
+  </Router>
+}
+
+function hasKey<O>(obj: O, key: string | number | symbol): key is keyof O {
+  return key in obj
+}
+
+function useBackendContextState() {
   const [lang, setLang] = useLang()
-  const [{url: backendURL}] = useBackend();
-  const { data } = useBackendConfig();
+  const [url, changeBackend] = useBackendURL();
+  const [token, updateToken] = useBackendDefaultToken();
+
+  return { url, token, changeBackend, updateToken, lang, setLang }
+}
+
+function onTranslationError(error: MessageError) {
+  if (typeof window === "undefined") return;
+  (window as any)['missing_locale'] = ([] as string[]).concat((window as 
any)['missing_locale']).concat(error.path.join())
+}
+
+export default function Application(): VNode {
+  const state = useBackendContextState()
 
   return (
-    <ConfigContext.Provider value={{backendURL, currency: data && 
data.currency}}>
-
-      <IntlProvider definition={(translations as any)[lang] || 
translations.en}>
-        <div id="app">
-          <NavigationBar lang={lang} setLang={setLang} />
-          <Sidebar />
-          <Notifications notifications={notifications} />
-          <Router>
-            <Route path="/" component={Instances} 
pushNotification={pushNotification} />
-            <Route default component={NotFoundPage} />
-          </Router>
-          <Footer />
-        </div>
-      </IntlProvider >
-    </ConfigContext.Provider>
+    <BackendContext.Provider value={state}>
+      <MessageProvider locale={state.lang} onError={onTranslationError} 
messages={hasKey(messages, state.lang) ? messages[state.lang] : messages.en} 
pathSep={null as any} >
+        <AppRouting />
+      </MessageProvider >
+    </BackendContext.Provider>
   );
+}
+interface SubPagesProps {
+  id: string;
+  pushNotification: (n: Notification) => void;
+  addTokenCleaner: any;
+}
+
+
+function SubPages({ id, pushNotification, addTokenCleaner }: SubPagesProps): 
VNode {
+  const [token, updateToken] = useBackendInstanceToken(id);
+  const { changeBackend } = useContext(BackendContext)
+  const cleaner = useCallback(() =>{updateToken(undefined)},[id])
+  const i18n = useMessageTemplate('')
+  
+  useEffect(() => {
+    addTokenCleaner(cleaner)
+  }, [addTokenCleaner, cleaner])
+
+  const updateLoginStatus = (url: string, token?: string) => {
+    changeBackend(url)
+    if (token) updateToken(token)
+  }
+
+  return <InstanceContext.Provider value={{id, token}}>
+    <Router>
+      <Route path={InstancePages.details}
+        component={Details}
+
+        onUnauthorized={() => <Login
+          withMessage={{ message: i18n`unauthorized`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus}
+        />}
+
+        onUpdate={() => {
+          route(`/instance/${id}/update`)
+        }}
+
+        onLoadError={(e: SwrError) => {
+          pushNotification({ message: i18n`update_load_error`, type: 'ERROR', 
params: e })
+          route(`/instance/${id}/`)
+          return <div />
+        }}
+      />
+
+      <Route path={InstancePages.update}
+        component={Update}
+        onUnauthorized={() => <Login
+          withMessage={{ message: i18n`unauthorized`, type: 'ERROR', }}
+          onConfirm={updateLoginStatus}
+        />}
+        onLoadError={(e: SwrError) => {
+          pushNotification({ message: i18n`update_load_error`, type: 'ERROR', 
params: e })
+          route(`/instance/${id}/`)
+          return <div />
+        }}
+        onBack={() => {
+          route(`/instance/${id}/`)
+        }}
+        onConfirm={() => {
+          pushNotification({ message: i18n`create_success`, type: 'SUCCESS' })
+          route(`/instance/${id}/`)
+        }}
+        onUpdateError={(e: Error) => {
+          pushNotification({ message: i18n`update_error`, type: 'ERROR', 
params: e })
+        }}
+      />
+
+      <Route default component={NotFoundPage} />
+    </Router>
+  </InstanceContext.Provider>
+
 }
\ No newline at end of file
diff --git a/packages/frontend/src/messages/en.po 
b/packages/frontend/src/messages/en.po
new file mode 100644
index 0000000..b0be0c0
--- /dev/null
+++ b/packages/frontend/src/messages/en.po
@@ -0,0 +1,155 @@
+# Examples from http://pology.nedohodnik.net/doc/user/en_US/ch-poformat.html
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Language: en\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 
|| n%100>=20) ? 1 : 2);\n"
+ 
+msgid "Time: %1 second"
+msgid_plural "Time: %1 seconds"
+msgstr[0] "Czas: %1 sekunda"
+msgstr[1] "Czas: %1 sekundy"
+msgstr[2] "Czas: %1 sekund"
+ 
+msgid "Hi"
+msgstr "Hello"
+
+msgid "List of configured instances"
+msgstr "List of configured instances"
+
+msgid "There is no instances yet, add more pressing the + sign"
+msgstr "There is no instances yet, add more pressing the + sign"
+
+#  msgctxt "fields.instance.name"
+#  msgid "placeholder"
+#  msgstr ""
+
+#  |msgctxt "fields"
+#  |msgctxt "instance"
+#  msgctxt "fields.instance.id.label"
+
+msgid "fields.instance.id.label"
+msgstr "Id"
+
+msgid "fields.instance.name.label"
+msgstr "Name"
+
+msgid "fields.instance.merchant.pub.label"
+msgstr "Public key"
+
+msgid "fields.instance.payment.targets.label"
+msgstr "Payment targets"
+
+msgid "fields.instance.auth_token.label"
+msgstr "Auth token"
+
+msgid "fields.instance.auth_token.tooltip"
+msgstr "Use this token to secure an instance with a password"
+
+msgid "fields.instance.payto_uris.label"
+msgstr "Account address"
+
+msgid "fields.instance.payto_uris.help"
+msgstr "payto://x-taler-bank/bank.taler:5882/blogger"
+
+msgid "fields.instance.default_max_deposit_fee.label"
+msgstr "Max deposit fee label"
+
+msgid "fields.instance.default_max_wire_fee.label"
+msgstr "Max wire fee label"
+
+msgid "fields.instance.default_wire_fee_amortization.label"
+msgstr "Wire fee Amortization"
+
+msgid "fields.instance.address.label"
+msgstr "Address"
+
+msgid "fields.instance.address.country.label"
+msgstr "Country"
+
+msgid "fields.instance.address.country_subdivision.label"
+msgstr "Country Subdivision"
+
+msgid "fields.instance.address.district.label"
+msgstr "District"
+
+msgid "fields.instance.address.town.label"
+msgstr "Town"
+
+msgid "fields.instance.address.town_location.label"
+msgstr "Town Location"
+
+msgid "fields.instance.address.post_code.label"
+msgstr "Post code"
+
+msgid "fields.instance.address.street.label"
+msgstr "Street"
+
+msgid "fields.instance.address.building_name.label"
+msgstr "Building Name"
+
+msgid "fields.instance.address.building_number.label"
+msgstr "Building Number"
+
+msgid "fields.instance.address.address_lines.label"
+msgstr "Adress Line"
+
+msgid "fields.instance.jurisdiction.label"
+msgstr "Jurisdiction"
+
+msgid "fields.instance.jurisdiction.country.label"
+msgstr "Country"
+
+msgid "fields.instance.jurisdiction.country_subdivision.label"
+msgstr "Country Subdivision"
+
+msgid "fields.instance.jurisdiction.district.label"
+msgstr "District"
+
+msgid "fields.instance.jurisdiction.town.label"
+msgstr "Town"
+
+msgid "fields.instance.jurisdiction.town_location.label"
+msgstr "Town Location"
+
+msgid "fields.instance.jurisdiction.post_code.label"
+msgstr "Post code"
+
+msgid "fields.instance.jurisdiction.street.label"
+msgstr "Street"
+
+msgid "fields.instance.jurisdiction.building_name.label"
+msgstr "Building Name"
+
+msgid "fields.instance.jurisdiction.building_number.label"
+msgstr "Building Number"
+
+msgid "fields.instance.jurisdiction.address_lines.label"
+msgstr "Adress Line"
+
+msgid "fields.instance.default_pay_delay.label"
+msgstr "Pay delay"
+
+msgid "fields.instance.default_wire_transfer_delay.label"
+msgstr "Wire transfer delay"
+
+msgid "Couldnt access the server"
+msgstr "Couldnt access the server"
+
+msgid "Got message: %s from: %s (hasToken: %s)"
+msgstr "Recibimos el mensaje: %s desde: %s (con token: %s)"
+
+msgid "Merchant"
+msgstr "Merchant"
+
+msgid "Instances"
+msgstr "Instances"
+
+msgid "Update this instance"
+msgstr "Update this instance"
+
+msgid "Cancel"
+msgstr "Cancel"
+
+msgid "Confirm"
+msgstr "Confirm"
\ No newline at end of file
diff --git a/packages/frontend/src/messages/es.po 
b/packages/frontend/src/messages/es.po
new file mode 100644
index 0000000..16bfcce
--- /dev/null
+++ b/packages/frontend/src/messages/es.po
@@ -0,0 +1,22 @@
+# Examples from http://pology.nedohodnik.net/doc/user/en_US/ch-poformat.html
+msgid ""
+msgstr ""
+"Content-Type: text/plain; charset=UTF-8\n"
+"Language: pl\n"
+"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 
|| n%100>=20) ? 1 : 2);\n"
+ 
+msgid "Time: %1 second"
+msgid_plural "Time: %1 seconds"
+msgstr[0] "Czas: %1 sekunda"
+msgstr[1] "Czas: %1 sekundy"
+msgstr[2] "Czas: %1 sekund"
+ 
+msgid "Hi"
+msgstr "Hola"
+
+msgid "List of configured instances"
+msgstr "Lista de instancias configuradas"
+
+msgid "There is no instances yet, add more pressing the + sign"
+msgstr "No hay instancias todavía, agregá mas presionando el signo +"
+
diff --git a/packages/frontend/src/messages/index.ts 
b/packages/frontend/src/messages/index.ts
new file mode 100644
index 0000000..3b965a4
--- /dev/null
+++ b/packages/frontend/src/messages/index.ts
@@ -0,0 +1,3 @@
+export * as en from './en.po'
+export * as es from './es.po'
+
diff --git a/packages/frontend/src/routes/instances/Create.stories.tsx 
b/packages/frontend/src/routes/instances/create/Create.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/Create.stories.tsx
rename to packages/frontend/src/routes/instances/create/Create.stories.tsx
diff --git a/packages/frontend/src/routes/instances/CreatePage.tsx 
b/packages/frontend/src/routes/instances/create/CreatePage.tsx
similarity index 84%
rename from packages/frontend/src/routes/instances/CreatePage.tsx
rename to packages/frontend/src/routes/instances/create/CreatePage.tsx
index 7f16c2d..c779f94 100644
--- a/packages/frontend/src/routes/instances/CreatePage.tsx
+++ b/packages/frontend/src/routes/instances/create/CreatePage.tsx
@@ -21,16 +21,16 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { MerchantBackend } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceCreateSchema as schema } from '../../schemas'
-import { Text } from "preact-i18n";
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceCreateSchema as schema } from '../../../schemas'
+import { Message } from "preact-messages";
 
 interface Props {
   onCreate: (d: MerchantBackend.Instances.InstanceConfigurationMessage) => 
void;
   isLoading: boolean;
-  goBack: () => void;
+  onBack: () => void;
 }
 
 interface KeyValue {
@@ -45,7 +45,7 @@ function with_defaults(): 
Partial<MerchantBackend.Instances.InstanceConfiguratio
   };
 }
 
-export function CreatePage({ onCreate, isLoading, goBack }: Props): VNode {
+export function CreatePage({ onCreate, isLoading, onBack }: Props): VNode {
   const [value, valueHandler] = useState(with_defaults())
   const [errors, setErrors] = useState<KeyValue>({})
 
@@ -53,7 +53,7 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
     try {
       schema.validateSync(value, { abortEarly: false })
       onCreate(schema.cast(value) as 
MerchantBackend.Instances.InstanceConfigurationMessage);
-      goBack()
+      onBack()
     } catch (err) {
       const errors = err.inner as yup.ValidationError[]
       const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message 
} }), {})
@@ -67,8 +67,8 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
         <div class="level-left">
           <div class="level-item">
             <ul>
-              <li><Text id="text.merchant" /></li>
-              <li><Text id="text.instances" /></li>
+              <li><Message id="Merchant" /></li>
+              <li><Message id="Instances" /></li>
             </ul>
           </div>
         </div>
@@ -81,7 +81,7 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
           <div class="level-left">
             <div class="level-item">
               <h1 class="title">
-                <Text id="text.create_new_instances" />
+                <Message id="Create new instances" />
               </h1>
             </div>
           </div>
@@ -102,8 +102,8 @@ export function CreatePage({ onCreate, isLoading, goBack }: 
Props): VNode {
               valueHandler={valueHandler} info={schema.fields[f].describe()}
             />)}
           <div class="buttons is-right">
-            <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
-            <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
+            <button class="button" onClick={onBack} ><Message id="Cancel" 
/></button>
+            <button class="button is-success" onClick={submit} ><Message 
id="Confirm" /></button>
           </div>
         </div>
         <div class="column" />
diff --git a/packages/frontend/src/routes/instances/create/index.tsx 
b/packages/frontend/src/routes/instances/create/index.tsx
new file mode 100644
index 0000000..4af7b5a
--- /dev/null
+++ b/packages/frontend/src/routes/instances/create/index.tsx
@@ -0,0 +1,21 @@
+import { h, VNode } from "preact";
+import { MerchantBackend } from "../../../declaration";
+import { useBackendMutateAPI } from "../../../hooks/backend";
+import { CreatePage } from "./CreatePage";
+
+interface Props {
+  onBack: () => void;
+  onConfirm: () => void;
+  onError: (error: any) => void;
+}
+
+export default function Create({ onBack, onConfirm, onError }: Props): VNode {
+  const { createInstance } = useBackendMutateAPI();
+
+  return <CreatePage 
+    onBack={onBack}
+    isLoading={false}
+    onCreate={(d: MerchantBackend.Instances.InstanceConfigurationMessage): 
Promise<void> => {
+      return createInstance(d).then(onConfirm).catch(onError)
+    }} />
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/UpdatePage.tsx 
b/packages/frontend/src/routes/instances/details/DetailPage.tsx
similarity index 66%
copy from packages/frontend/src/routes/instances/UpdatePage.tsx
copy to packages/frontend/src/routes/instances/details/DetailPage.tsx
index feed125..17dab63 100644
--- a/packages/frontend/src/routes/instances/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/details/DetailPage.tsx
@@ -21,17 +21,16 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
-import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceUpdateSchema as schema } from '../../schemas'
-import { Text } from "preact-i18n";
+import { MerchantBackend } from "../../../declaration";
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceUpdateSchema as schema } from '../../../schemas'
+import { Message } from "preact-messages";
 
 interface Props {
-  onUpdate: (id: string, d: 
MerchantBackend.Instances.InstanceReconfigurationMessage) => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+  onUpdate: () => void;
+  onDelete: () => void;
+  selected: MerchantBackend.Instances.QueryInstancesResponse;
   isLoading: boolean;
-  goBack: () => void;
 }
 
 interface KeyValue {
@@ -49,22 +48,10 @@ function convert(from: 
MerchantBackend.Instances.QueryInstancesResponse): Mercha
   return { ...defaults, ...rest, payto_uris };
 }
 
-export function UpdatePage({ onUpdate, isLoading, selected, goBack }: Props): 
VNode {
+export function DetailPage({ onUpdate, isLoading, selected, onDelete }: 
Props): VNode {
   const [value, valueHandler] = useState(convert(selected))
   const [errors, setErrors] = useState<KeyValue>({})
 
-  const submit = (): void => {
-    try {
-      schema.validateSync(value, { abortEarly: false })
-      onUpdate(selected.id, schema.cast(value));
-      goBack()
-    } catch (err) {
-      const errors = err.inner as yup.ValidationError[]
-      const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message 
} }), {})
-      setErrors(pathMessages)
-    }
-  }
-
   return <div>
     <section class="section is-title-bar">
 
@@ -72,8 +59,8 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
         <div class="level-left">
           <div class="level-item">
             <ul>
-              <li><Text id="text.merchant" /></li>
-              <li><Text id="text.instances" /></li>
+              <li><Message id="Merchant" /></li>
+              <li><Message id="Instances" /></li>
             </ul>
           </div>
         </div>
@@ -86,7 +73,7 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
           <div class="level-left">
             <div class="level-item">
               <h1 class="title">
-                <Text id="text.create_new_instances" />
+                <Message id="Instance details" />
               </h1>
             </div>
           </div>
@@ -106,9 +93,9 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
               field={f} errors={errors} object={value}
               valueHandler={valueHandler} info={schema.fields[f].describe()}
             />)}
-         <div class="buttons is-right">
-            <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
-            <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
+          <div class="buttons is-right">
+            <button class="button is-danger" onClick={() => onDelete()} 
><Message id="delete" /></button>
+            <button class="button is-success" onClick={() => onUpdate()} 
><Message id="update" /></button>
           </div>
         </div>
         <div class="column" />
@@ -117,5 +104,4 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
 
   </div>
 
-  // </ConfirmModal>
 }
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/details/index.tsx 
b/packages/frontend/src/routes/instances/details/index.tsx
new file mode 100644
index 0000000..ffd3be8
--- /dev/null
+++ b/packages/frontend/src/routes/instances/details/index.tsx
@@ -0,0 +1,53 @@
+import { Fragment, h, VNode } from "preact";
+import { useContext, useState } from "preact/hooks";
+import { InstanceContext } from "../../../context/backend";
+import { Notification } from "../../../declaration";
+import { useBackendInstance, useBackendInstanceMutateAPI, SwrError } from 
"../../../hooks/backend";
+import { DeleteModal } from "../list/DeleteModal";
+import { DetailPage } from "./DetailPage";
+
+interface Props {
+  onUnauthorized: () => VNode;
+  onLoadError: (e: SwrError) => VNode;
+  onUpdate: () => void;
+  pushNotification: (n: Notification) => void;
+}
+
+export default function Detail({ onUpdate, onLoadError, onUnauthorized, 
pushNotification }: Props): VNode {
+  const { id } = useContext(InstanceContext)
+  const details = useBackendInstance()
+  const [deleting, setDeleting] = useState<boolean>(false)
+
+  const { deleteInstance } = useBackendInstanceMutateAPI()
+
+  if (!details.data) {
+    if (details.unauthorized) return onUnauthorized()
+    if (details.error) return onLoadError(details.error)
+    return <div>
+      loading ....
+    </div>
+  }
+
+  return <Fragment>
+    <DetailPage
+      isLoading={false}
+      selected={details.data} 
+      onUpdate={onUpdate}
+      onDelete={() => setDeleting(true) }
+    />
+    {deleting && <DeleteModal 
+      element={{name: details.data.name, id }} 
+      onCancel={() => setDeleting(false) } 
+      onConfirm={async (): Promise<void> => {
+        try {
+          await deleteInstance()
+          pushNotification({ message: 'delete_success', type: 'SUCCESS' })
+        } catch (error) {
+          pushNotification({ message: 'delete_error', type: 'ERROR', params: 
error })
+        }
+        setDeleting(false)
+    }}
+    />}
+
+  </Fragment>
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/index.tsx 
b/packages/frontend/src/routes/instances/index.tsx
deleted file mode 100644
index e44b087..0000000
--- a/packages/frontend/src/routes/instances/index.tsx
+++ /dev/null
@@ -1,97 +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/>
- */
-
-/**
-*
-* @author Sebastian Javier Marchano (sebasjm)
-*/
-
-import { h, VNode } from 'preact';
-import { View } from './View';
-import { LoginPage } from '../../components/auth';
-import { useBackendInstance, useBackendInstances } from '../../hooks/backend';
-import { useEffect, useState } from 'preact/hooks';
-import { Notification } from '../../declaration';
-import { CreatePage } from './CreatePage';
-import { UpdatePage } from './UpdatePage';
-
-interface Props {
-  pushNotification: (n: Notification) => void;
-}
-
-export default function Instances({ pushNotification }: Props): VNode {
-  const list = useBackendInstances()
-  const [selectedId, select] = useState<string | null>(null)
-  const details = useBackendInstance(selectedId)
-  const [create, setCreate] = useState<boolean>(false)
-  const [update, setUpdate] = useState<boolean>(false)
-
-
-  const requiresToken = (!list.data && list.needsAuth) || (selectedId != null 
&& !details.data && details.needsAuth)
-  const isLoadingTheList = (!list.data && !list.error)
-  const isLoadingTheDetails = (!details.data && !details.error)
-
-  const genericError = !list.data && list.error || !details.data && 
details.error
-
-  useEffect(() => {
-    if (requiresToken) {
-      pushNotification({ messageId: 'unauthorized', type: 'ERROR' })
-    } else if (genericError) {
-      pushNotification({ messageId: 'error', params: genericError, type: 
'ERROR' })
-    }
-  }, [requiresToken, genericError])
-
-
-  if (requiresToken) {
-    return <LoginPage />
-  }
-
-  if (create) {
-    return <CreatePage
-      goBack={() => setCreate(false)}
-      isLoading={false}
-
-      onCreate={(d): Promise<void> => list.create(d)
-        .then((): void => pushNotification({ messageId: 'create_success', 
type: 'SUCCESS' }))
-        .catch((error): void => pushNotification({ messageId: 'create_error', 
type: 'ERROR', params: error }))
-      }
-    />
-  }
-
-  if (update && details.data && selectedId) {
-    return <UpdatePage
-      goBack={() => setUpdate(false)}
-      isLoading={false}
-      selected={{ ...details.data, id: selectedId }}
-      onUpdate={(id, d): Promise<void> => details.update(id, d)
-        .then((): void => pushNotification({ messageId: 'update_success', 
type: 'SUCCESS' }))
-        .catch((error): void => pushNotification({ messageId: 'update_error', 
type: 'ERROR', params: error }))
-      }
-    />
-  }
-
-  return <View instances={list.data?.instances || []}
-    isLoading={isLoadingTheList || isLoadingTheDetails}
-    onCreate={setCreate}
-    onUpdate={setUpdate}
-    onDelete={(id): Promise<void> => details.delete(id)
-      .then((): void => pushNotification({ messageId: 'delete_success', type: 
'SUCCESS' }))
-      .catch((error): void => pushNotification({ messageId: 'delete_error', 
type: 'ERROR', params: error }))
-    }
-    onSelect={select}
-    selected={!details.data || !selectedId ? undefined : { ...details.data, 
id: selectedId }}
-  />;
-}
diff --git a/packages/frontend/src/routes/instances/CardTable.tsx 
b/packages/frontend/src/routes/instances/list/CardTable.tsx
similarity index 80%
rename from packages/frontend/src/routes/instances/CardTable.tsx
rename to packages/frontend/src/routes/instances/list/CardTable.tsx
index 2a4e9d5..53a6f54 100644
--- a/packages/frontend/src/routes/instances/CardTable.tsx
+++ b/packages/frontend/src/routes/instances/list/CardTable.tsx
@@ -20,17 +20,18 @@
 */
 
 import { h, VNode } from "preact";
-import { Text } from "preact-i18n";
+import { Message } from "preact-messages";
 import { useEffect, useState } from "preact/hooks";
-import { MerchantBackend, WidthId as WithId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import { EmptyTable } from "./EmptyTable";
 import { Table } from "./Table";
 
 interface Props {
   instances: MerchantBackend.Instances.Instance[];
-  onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+  onUpdate: (id: string) => void;
+  onDelete: (id: MerchantBackend.Instances.Instance) => void;
   onCreate: () => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WithId | 
undefined;
+  selected?: boolean;
 }
 
 interface Actions {
@@ -48,28 +49,28 @@ function buildActions(intances: 
MerchantBackend.Instances.Instance[], selected:
     .map(id => ({ element: id, type: action }))
 }
 
-export function CardTable({ instances, onCreate, onSelect, selected }: Props): 
VNode {
+export function CardTable({ instances, onCreate, onUpdate, onDelete, selected 
}: Props): VNode {
   const [actionQueue, actionQueueHandler] = useState<Actions[]>([]);
   const [rowSelection, rowSelectionHandler] = useState<string[]>([])
 
   useEffect(() => {
     if (actionQueue.length > 0 && !selected && actionQueue[0].type == 
'DELETE') {
-      onSelect(actionQueue[0].element.id, 'DELETE')
+      onDelete(actionQueue[0].element)
       actionQueueHandler(actionQueue.slice(1))
     }
-  }, [actionQueue, selected, onSelect])
+  }, [actionQueue, selected, onDelete])
 
   useEffect(() => {
     if (actionQueue.length > 0 && !selected && actionQueue[0].type == 
'UPDATE') {
-      onSelect(actionQueue[0].element.id, 'UPDATE')
+      onUpdate(actionQueue[0].element.id)
       actionQueueHandler(actionQueue.slice(1))
     }
-  }, [actionQueue, selected, onSelect])
+  }, [actionQueue, selected, onUpdate])
 
 
   return <div class="card has-table">
     <header class="card-header">
-      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Text id="text.instances" /></p>
+      <p class="card-header-title"><span class="icon"><i class="mdi 
mdi-account-multiple" /></span><Message id="Instances" /></p>
 
       <div class="card-header-icon" aria-label="more options">
 
@@ -89,7 +90,7 @@ export function CardTable({ instances, onCreate, onSelect, 
selected }: Props): V
       <div class="b-table has-pagination">
         <div class="table-wrapper has-mobile-cards">
           {instances.length > 0 ?
-            <Table instances={instances} onSelect={onSelect} 
rowSelection={rowSelection} rowSelectionHandler={rowSelectionHandler} /> :
+            <Table instances={instances} onUpdate={onUpdate} 
onDelete={onDelete} rowSelection={rowSelection} 
rowSelectionHandler={rowSelectionHandler} /> :
             <EmptyTable />
           }
         </div>
diff --git a/packages/frontend/src/routes/instances/DeleteModal.tsx 
b/packages/frontend/src/routes/instances/list/DeleteModal.tsx
similarity index 76%
rename from packages/frontend/src/routes/instances/DeleteModal.tsx
rename to packages/frontend/src/routes/instances/list/DeleteModal.tsx
index a682d12..f526456 100644
--- a/packages/frontend/src/routes/instances/DeleteModal.tsx
+++ b/packages/frontend/src/routes/instances/list/DeleteModal.tsx
@@ -20,17 +20,16 @@
 */
 
 import { h, VNode } from "preact";
-import { MerchantBackend, WidthId } from "../../declaration";
-import { ConfirmModal } from "../../components/modal";
+import { ConfirmModal } from "../../../components/modal";
 
 interface Props {
-  element: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+  element: {id: string, name: string};
   onCancel: () => void;
-  onConfirm: (i: MerchantBackend.Instances.QueryInstancesResponse & WidthId) 
=> void;
+  onConfirm: (id: string) => void;
 }
 
 export function DeleteModal({ element, onCancel, onConfirm }: Props): VNode {
-  return <ConfirmModal description="delete_instance" danger active 
onCancel={onCancel} onConfirm={() => onConfirm(element)}>
+  return <ConfirmModal description="delete_instance" danger active 
onCancel={onCancel} onConfirm={() => onConfirm(element.id)}>
     <p>This will permanently delete instance "{element.name}" with id 
<b>{element.id}</b></p>
     <p>Please confirm this action</p>
   </ConfirmModal>
diff --git a/packages/frontend/src/routes/instances/EmptyTable.tsx 
b/packages/frontend/src/routes/instances/list/EmptyTable.tsx
similarity index 84%
rename from packages/frontend/src/routes/instances/EmptyTable.tsx
rename to packages/frontend/src/routes/instances/list/EmptyTable.tsx
index 5a0af48..154e3a8 100644
--- a/packages/frontend/src/routes/instances/EmptyTable.tsx
+++ b/packages/frontend/src/routes/instances/list/EmptyTable.tsx
@@ -20,13 +20,14 @@
  */
 
 import { h, VNode } from "preact";
-import { Text } from "preact-i18n";
+import { useMessageTemplate } from "preact-messages";
+import { Message } from "preact-messages";
 
 export function EmptyTable(): VNode {
   return <div class="content has-text-grey has-text-centered">
     <p>
       <span class="icon is-large"><i class="mdi mdi-emoticon-sad mdi-48px" 
/></span>
     </p>
-    <p><Text id="text.instance.empty_list" /></p>
+    <p><Message id="There is no instances yet, add more pressing the + sign" 
/></p>
   </div>
 }
diff --git a/packages/frontend/src/routes/instances/Table.tsx 
b/packages/frontend/src/routes/instances/list/Table.tsx
similarity index 82%
rename from packages/frontend/src/routes/instances/Table.tsx
rename to packages/frontend/src/routes/instances/list/Table.tsx
index 4faf6b5..a7298ef 100644
--- a/packages/frontend/src/routes/instances/Table.tsx
+++ b/packages/frontend/src/routes/instances/list/Table.tsx
@@ -20,14 +20,15 @@
  */
 
 import { h, VNode } from "preact"
-import { Text } from "preact-i18n"
+import { Message } from "preact-messages"
 import { StateUpdater } from "preact/hooks"
-import { MerchantBackend } from "../../declaration"
+import { MerchantBackend } from "../../../declaration"
 
 interface Props {
   rowSelection: string[];
   instances: MerchantBackend.Instances.Instance[];
-  onSelect: (id: string | null, action: 'UPDATE' | 'DELETE') => void;
+  onUpdate: (id: string) => void;
+  onDelete: (id: MerchantBackend.Instances.Instance) => void;
   rowSelectionHandler: StateUpdater<string[]>;
 }
 
@@ -35,7 +36,7 @@ function toggleSelected<T>(id: T): (prev: T[]) => T[] {
   return (prev: T[]): T[] => prev.indexOf(id) == -1 ? [...prev, id] : 
prev.filter(e => e != id)
 }
 
-export function Table({ rowSelection, rowSelectionHandler, instances, onSelect 
}: Props): VNode {
+export function Table({ rowSelection, rowSelectionHandler, instances, 
onUpdate, onDelete }: Props): VNode {
   return (
     <table class="table is-fullwidth is-striped is-hoverable is-fullwidth">
       <thead>
@@ -46,10 +47,10 @@ export function Table({ rowSelection, rowSelectionHandler, 
instances, onSelect }
               <span class="check" />
             </label>
           </th>
-          <th><Text id="fields.instance.id.label" /></th>
-          <th><Text id="fields.instance.name.label" /></th>
-          <th><Text id="fields.instance.merchant_pub.label" /></th>
-          <th><Text id="fields.instance.payment_targets.label" /></th>
+          <th><Message id="fields_instance_id_label" /></th>
+          <th><Message id="fields_instance_name_label" /></th>
+          <th><Message id="fields_instance_merchant_pub_label" /></th>
+          <th><Message id="fields_instance_payment_targets_label" /></th>
           <th />
         </tr>
       </thead>
@@ -68,10 +69,10 @@ export function Table({ rowSelection, rowSelectionHandler, 
instances, onSelect }
             <td >{i.payment_targets}</td>
             <td class="is-actions-cell">
               <div class="buttons is-right">
-                <button class="button is-small is-primary" type="button" 
onClick={(): void => onSelect(i.id, 'UPDATE')}>
+                <button class="button is-small is-primary" type="button" 
onClick={(): void => onUpdate(i.id)}>
                   <span class="icon"><i class="mdi mdi-eye" /></span>
                 </button>
-                <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onSelect(i.id, 'DELETE')}>
+                <button class="button is-small is-danger jb-modal" 
type="button" onClick={(): void => onDelete(i)}>
                   <span class="icon"><i class="mdi mdi-trash-can" /></span>
                 </button>
               </div>
diff --git a/packages/frontend/src/routes/instances/View.stories.tsx 
b/packages/frontend/src/routes/instances/list/View.stories.tsx
similarity index 100%
rename from packages/frontend/src/routes/instances/View.stories.tsx
rename to packages/frontend/src/routes/instances/list/View.stories.tsx
diff --git a/packages/frontend/src/routes/instances/View.tsx 
b/packages/frontend/src/routes/instances/list/View.tsx
similarity index 58%
rename from packages/frontend/src/routes/instances/View.tsx
rename to packages/frontend/src/routes/instances/list/View.tsx
index 438e54f..f54e64a 100644
--- a/packages/frontend/src/routes/instances/View.tsx
+++ b/packages/frontend/src/routes/instances/list/View.tsx
@@ -20,30 +20,20 @@
 */
 
 import { h, VNode } from "preact";
-import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import { CardTable } from './CardTable';
-import { DeleteModal } from './DeleteModal'
-import { Text } from "preact-i18n";
+import { Message } from "preact-messages";
 
 interface Props {
   instances: MerchantBackend.Instances.Instance[];
-  onCreate: (s: boolean) => void;
-  onUpdate: (s: boolean) => void;
-  onDelete: (id: string) => void;
-  onSelect: (id: string | null) => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId | 
undefined;
+  onCreate: () => void;
+  onUpdate: (id: string) => void;
+  onDelete: (id: MerchantBackend.Instances.Instance) => void;
+  selected?: boolean;
   isLoading: boolean;
 }
 
-export function View({ instances, isLoading, onCreate, onDelete, onSelect, 
onUpdate, selected }: Props): VNode {
-  const [action, setAction] = useState<'UPDATE' | 'DELETE' | null>(null)
-
-  const onSelectAction = (id: string | null, action?: 'UPDATE' | 'DELETE'): 
void => {
-    onSelect(id)
-    setAction(action || null)
-    if (action === 'UPDATE') onUpdate(true)
-  }
+export function View({ instances, isLoading, onCreate, onDelete, onUpdate, 
selected }: Props): VNode {
 
   return <div id="app">
     <section class="section is-title-bar">
@@ -52,8 +42,8 @@ export function View({ instances, isLoading, onCreate, 
onDelete, onSelect, onUpd
         <div class="level-left">
           <div class="level-item">
             <ul>
-              <li><Text id="text.merchant" /></li>
-              <li><Text id="text.instances" /></li>
+              <li><Message id="Merchant" /></li>
+              <li><Message id="Instances" /></li>
             </ul>
           </div>
         </div>
@@ -66,7 +56,7 @@ export function View({ instances, isLoading, onCreate, 
onDelete, onSelect, onUpd
           <div class="level-left">
             <div class="level-item">
               <h1 class="title">
-                <Text id="text.list_of_configured_instances" />
+                <Message id="List of configured instances" />
               </h1>
             </div>
           </div>
@@ -77,16 +67,8 @@ export function View({ instances, isLoading, onCreate, 
onDelete, onSelect, onUpd
       </div>
     </section>
     <section class="section is-main-section">
-      <CardTable instances={instances} onSelect={onSelectAction} 
selected={selected} onCreate={(): void => onCreate(true)} />
+      <CardTable instances={instances} onDelete={onDelete} onUpdate={onUpdate} 
selected={selected} onCreate={onCreate} />
     </section>
 
-    {selected && action === 'DELETE' ?
-      <DeleteModal element={selected} onCancel={(): void => 
onSelectAction(null)} onConfirm={(i): void => {
-        onDelete(i.id)
-        onSelectAction(null);
-      }}
-      />
-      : null}
-
   </div >
 }
\ No newline at end of file
diff --git a/packages/frontend/src/routes/instances/list/index.tsx 
b/packages/frontend/src/routes/instances/list/index.tsx
new file mode 100644
index 0000000..ba63b3e
--- /dev/null
+++ b/packages/frontend/src/routes/instances/list/index.tsx
@@ -0,0 +1,71 @@
+/*
+ This file is part of GNU Taler
+ (C) 2021 Taler Systems S.A.
+
+ GNU Taler is free software; you can redistribute it and/or modify it under the
+ terms of the GNU General Public License as published by the Free Software
+ Foundation; either version 3, or (at your option) any later version.
+
+ GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
+ A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License along with
+ GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
+ */
+
+/**
+*
+* @author Sebastian Javier Marchano (sebasjm)
+*/
+
+import { Fragment, h, VNode } from 'preact';
+import { View } from './View';
+import { useBackendInstances, useBackendInstanceMutateAPI, SwrError } from 
'../../../hooks/backend';
+import { useState } from 'preact/hooks';
+import { MerchantBackend, Notification } from '../../../declaration';
+import { DeleteModal } from './DeleteModal';
+interface Props {
+  pushNotification: (n: Notification) => void;
+  onUnauthorized: () => VNode;
+  onError: (e: SwrError) => VNode;
+  onCreate: () => void;
+  onUpdate: (id: string) => void;
+}
+
+export default function Instances({ pushNotification, onUnauthorized, onError, 
onCreate, onUpdate }: Props): VNode {
+  const list = useBackendInstances()
+  const [deleting, setDeleting] = useState<MerchantBackend.Instances.Instance 
| null>(null)
+  const { deleteInstance } = useBackendInstanceMutateAPI()
+
+  const isLoadingTheList = (!list.data && !list.error)
+  const error = !list.data && list.error
+
+  if (!list.data) {
+    if (list.unauthorized) return onUnauthorized()
+    if (list.error) return onError(list.error)    
+  }
+
+  return <Fragment>
+    <View instances={list.data?.instances || []}
+      isLoading={isLoadingTheList}
+      onDelete={setDeleting}
+      onCreate={onCreate}
+      onUpdate={onUpdate}
+      selected={!!deleting}
+    />
+    {deleting && <DeleteModal 
+      element={deleting} 
+      onCancel={() => setDeleting(null) } 
+      onConfirm={async (): Promise<void> => {
+        try {
+          await deleteInstance()
+          pushNotification({ message: 'delete_success', type: 'SUCCESS' })
+        } catch (e) {
+          pushNotification({ message: 'delete_error', type: 'ERROR', params: 
error })
+        }
+        setDeleting(null)
+    }}
+    />}
+  </Fragment>;
+}
diff --git a/packages/frontend/src/routes/instances/UpdatePage.tsx 
b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
similarity index 80%
rename from packages/frontend/src/routes/instances/UpdatePage.tsx
rename to packages/frontend/src/routes/instances/update/UpdatePage.tsx
index feed125..8cbd2d6 100644
--- a/packages/frontend/src/routes/instances/UpdatePage.tsx
+++ b/packages/frontend/src/routes/instances/update/UpdatePage.tsx
@@ -21,17 +21,17 @@
 
 import { h, VNode } from "preact";
 import { useState } from "preact/hooks";
-import { MerchantBackend, WidthId } from "../../declaration";
+import { MerchantBackend } from "../../../declaration";
 import * as yup from 'yup';
-import { YupField } from "../../components/yup/YupField"
-import { InstanceUpdateSchema as schema } from '../../schemas'
-import { Text } from "preact-i18n";
+import { YupField } from "../../../components/yup/YupField"
+import { InstanceUpdateSchema as schema } from '../../../schemas'
+import { Message } from "preact-messages";
 
 interface Props {
-  onUpdate: (id: string, d: 
MerchantBackend.Instances.InstanceReconfigurationMessage) => void;
-  selected: MerchantBackend.Instances.QueryInstancesResponse & WidthId;
+  onUpdate: (d: MerchantBackend.Instances.InstanceReconfigurationMessage) => 
void;
+  selected: MerchantBackend.Instances.QueryInstancesResponse;
   isLoading: boolean;
-  goBack: () => void;
+  onBack: () => void;
 }
 
 interface KeyValue {
@@ -49,15 +49,15 @@ function convert(from: 
MerchantBackend.Instances.QueryInstancesResponse): Mercha
   return { ...defaults, ...rest, payto_uris };
 }
 
-export function UpdatePage({ onUpdate, isLoading, selected, goBack }: Props): 
VNode {
+export function UpdatePage({ onUpdate, isLoading, selected, onBack }: Props): 
VNode {
   const [value, valueHandler] = useState(convert(selected))
   const [errors, setErrors] = useState<KeyValue>({})
 
   const submit = (): void => {
     try {
       schema.validateSync(value, { abortEarly: false })
-      onUpdate(selected.id, schema.cast(value));
-      goBack()
+      onUpdate(schema.cast(value));
+      onBack()
     } catch (err) {
       const errors = err.inner as yup.ValidationError[]
       const pathMessages = errors.reduce((prev, cur) => !cur.path ? prev : ({ 
...prev, [cur.path]: { type: cur.type, params: cur.params, message: cur.message 
} }), {})
@@ -72,8 +72,8 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
         <div class="level-left">
           <div class="level-item">
             <ul>
-              <li><Text id="text.merchant" /></li>
-              <li><Text id="text.instances" /></li>
+              <li><Message id="Merchant" /></li>
+              <li><Message id="Instances" /></li>
             </ul>
           </div>
         </div>
@@ -86,7 +86,7 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
           <div class="level-left">
             <div class="level-item">
               <h1 class="title">
-                <Text id="text.create_new_instances" />
+                <Message id="Update this instance" />
               </h1>
             </div>
           </div>
@@ -107,8 +107,8 @@ export function UpdatePage({ onUpdate, isLoading, selected, 
goBack }: Props): VN
               valueHandler={valueHandler} info={schema.fields[f].describe()}
             />)}
          <div class="buttons is-right">
-            <button class="button" onClick={goBack} ><Text id="cancel" 
/></button>
-            <button class="button is-success" onClick={submit} ><Text 
id="confirm" /></button>
+            <button class="button" onClick={onBack} ><Message id="Cancel" 
/></button>
+            <button class="button is-success" onClick={submit} ><Message 
id="Confirm" /></button>
           </div>
         </div>
         <div class="column" />
diff --git a/packages/frontend/src/routes/instances/update/index.tsx 
b/packages/frontend/src/routes/instances/update/index.tsx
new file mode 100644
index 0000000..ecaad37
--- /dev/null
+++ b/packages/frontend/src/routes/instances/update/index.tsx
@@ -0,0 +1,36 @@
+import { h, VNode } from "preact";
+import { MerchantBackend } from "../../../declaration";
+import { SwrError, useBackendInstance, useBackendInstanceMutateAPI } from 
"../../../hooks/backend";
+import { UpdatePage } from "./UpdatePage";
+
+interface Props {
+  onBack: () => void;
+  onConfirm: () => void;
+  pushNotification: (n: Notification) => void;
+
+  onUnauthorized: () => VNode;
+  onLoadError: (e: SwrError) => VNode;
+  onUpdateError: (e: Error) => void;
+
+}
+
+export default function Update({ onBack, onConfirm, onLoadError, 
onUpdateError, onUnauthorized }: Props): VNode {
+  const { updateInstance } = useBackendInstanceMutateAPI();
+  const details = useBackendInstance()
+
+  if (!details.data) {
+    if (details.unauthorized) return onUnauthorized()
+    if (details.error) return onLoadError(details.error)
+    return <div>
+      loading ....
+    </div>  
+  }
+
+  return <UpdatePage 
+    onBack={onBack}
+    isLoading={false}
+    selected={details.data}
+    onUpdate={(d: MerchantBackend.Instances.InstanceReconfigurationMessage): 
Promise<void> => {
+      return updateInstance(d).then(onConfirm).catch(onUpdateError)
+  }} />
+}
\ No newline at end of file
diff --git a/packages/frontend/src/routes/login/index.tsx 
b/packages/frontend/src/routes/login/index.tsx
new file mode 100644
index 0000000..b7aa67a
--- /dev/null
+++ b/packages/frontend/src/routes/login/index.tsx
@@ -0,0 +1,11 @@
+import { h, VNode } from "preact";
+import { LoginModal } from '../../components/auth';
+import { Notification } from "../../declaration";
+
+interface Props {
+  withMessage?: Notification;
+  onConfirm: (url: string, token?: string) => void;
+}
+export default function LoginPage({ onConfirm, withMessage }: Props): VNode {
+  return <LoginModal onConfirm={onConfirm} withMessage={withMessage} />
+} 
\ No newline at end of file
diff --git a/packages/frontend/src/schemas/index.ts 
b/packages/frontend/src/schemas/index.ts
index d7c6cbc..b047bc8 100644
--- a/packages/frontend/src/schemas/index.ts
+++ b/packages/frontend/src/schemas/index.ts
@@ -69,27 +69,27 @@ const InstanceSchema = yup.object().shape({
     .required(),
   address: yup.object().shape({
     country: yup.string().optional(),
-    country_subdivision: yup.string().optional(),
-    district: yup.string().optional(),
-    town: yup.string(),
-    town_location: yup.string().optional(),
-    post_code: yup.string().optional(),
-    street: yup.string().optional(),
-    building_name: yup.string().optional(),
-    building_number: yup.string().optional(),
     address_lines: yup.array().of(yup.string()).max(7).optional(),
+    building_number: yup.string().optional(),
+    building_name: yup.string().optional(),
+    street: yup.string().optional(),
+    post_code: yup.string().optional(),
+    town_location: yup.string().optional(),
+    town: yup.string(),
+    district: yup.string().optional(),
+    country_subdivision: yup.string().optional(),
   }).meta({type:'group'}),
   jurisdiction: yup.object().shape({
     country: yup.string().optional(),
-    country_subdivision: yup.string().optional(),
-    district: yup.string().optional(),
-    town: yup.string(),
-    town_location: yup.string().optional(),
-    post_code: yup.string().optional(),
-    street: yup.string().optional(),
-    building_name: yup.string().optional(),
-    building_number: yup.string().optional(),
     address_lines: yup.array().of(yup.string()).max(7).optional(),
+    building_number: yup.string().optional(),
+    building_name: yup.string().optional(),
+    street: yup.string().optional(),
+    post_code: yup.string().optional(),
+    town_location: yup.string().optional(),
+    town: yup.string(),
+    district: yup.string().optional(),
+    country_subdivision: yup.string().optional(),
   }).meta({type:'group'}),
   default_pay_delay: yup.object()
     .shape({ d_ms: yup.number() })
diff --git a/packages/frontend/src/scss/main.scss 
b/packages/frontend/src/scss/main.scss
index 4696247..9e9e5c0 100644
--- a/packages/frontend/src/scss/main.scss
+++ b/packages/frontend/src/scss/main.scss
@@ -48,17 +48,16 @@
 @import 
"../../node_modules/@creativebulma/bulma-tooltip/dist/bulma-tooltip.min.css";
 
 .toast {
-  position: fixed;
-  right: 10px;
-  top: 10px;
+  position: absolute;
+  width: 60%;
+  margin-left: 10%;
+  margin-right: 10%;
   z-index: 999;
 
   display: flex;
   flex-direction: column;
   padding: 15px;
-  text-align: right;
-  align-items: flex-end;
-  width: auto;
+  text-align: center;
   pointer-events: none;
 }
 
diff --git a/packages/frontend/tests/header.test.tsx 
b/packages/frontend/tests/header.test.tsx
index 04932b7..2eefda3 100644
--- a/packages/frontend/tests/header.test.tsx
+++ b/packages/frontend/tests/header.test.tsx
@@ -20,14 +20,13 @@
 */
 
 import { h } from 'preact';
-import { Footer } from '../src/components/footer';
+import { Sidebar } from '../src/components/sidebar';
 // See: https://github.com/preactjs/enzyme-adapter-preact-pure
 import { shallow } from 'enzyme';
 
-describe('Initial Test of the Footer', () => {
-    test('Footer renders an anchor with Taler text', () => {
-        const context = shallow(<Footer />);
-        expect(context.find('a').text()).toBe('Taler');
-        expect(context.find('footer').length).toBe(1);
+describe('Initial Test of the Sidebar', () => {
+    test('Sidbar renders anchors with text', () => {
+        const context = shallow(<Sidebar />);
+        expect(context.find('a').map( a => a.text())).toEqual(["Instances", 
"Details", "Orders", "Inventory", "Tipping", "About"]);
     });
 });
diff --git a/packages/frontend/tests/hooks/notification.test.ts 
b/packages/frontend/tests/hooks/notification.test.ts
index 1fd248f..6825a82 100644
--- a/packages/frontend/tests/hooks/notification.test.ts
+++ b/packages/frontend/tests/hooks/notification.test.ts
@@ -28,13 +28,13 @@ test('notification should disapear after timeout', () => {
   jest.spyOn(global, 'setTimeout');
 
   const timeout = 1000
-  const { result, rerender } = renderHook(() => useNotifications(timeout));
+  const { result, rerender } = renderHook(() => useNotifications(undefined, 
timeout));
 
   expect(result.current?.notifications.length).toBe(0);
 
   act(() => {
     result.current?.pushNotification({
-      messageId: 'some_id',
+      message: 'some_id',
       type: 'INFO'
     });
   });
diff --git a/packages/preact-message/.gitignore 
b/packages/preact-message/.gitignore
new file mode 100644
index 0000000..ad09c5f
--- /dev/null
+++ b/packages/preact-message/.gitignore
@@ -0,0 +1,2 @@
+/example/dist/
+/lib/
diff --git a/packages/preact-message/CHANGELOG.md 
b/packages/preact-message/CHANGELOG.md
new file mode 100644
index 0000000..4681441
--- /dev/null
+++ b/packages/preact-message/CHANGELOG.md
@@ -0,0 +1,16 @@
+# Change Log
+
+All notable changes to this project will be documented in this file.
+See [Conventional Commits](https://conventionalcommits.org) for commit 
guidelines.
+
+# 1.0.0-beta.1 (2020-11-29)
+
+
+### Features
+
+* Add @messageformat/react (was react-message-context) 
([#292](https://github.com/messageformat/messageformat/issues/292)) 
([9089f0a](https://github.com/messageformat/messageformat/commit/9089f0ad52f21f8ab6c356fd4f51bb140dc36855))
+
+
+# 0.6.2 and earlier
+
+For earlier changes, see 
https://github.com/eemeli/react-message-context/releases
diff --git a/packages/preact-message/LICENSE b/packages/preact-message/LICENSE
new file mode 100644
index 0000000..78918d5
--- /dev/null
+++ b/packages/preact-message/LICENSE
@@ -0,0 +1,20 @@
+Copyright OpenJS Foundation and contributors, https://openjsf.org/
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/packages/preact-message/README.md 
b/packages/preact-message/README.md
new file mode 100644
index 0000000..a52d5b3
--- /dev/null
+++ b/packages/preact-message/README.md
@@ -0,0 +1,126 @@
+# @messageformat/react
+
+An efficient React front-end for message formatting libraries.
+Designed in particular for use with [messageformat], but will work with any 
messages.
+Provides the best possible API for a front-end developer, without making the 
back end any more difficult than it needs to be either.
+Should add at most about 1kB to your compiled & minified bundle size.
+
+This package was previously named 
[react-message-context](https://www.npmjs.com/package/react-message-context).
+
+[messageformat]: https://messageformat.github.io
+
+## Installation
+
+```
+npm install @messageformat/react
+```
+
+The library has React 16.8 or later as a peer dependency.
+It is published as an **ES module** only, which should work directly with 
almost all tools and environments that support modern development targeting 
browser environments.
+For tools such as Jest that define their own import methods, you may need to 
add something like `transformIgnorePatterns: 
['node_modules/(?!@messageformat/react)']` to your configuration.
+
+## [API Documentation]
+
+- [`<MessageProvider messages [locale] [onError] 
[pathSep]>`](http://messageformat.github.io/messageformat/api/react.messageprovider/)
+- [`<Message id [locale] [props] 
[...msgProps]>`](http://messageformat.github.io/messageformat/api/react.message/)
+- 
[`useLocales()`](http://messageformat.github.io/messageformat/api/react.uselocales/)
+- [`useMessage(id, [params], 
[locale])`](http://messageformat.github.io/messageformat/api/react.usemessage/)
+- [`useMessageGetter(rootId, [{ baseParams, locale 
}])`](http://messageformat.github.io/messageformat/api/react.usemessagegetter/)
+
+## Usage Examples
+
+In addition to the examples included below and in the [API documentation], see 
the [example] for a simple, but fully functional example of using this library 
along with [@messageformat/core] and [@messageformat/loader] to handle 
localized messages, with dynamic loading of non-default locales.
+
+[api documentation]: http://messageformat.github.io/messageformat/api/react/
+[example]: 
https://github.com/messageformat/messageformat/tree/master/packages/react/example
+[@messageformat/core]: https://www.npmjs.com/package/@messageformat/core
+[@messageformat/loader]: https://www.npmjs.com/package/@messageformat/loader
+
+---
+
+Within a `MessageProvider`, access to the messages is possible using either 
the `Message` component, or via custom hooks such as `useMessageGetter`:
+
+```js
+import React from 'preact';
+import {
+  Message,
+  MessageProvider,
+  useMessageGetter
+} from '@messageformat/react';
+
+const messages = {
+  message: 'Your message is important',
+  answers: {
+    sixByNine: ({ base }) => (6 * 9).toString(base),
+    universe: 42
+  }
+};
+
+function Equality() {
+  const getAnswer = useMessageGetter('answers');
+  const foo = getAnswer('sixByNine', { base: 13 });
+  const bar = getAnswer('universe');
+  return `${foo} and ${bar} are equal`;
+}
+
+export const Example = () => (
+  <MessageProvider messages={messages}>
+    <ul>
+      <li>
+        <Message id="message" />
+      </li>
+      <li>
+        <Equality />
+      </li>
+    </ul>
+  </MessageProvider>
+);
+
+// Will render as:
+//   - Your message is important
+//   - 42 and 42 are equal
+```
+
+---
+
+Using MessageProviders within each other allows for multiple locales and 
namespaces:
+
+```jsx
+import React from 'preact';
+import { Message, MessageProvider } from '@messageformat/react';
+
+export const Example = () => (
+  <MessageProvider locale="en" messages={{ foo: 'FOO', qux: 'QUX' }}>
+    <MessageProvider locale="fi" messages={{ foo: 'FÖÖ', bar: 'BÄR' }}>
+      <ul>
+        <li>
+          <Message id="foo" />
+        </li>
+        <li>
+          <Message id="foo" locale="en" />
+        </li>
+        <li>
+          <Message id="bar" />
+        </li>
+        <li>
+          <Message id="bar" locale="en" />
+        </li>
+        <li>
+          <Message id="qux" />
+        </li>
+        <li>
+          <Message id="quux">xyzzy</Message>
+        </li>
+      </ul>
+    </MessageProvider>
+  </MessageProvider>
+);
+
+// Will render as:
+// - FÖÖ
+// - FOO
+// - BÄR
+// - bar  (uses fallback to key)
+// - QUX  (uses fallback to "en" locale)
+// - xyzzy  (uses fallback to child node)
+```
diff --git a/packages/preact-message/package.json 
b/packages/preact-message/package.json
new file mode 100644
index 0000000..47bc250
--- /dev/null
+++ b/packages/preact-message/package.json
@@ -0,0 +1,39 @@
+{
+  "name": "preact-messages",
+  "version": "1.0.0-beta.1",
+  "description": "PReact hooks and other bindings for messages",
+  "keywords": [
+    "i18n",
+    "preact",
+    "context",
+    "messages",
+    "messageformat",
+    "provider"
+  ],
+  "contributors": [
+    "Eemeli Aro <eemeli@gmail.com>"
+  ],
+  "license": "MIT",
+  "homepage": "http://messageformat.github.io/messageformat/api/react/";,
+  "main": "lib/index.js",
+  "types": "lib/index.d.ts",
+  "exports": {
+    ".": "./lib/index.js",
+    "./package.json": "./package.json"
+  },
+  "files": [
+    "lib/"
+  ],
+  "sideEffects": false,
+  "scripts": {
+    "build": "tsc",
+    "extract-api": "api-extractor run --local --verbose"
+  },
+  "peerDependencies": {
+    "preact": ">=10.3.0"
+  },
+  "dependencies": {
+    "preact": "^10.5.12",
+    "typescript": "^4.1.5"
+  }
+}
diff --git a/packages/preact-message/src/MessageProvider.ts 
b/packages/preact-message/src/MessageProvider.ts
new file mode 100644
index 0000000..cb72622
--- /dev/null
+++ b/packages/preact-message/src/MessageProvider.ts
@@ -0,0 +1,183 @@
+import { createElement } from 'preact';
+import { useContext, useMemo } from 'preact/hooks';
+import { MessageContext, MessageObject, defaultValue } from 
'./message-context';
+// import { MessageProviderProps, getPathSep, getLocales, getMessages, 
getOnError } from './message-provider';
+import { MessageError, ErrorCode, errorMessages } from './message-error';
+
+/**
+ * `<MessageProvider messages [locale] [merge] [onError] [pathSep]>`
+ *
+ * Makes the messages available for its descendants via a React Context.
+ * To support multiple locales and/or namespaces, MessageProviders may be used 
within each other, merging each provider's messages with those of its parents.
+ * The locale preference order is also set similarly, from nearest to furthest.
+ *
+ * @public
+ *
+ * @example
+ * ```js
+ * import React from 'preact'
+ * import { Message, MessageProvider } from '@messageformat/react'
+ *
+ * const messages = { example: { key: 'Your message here' } }
+ * const extended = { other: { key: 'Another message' } }
+ *
+ * const Example = () => (
+ *   <span>
+ *     <Message id={['example', 'key']} />
+ *     {' | '}
+ *     <Message id="other/key" />
+ *   </span>
+ * ) // 'Your message here | Another message'
+ *
+ * export const App = () => (
+ *   <MessageProvider messages={messages} pathSep="/">
+ *     <MessageProvider messages={extended}>
+ *       <Example />
+ *     </MessageProvider>
+ *   </MessageProvider>
+ * )
+ * ```
+ */
+
+export function MessageProvider(props: MessageProviderProps) {
+  const {
+    children,
+    context: propContext,
+    debug,
+    locale = '',
+    merge,
+    messages,
+    onError,
+    pathSep
+  } = props;
+  let parent = useContext(MessageContext);
+  if (propContext)
+    parent = propContext;
+  else if (propContext === null)
+    parent = defaultValue;
+  const value: MessageContext = useMemo(() => {
+    const ps = getPathSep(parent, pathSep);
+    return {
+      locales: getLocales(parent, locale),
+      merge: merge || parent.merge,
+      messages: getMessages(parent, locale, messages),
+      onError: getOnError(parent, ps, onError, debug),
+      pathSep: ps
+    };
+  }, [parent, locale, merge, messages, pathSep]);
+  return createElement(MessageContext.Provider, { value } as any, children);
+}
+
+
+
+/** @public */
+export interface MessageProviderProps {
+  children: any;
+
+  /**
+   * A hierarchical object containing the messages as boolean, number, string 
or function values.
+   */
+  messages: MessageObject;
+  context?: MessageContext;
+
+  /** @deprecated Use onError instead */
+  debug?: 'error' | 'warn' | ((msg: string) => any);
+
+  /**
+   * A key for the locale of the given messages.
+   * If uset, will inherit the locale from the parent context, or ultimately 
use en empty string.
+   */
+  locale?: string;
+
+  /**
+   * By default, top-level namespaces defined in a child `MessageProvider` 
overwrite those defined in a parent.
+   * Set this to {@link https://lodash.com/docs/#merge | _.merge} or some 
other function with the same arguments as
+   * {@link 
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign
 | Object.assign} to allow for deep merges.
+   */
+  merge?: MessageContext['merge'];
+
+  /**
+   * What to do on errors; most often called if a message is not found.
+   *
+   * - `"silent"`: Ignore the error; use the message's id as the replacement 
message.
+   *
+   * - `"error"`: Throw the error.
+   *
+   * - `"warn"` (default): Print a warning in the console and use the 
message's id as the replacement message.
+   *
+   * - `(error) => any`: A custom function that is called with an `Error` 
object with `code: string` and `path: string[]` fields set.
+   *   The return falue is used as the replacement message.
+   */
+  onError?: 'error' | 'silent' | 'warn' | ((error: MessageError) => any);
+
+  /**
+   * By default, `.` in a `<Message id>` splits the path into parts, such that 
e.g. `'a.b'` is equivalent to `['a', 'b']`.
+   * Use this option to customize or disable this behaviour (by setting it to 
`null`).
+   */
+  pathSep?: string;
+}
+
+export function getOnError(
+  parent: MessageContext,
+  pathSep: string | null,
+  onError: MessageProviderProps['onError'],
+  debug: MessageProviderProps['debug']
+) {
+  const asId = (path: string[]) => path.join(pathSep || ',');
+  function msgError(path: string[], code: ErrorCode) {
+    throw new MessageError(path, code, asId);
+  }
+  function msgWarning(path: string[], code: ErrorCode) {
+    console.warn(errorMessages[code], path);
+    return asId(path);
+  }
+
+  if (onError === undefined) {
+    // debug is deprecated, will be removed later
+    if (typeof debug === 'function')
+      return (path: string[], code: ErrorCode) =>
+        debug(`${errorMessages[code]}: ${asId(path)}`);
+    onError = debug;
+  }
+
+  switch (onError) {
+    case 'silent':
+      return asId;
+    case 'error':
+      return msgError;
+    case 'warn':
+      return msgWarning;
+    default:
+      if (typeof onError === 'function') {
+        const _onError = onError;
+        return (path: string[], code: ErrorCode) =>
+          _onError(new MessageError(path, code, asId));
+      }
+      return parent.onError || msgWarning;
+  }
+}
+
+export function getLocales({ locales }: MessageContext, locale: string) {
+  const fallback = locales.filter(fb => fb !== locale);
+  return [locale].concat(fallback);
+}
+
+export function getMessages(
+  { merge, messages }: MessageContext,
+  locale: string,
+  lcMessages: MessageObject
+) {
+  const res = Object.assign({}, messages);
+  const prev = res[locale];
+  res[locale] =
+    prev && typeof prev === 'object' ? merge({}, prev, lcMessages) : 
lcMessages;
+  return res;
+}
+
+export function getPathSep(context: MessageContext, pathSep?: string | null) {
+  return pathSep === null || typeof pathSep === 'string'
+    ? pathSep
+    : context.pathSep;
+}
+
+
diff --git a/packages/preact-message/src/declarations.d.ts 
b/packages/preact-message/src/declarations.d.ts
new file mode 100644
index 0000000..acb8034
--- /dev/null
+++ b/packages/preact-message/src/declarations.d.ts
@@ -0,0 +1,3 @@
+export module "" {
+  
+}
\ No newline at end of file
diff --git a/packages/preact-message/src/get-message.ts 
b/packages/preact-message/src/get-message.ts
new file mode 100644
index 0000000..e04d076
--- /dev/null
+++ b/packages/preact-message/src/get-message.ts
@@ -0,0 +1,87 @@
+import {
+  MessageContext,
+  MessageObject,
+  MessageValue
+} from './message-context.js';
+
+function getIn(messages: MessageValue | MessageObject, path: string[]) {
+  if (messages) {
+    for (let i = 0; i < path.length; ++i) {
+      if (typeof messages !== 'object') return undefined;
+      messages = messages[path[i]];
+      if (messages === undefined) return undefined;
+    }
+  }
+  return messages;
+}
+
+export function getPath(id?: string | string[], pathSep?: string | null) {
+  if (!id) return [];
+  if (Array.isArray(id)) return id;
+  return pathSep ? id.split(pathSep) : [id];
+}
+
+/**
+ * Given a `MessageContext` instance, fetches an entry from the messages 
object of the current or given locale.
+ * The returned value will be `undefined` if not found, or otherwise exactly 
as set in the `MessageProvider` props.
+ *
+ * @public
+ * @param id - The key or key path of the message or message object.
+ *   If empty or `[]`, matches the root of the messages object
+ * @param locale - If set, overrides the current locale precedence as set by 
parent MessageProviders.
+ */
+export function getMessage(
+  context: MessageContext,
+  id?: string | string[],
+  locale?: string | string[]
+) {
+  const { locales, messages, onError, pathSep } = context;
+  const lca =
+    locale == null ? locales : Array.isArray(locale) ? locale : [locale];
+  const path = getPath(id, pathSep);
+  for (let i = 0; i < lca.length; ++i) {
+    const lc = lca[i];
+    const msg = getIn(messages[lc], path);
+    if (msg !== undefined) return msg;
+  }
+  return onError ? onError(path, 'ENOMSG') : undefined;
+}
+
+/**
+ * @param id - Message identifier; extends the path set by `rootId`
+ * @param params - Parameters for a function message
+ */
+export interface MessageGetterOptions {
+  baseParams?: any;
+  locale?: string | string[];
+}
+
+/**
+ * Given a `MessageContext` instance, returns a message getter function, which 
may have a preset root id path, locale, and/or base parameters for message 
functions.
+ *
+ * The returned function takes two parameters `(msgId, msgParams)`, which will 
extend any values set by the hook's arguments.
+ *
+ * @public
+ * @param context - The `MessageContext` instance
+ * @param rootId - The key or key path of the message or message object.
+ *   If empty or `[]`, matches the root of the messages object
+ * @param options - If `baseParams` is set, message function parameters will 
be assumed to always be an object, with these values initially set.
+ *   `locale` overrides the current locale precedence as set by parent 
MessageProviders.
+ */
+export function getMessageGetter(
+  context: MessageContext,
+  rootId?: string | string[],
+  { baseParams, locale }: MessageGetterOptions = {}
+) {
+  const { pathSep } = context;
+  const pathPrefix = getPath(rootId, pathSep);
+  return function message(id?: string | string[], params?: any) {
+    const path = pathPrefix.concat(getPath(id, pathSep));
+    const msg = getMessage(context, path, locale);
+    if (typeof msg !== 'function') return msg;
+    const msgParams = baseParams
+      ? Object.assign({}, baseParams, params)
+      : params;
+    return msg(msgParams);
+  };
+}
diff --git a/packages/preact-message/src/index.ts 
b/packages/preact-message/src/index.ts
new file mode 100644
index 0000000..d8dcd69
--- /dev/null
+++ b/packages/preact-message/src/index.ts
@@ -0,0 +1,32 @@
+/**
+ * An efficient React front-end for message formatting
+ *
+ * @packageDocumentation
+ * @remarks
+ * Designed in particular for use with {@link https://messageformat.github.io 
| messageformat}, but will work with any messages.
+ * Provides the best possible API for a front-end developer, without making 
the back end any more difficult than it needs to be either.
+ * Should add at most about 1kB to your compiled & minified bundle size.
+ *
+ * @example
+ * ```js
+ * import {
+ *   MessageContext,
+ *   MessageProvider,
+ *   Message,
+ *   getMessage,
+ *   getMessageGetter,
+ *   useLocales,
+ *   useMessage,
+ *   useMessageGetter
+ * } from '@messageformat/react'
+ * ```
+ */
+export { getMessage, getMessageGetter } from './get-message';
+export { Message, MessageProps } from './message';
+export { MessageContext, MessageObject, MessageValue } from 
'./message-context';
+export { MessageError } from './message-error';
+export { MessageProvider } from './MessageProvider';
+export { useLocales } from './use-locales';
+export { useMessage } from './use-message';
+export { useMessageGetter } from './use-message-getter';
+export { useMessageTemplate } from './use-message-template';
diff --git a/packages/preact-message/src/message-context.ts 
b/packages/preact-message/src/message-context.ts
new file mode 100644
index 0000000..c344a11
--- /dev/null
+++ b/packages/preact-message/src/message-context.ts
@@ -0,0 +1,72 @@
+// @ts-ignore - https://github.com/microsoft/rushstack/issues/1050
+import { createContext } from 'preact';
+import { ErrorCode } from './message-error';
+
+/** @internal */
+export type MessageValue = string | number | boolean | ((props: any) => any);
+
+/** @internal */
+export interface MessageObject {
+  [key: string]: MessageValue | MessageObject;
+}
+
+/** @public */
+export interface MessageContext {
+  locales: string[];
+  merge: (target: MessageObject, ...sources: MessageObject[]) => MessageObject;
+  messages: MessageObject;
+
+  /** Always defined in MessageProvider children */
+  onError?: (path: string[], code: ErrorCode) => any;
+  pathSep: string | null;
+}
+
+export const defaultValue: MessageContext = {
+  locales: [],
+  merge: Object.assign,
+  messages: {},
+  pathSep: '.'
+};
+
+/**
+ * The context object used internally by the library.
+ * Probably only useful with `Class.contextType` or for building custom hooks.
+ *
+ * @public
+ *
+ * @example
+ * ```js
+ * import React, { Component } from 'preact'
+ * import {
+ *   getMessage,
+ *   getMessageGetter,
+ *   MessageContext,
+ *   MessageProvider
+ * } from '@messageformat/react'
+ *
+ * const messages = {
+ *   example: { key: 'Your message here' },
+ *   other: { key: 'Another message' }
+ * }
+ *
+ * class Example extends Component {
+ *   render() {
+ *     const message = getMessage(this.context, 'example.key')
+ *     const otherMsg = getMessageGetter(this.context, 'other')
+ *     return (
+ *       <span>
+ *         {message} | {otherMsg('key')}
+ *       </span>
+ *     ) // 'Your message here | Another message'
+ *   }
+ * }
+ * Example.contextType = MessageContext
+ *
+ * export const App = () => (
+ *   <MessageProvider messages={messages}>
+ *     <Example />
+ *   </MessageProvider>
+ * )
+ * ```
+ */
+export const MessageContext = createContext(defaultValue);
diff --git a/packages/preact-message/src/message-error.ts 
b/packages/preact-message/src/message-error.ts
new file mode 100644
index 0000000..f641544
--- /dev/null
+++ b/packages/preact-message/src/message-error.ts
@@ -0,0 +1,22 @@
+export const errorMessages = {
+  EBADMSG: 'Message with unexpected object value',
+  ENOMSG: 'Message not found'
+};
+
+export type ErrorCode = keyof typeof errorMessages;
+
+/** @internal */
+export class MessageError extends Error {
+  code: ErrorCode;
+  path: string[];
+
+  constructor(
+    path: string[],
+    code: ErrorCode,
+    asId: (path: string[]) => string
+  ) {
+    super(`${errorMessages[code]}: ${asId(path)}`);
+    this.code = code;
+    this.path = path;
+  }
+}
diff --git a/packages/preact-message/src/message.ts 
b/packages/preact-message/src/message.ts
new file mode 100644
index 0000000..e64481b
--- /dev/null
+++ b/packages/preact-message/src/message.ts
@@ -0,0 +1,89 @@
+import { useContext } from 'preact/hooks';
+import { getMessage, getPath } from './get-message';
+import { MessageContext } from './message-context';
+
+/** @public */
+export interface MessageProps {
+  /**
+   * If a function, will be called with the found message.
+   * In this case, `params` will be ignored and `id` is optional.
+   * If some other type of non-empty renderable node, it will be used as a 
fallback value if the message is not found.
+   */
+  children?: any;
+
+  /** The key or key path of the message. */
+  id?: string | string[];
+
+  /** If set, overrides the `locale` of the nearest MessageProvider. */
+  locale?: string | string[];
+
+  /**
+   * Parameters to pass to function messages as their first and only argument.
+   * `params` will override `msgParams`, to allow for data keys such as `key` 
and `locale`.
+   */
+  params?: any;
+
+  /**
+   * Parameters to pass to function messages as their first and only argument.
+   * Overriden by `params`, to allow for data keys such as `key` and `locale`.
+   */
+  [msgParamKey: string]: any;
+}
+
+// Just using { foo, ...bar } adds a polyfill with a boilerplate copyright
+// statement that would add 50% to the minified size of the whole library.
+function rest(props: { [key: string]: any }, exclude: string[]) {
+  const t: typeof props = {};
+  for (const k of Object.keys(props)) if (!exclude.includes(k)) t[k] = 
props[k];
+  return t;
+}
+
+/**
+ * `<Message id [locale] [params] [...msgParams]>`
+ *
+ * The value of a message.
+ * May also be used with a render prop: `<Message id={id}>{msg => 
{...}}</Message>`.
+ *
+ * @public
+ *
+ * @example
+ * ```js
+ * import React from 'preact'
+ * import { Message, MessageProvider } from '@messageformat/react'
+ *
+ * const messages = { example: { key: ({ thing }) => `Your ${thing} here` } }
+ *
+ * const Example = () => (
+ *   <span>
+ *     <Message id="example.key" thing="message" />
+ *   </span>
+ * ) // 'Your message here'
+ *
+ * export const App = () => (
+ *   <MessageProvider messages={messages}>
+ *     <Example />
+ *   </MessageProvider>
+ * )
+ * ```
+ */
+export function Message(props: MessageProps) {
+  const { children, id, locale, params } = props;
+  const msgParams = rest(props, ['children', 'id', 'locale', 'params']);
+  let context = useContext(MessageContext);
+  let fallback = false;
+  if (children && typeof children !== 'function')
+    context = Object.assign({}, context, { onError: () => (fallback = true) });
+  const msg = getMessage(context, id, locale);
+  if (fallback) return children;
+  if (typeof children === 'function') return children(msg);
+  switch (typeof msg) {
+    case 'function':
+      return msg(Object.assign(msgParams, params));
+    case 'boolean':
+      return String(msg);
+    case 'object':
+      if (msg && !Array.isArray(msg))
+        return context.onError ? context.onError(getPath(id), 'EBADMSG') : 
null;
+  }
+  return msg || null;
+}
diff --git a/packages/preact-message/src/use-locales.ts 
b/packages/preact-message/src/use-locales.ts
new file mode 100644
index 0000000..6addd8a
--- /dev/null
+++ b/packages/preact-message/src/use-locales.ts
@@ -0,0 +1,44 @@
+import { useContext } from 'preact/hooks';
+import { MessageContext } from './message-context';
+
+/**
+ * A custom React hook providing the current locales as an array of string 
identifiers, with earlier entries taking precedence over latter ones.
+ * Undefined locales are identified by an empty string `''`.
+ *
+ * @public
+ *
+ * @example
+ * ```js
+ * import React from 'preact'
+ * import { MessageProvider, useLocales } from '@messageformat/react'
+ *
+ * <MessageProvider locale="en" messages={ { foo: 'FOO' } }>
+ *   {() => useLocales().join(',') // 'en'
+ *   }
+ *   <MessageProvider locale="fi" messages={ { foo: 'FÖÖ' } }>
+ *     {() => useLocales().join(',') // 'fi,en'
+ *     }
+ *   </MessageProvider>
+ * </MessageProvider>
+ * ```
+ *
+ * @example
+ * ```js
+ * import React, { Component } from 'preact'
+ * import { MessageContext, MessageProvider, useLocales } from 
'@messageformat/react'
+ *
+ * // Within a class component, locales are available via the context object
+ * class Foo extends Component {
+ *   static contextType = MessageContext
+ *   declare context: React.ContextType<typeof MessageContext> // TS
+ *   render() {
+ *     const { locales } = this.context
+ *     return locales.join(',')
+ *   }
+ * }
+ * ```
+ */
+export function useLocales() {
+  const { locales } = useContext(MessageContext);
+  return locales.slice();
+}
diff --git a/packages/preact-message/src/use-message-getter.ts 
b/packages/preact-message/src/use-message-getter.ts
new file mode 100644
index 0000000..a4b08fb
--- /dev/null
+++ b/packages/preact-message/src/use-message-getter.ts
@@ -0,0 +1,47 @@
+import { useContext } from 'preact/hooks';
+import { getMessageGetter, MessageGetterOptions } from './get-message';
+import { MessageContext } from './message-context';
+
+/**
+ * A custom [React hook] providing a message getter function, which may have a 
preset root id path, locale, and/or base parameters for message functions.
+ *
+ * The returned function takes two parameters `(msgId, msgParams)`, which will 
extend any values set by the hook's arguments.
+ *
+ * @public
+ * @param rootId - The key or key path of the message or message object.
+ *   If empty or `[]`, matches the root of the messages object
+ * @param options - If `baseParams` is set, message function parameters will 
be assumed to always be an object, with these values initially set.
+ *   `locale` overrides the current locale precedence as set by parent 
MessageProviders.
+ *
+ * @example
+ * ```js
+ * import React from 'preact'
+ * import { MessageProvider, useMessageGetter } from '@messageformat/react'
+ *
+ * const messages = {
+ *   example: {
+ *     funMsg: ({ thing }) => `Your ${thing} here`,
+ *     thing: 'message'
+ *   }
+ * }
+ *
+ * function Example() {
+ *   const getMsg = useMessageGetter('example')
+ *   const thing = getMsg('thing') // 'message'
+ *   return getMsg('funMsg', { thing }) // 'Your message here'
+ * }
+ *
+ * export const App = () => (
+ *   <MessageProvider messages={messages}>
+ *     <Example />
+ *   </MessageProvider>
+ * )
+ * ```
+ */
+export function useMessageGetter(
+  rootId: string | string[],
+  opt?: MessageGetterOptions
+) {
+  const context = useContext(MessageContext);
+  return getMessageGetter(context, rootId, opt);
+}
diff --git a/packages/preact-message/src/use-message-template.ts 
b/packages/preact-message/src/use-message-template.ts
new file mode 100644
index 0000000..e4de07a
--- /dev/null
+++ b/packages/preact-message/src/use-message-template.ts
@@ -0,0 +1,31 @@
+import { useContext } from 'preact/hooks';
+import { MessageGetterOptions, getPath, getMessage } from './get-message';
+import { MessageContext } from './message-context';
+
+
+export function useMessageTemplate(
+  rootId?: string | string[],
+  opt?: MessageGetterOptions
+) {
+  const context = useContext(MessageContext);
+  return getMessageGetter(context, rootId, opt);
+}
+
+export function getMessageGetter(
+  context: MessageContext,
+  rootId?: string | string[],
+  { baseParams, locale }: MessageGetterOptions = {}
+) {
+  const { pathSep } = context;
+  const pathPrefix = getPath(rootId, pathSep);
+  return function message(id?: TemplateStringsArray, ...params: any) {
+    const path = pathPrefix.concat(getPath(id?.join('%s'), pathSep));
+    const msg = getMessage(context, path, locale);
+    if (typeof msg !== 'function') return msg;
+    const msgParams = baseParams
+      ? Object.assign({}, baseParams, params)
+      : params;
+    return msg(msgParams);
+  };
+}
+
diff --git a/packages/preact-message/src/use-message.ts 
b/packages/preact-message/src/use-message.ts
new file mode 100644
index 0000000..c44ec9f
--- /dev/null
+++ b/packages/preact-message/src/use-message.ts
@@ -0,0 +1,58 @@
+import { useContext } from 'preact/hooks';
+import { getMessage } from './get-message';
+import { MessageContext } from './message-context';
+
+/**
+ * A custom React hook providing an entry from the messages object of the 
current or given locale.
+ * The returned value will be `undefined` if not found.
+ *
+ * If the identified message value is a function, the returned value will be 
the result of calling it with a single argument `params`, or `{}` if empty.
+ * Otherwise the value set in the `MessageProvider` props will be returned 
directly.
+ *
+ * @public
+ * @param id - The key or key path of the message or message object.
+ *   If empty or `[]`, matches the root of the messages object
+ * @param params - Argument to use if the identified message is a function
+ * @param locale - If set, overrides the current locale precedence as set by 
parent MessageProviders.
+ *
+ * @example
+ * ```js
+ * import React from 'preact'
+ * import { MessageProvider, useLocales, useMessage } from 
'@messageformat/react'
+ *
+ * const en = { example: { key: 'Your message here' } }
+ * const fi = { example: { key: 'Lisää viestisi tähän' } }
+ *
+ * // Intl.ListFormat may require a polyfill, such as intl-list-format
+ * function Example() {
+ *   const locales = useLocales() // ['fi', 'en']
+ *   const lfOpt = { style: 'long', type: 'conjunction' }
+ *   const lf = new Intl.ListFormat(locales, lfOpt)
+ *   const lcMsg = lf.format(locales.map(lc => JSON.stringify(lc))) // '"fi" 
ja "en"'
+ *   const keyMsg = useMessage('example.key') // 'Lisää viestisi tähän'
+ *   return (
+ *     <article>
+ *       <h1>{lcMsg}</h1>
+ *       <p>{keyMsg}</p>
+ *     </article>
+ *   )
+ * }
+ *
+ * export const App = () => (
+ *   <MessageProvider locale="en" messages={en}>
+ *     <MessageProvider locale="fi" messages={fi}>
+ *       <Example />
+ *     </MessageProvider>
+ *   </MessageProvider>
+ * )
+ * ```
+ */
+export function useMessage(
+  id: string | string[],
+  params?: any,
+  locale?: string | string[]
+) {
+  const context = useContext(MessageContext);
+  const msg = getMessage(context, id, locale);
+  return typeof msg === 'function' ? msg(params == null ? {} : params) : msg;
+}
diff --git a/packages/preact-message/tsconfig.json 
b/packages/preact-message/tsconfig.json
new file mode 100644
index 0000000..2ae553e
--- /dev/null
+++ b/packages/preact-message/tsconfig.json
@@ -0,0 +1,60 @@
+{
+  "compilerOptions": {
+      /* Basic Options */
+      "target": "ES5",                          /* Specify ECMAScript target 
version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
+      "module": "ESNext",                       /* Specify module code 
generation: 'none', commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
+      // "lib": [],                             /* Specify library files to be 
included in the compilation:  */
+      "allowJs": true,                          /* Allow javascript files to 
be compiled. */
+      // "checkJs": true,                       /* Report errors in .js files. 
*/
+      "jsx": "react",                           /* Specify JSX code 
generation: 'preserve', 'react-native', or 'react'. */
+      "jsxFactory": "h",                        /* Specify the JSX factory 
function to use when targeting react JSX emit, e.g. React.createElement or h. */
+      "declaration": true,                   /* Generates corresponding 
'.d.ts' file. */
+      // "sourceMap": true,                     /* Generates corresponding 
'.map' file. */
+      // "outFile": "./",                       /* Concatenate and emit output 
to single file. */
+      "outDir": "./lib/",                        /* Redirect output structure 
to the directory. */
+      // "rootDir": "./",                       /* Specify the root directory 
of input files. Use to control the output directory structure with --outDir. */
+      // "removeComments": true,                /* Do not emit comments to 
output. */
+      "noEmit": false,                           /* Do not emit outputs. */
+      // "importHelpers": true,                 /* Import emit helpers from 
'tslib'. */
+      // "downlevelIteration": true,            /* Provide full support for 
iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. 
*/
+      // "isolatedModules": true,               /* Transpile each file as a 
separate module (similar to 'ts.transpileModule'). */
+
+      /* Strict Type-Checking Options */
+      "strict": true,                           /* Enable all strict 
type-checking options. */
+      // "noImplicitAny": true,                 /* Raise error on expressions 
and declarations with an implied 'any' type. */
+      // "strictNullChecks": true,              /* Enable strict null checks. 
*/
+      // "noImplicitThis": true,                /* Raise error on 'this' 
expressions with an implied 'any' type. */
+      // "alwaysStrict": true,                  /* Parse in strict mode and 
emit "use strict" for each source file. */
+
+      /* Additional Checks */
+      // "noUnusedLocals": true,                /* Report errors on unused 
locals. */
+      // "noUnusedParameters": true,            /* Report errors on unused 
parameters. */
+      // "noImplicitReturns": true,             /* Report error when not all 
code paths in function return a value. */
+      // "noFallthroughCasesInSwitch": true,    /* Report errors for 
fallthrough cases in switch statement. */
+
+      /* Module Resolution Options */
+      "moduleResolution": "node",               /* Specify module resolution 
strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
+      "esModuleInterop": true,                  /* */
+      // "baseUrl": "./",                       /* Base directory to resolve 
non-absolute module names. */
+      // "paths": {},                           /* A series of entries which 
re-map imports to lookup locations relative to the 'baseUrl'. */
+      // "rootDirs": [],                        /* List of root folders whose 
combined content represents the structure of the project at runtime. */
+      // "typeRoots": [],                       /* List of folders to include 
type definitions from. */
+      // "types": [],                           /* Type declaration files to 
be included in compilation. */
+      // "allowSyntheticDefaultImports": true,  /* Allow default imports from 
modules with no default export. This does not affect code emit, just 
typechecking. */
+      // "preserveSymlinks": true,              /* Do not resolve the real 
path of symlinks. */
+
+      /* Source Map Options */
+      // "sourceRoot": "./",                    /* Specify the location where 
debugger should locate TypeScript files instead of source locations. */
+      // "mapRoot": "./",                       /* Specify the location where 
debugger should locate map files instead of generated locations. */
+      // "inlineSourceMap": true,               /* Emit a single file with 
source maps instead of having a separate file. */
+      // "inlineSources": true,                 /* Emit the source alongside 
the sourcemaps within a single file; requires '--inlineSourceMap' or 
'--sourceMap' to be set. */
+
+      /* Experimental Options */
+      // "experimentalDecorators": true,        /* Enables experimental 
support for ES7 decorators. */
+      // "emitDecoratorMetadata": true,         /* Enables experimental 
support for emitting type metadata for decorators. */
+
+      /* Advanced Options */
+      "skipLibCheck": true                      /* Skip type checking of 
declaration files. */
+  },
+  "include": ["src/**/*", "tests/**/*"]
+}
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index cc9e8b1..934ddbf 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -5,8 +5,9 @@ importers:
     dependencies:
       axios: 0.21.1
       date-fns: 2.17.0
+      messageformat: 2.3.0
       preact: 10.5.12
-      preact-i18n: 2.3.1-preactx_preact@10.5.12
+      preact-messages: link:../preact-message
       preact-router: 3.2.1_preact@10.5.12
       swr: 0.4.2
       yup: 0.32.9
@@ -24,7 +25,6 @@ importers:
       '@testing-library/preact-hooks': 1.1.0_368c9f1500877413beac8052be555e33
       '@types/enzyme': 3.10.8
       '@types/jest': 26.0.20
-      '@types/preact-i18n': 2.3.0
       '@typescript-eslint/eslint-plugin': 
4.15.1_dd080f2a8fb4d0ac76cfb4c7062ee728
       '@typescript-eslint/parser': 4.15.1_eslint@7.20.0+typescript@4.1.5
       ava: 3.15.0
@@ -42,6 +42,7 @@ importers:
       eslint-config-preact: 1.1.3_eslint@7.20.0+typescript@4.1.5
       jest: 26.6.3
       jest-preset-preact: 4.0.2_120c6743da4bd73ebdbf5629f89f97bc
+      messageformat-po-loader: 0.3.0_messageformat@2.3.0
       node-sass: 5.0.0
       preact-cli: 3.0.5_2abf32adaded329872bb8e69d10f8425
       preact-render-to-string: 5.1.12_preact@10.5.12
@@ -64,7 +65,6 @@ importers:
       '@testing-library/preact-hooks': ^1.1.0
       '@types/enzyme': ^3.10.5
       '@types/jest': ^26.0.8
-      '@types/preact-i18n': ^2.3.0
       '@typescript-eslint/eslint-plugin': ^4.15.1
       '@typescript-eslint/parser': ^4.15.1
       ava: ^3.15.0
@@ -84,10 +84,12 @@ importers:
       eslint-config-preact: ^1.1.1
       jest: ^26.2.2
       jest-preset-preact: ^4.0.2
+      messageformat: ^2.3.0
+      messageformat-po-loader: ^0.3.0
       node-sass: ^5.0.0
       preact: ^10.3.1
       preact-cli: ^3.0.5
-      preact-i18n: 2.3.1-preactx
+      preact-messages: workspace:*
       preact-render-to-string: ^5.1.4
       preact-router: ^3.2.1
       rimraf: ^3.0.2
@@ -97,7 +99,14 @@ importers:
       typedoc: ^0.20.25
       typescript: ^4.1.3
       yup: ^0.32.8
-lockfileVersion: 5.1
+  packages/preact-message:
+    dependencies:
+      preact: 10.5.12
+      typescript: 4.1.5
+    specifiers:
+      preact: ^10.5.12
+      typescript: ^4.1.5
+lockfileVersion: 5.2
 packages:
   /@babel/code-frame/7.12.11:
     dependencies:
@@ -1546,7 +1555,7 @@ packages:
       jest-haste-map: 26.6.2
       jest-message-util: 26.6.2
       jest-regex-util: 26.0.0
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-resolve-dependencies: 26.6.3
       jest-runner: 26.6.3
       jest-runtime: 26.6.3
@@ -1616,7 +1625,7 @@ packages:
       istanbul-lib-source-maps: 4.0.0
       istanbul-reports: 3.0.2
       jest-haste-map: 26.6.2
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-util: 26.6.2
       jest-worker: 26.6.2
       slash: 3.0.0
@@ -1833,7 +1842,7 @@ packages:
       '@prefresh/core': 0.8.1_preact@10.5.12
       '@prefresh/utils': 0.3.1
       preact: 10.5.12
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       preact: ^10.4.0
@@ -2536,7 +2545,7 @@ packages:
       unfetch: 4.2.0
       url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
       util-deprecate: 1.0.2
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-dev-middleware: 3.7.3_webpack@4.46.0
       webpack-filter-warnings-plugin: 1.2.1_webpack@4.46.0
       webpack-hot-middleware: 2.25.0
@@ -2646,7 +2655,7 @@ packages:
       unfetch: 4.2.0
       url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
       util-deprecate: 1.0.2
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-dev-middleware: 3.7.3_webpack@4.46.0
       webpack-filter-warnings-plugin: 1.2.1_webpack@4.46.0
       webpack-hot-middleware: 2.25.0
@@ -3121,12 +3130,6 @@ packages:
     dev: true
     resolution:
       integrity: 
sha512-kUNnecmtkunAoQ3CnjmMkzNU/gtxG8guhi+Fk2U/kOpIKjIMKnXGp4IJCgQJrXSgMsWYimYG4TGjz/UzbGEBTw==
-  /@types/preact-i18n/2.3.0:
-    dependencies:
-      preact: 10.5.12
-    dev: true
-    resolution:
-      integrity: 
sha512-qDgb5QbPnWJ141y+fca5R3MBQis5h7ITnSB9WQiHj5WH41Q5g9Wc4rCnqYERfqSBSC0ac4cE1JAlFisiAUIiLw==
   /@types/prettier/2.2.1:
     dev: true
     resolution:
@@ -4150,7 +4153,7 @@ packages:
     dependencies:
       chalk: 2.4.1
       deepcopy: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^4.28.4
@@ -4217,7 +4220,7 @@ packages:
       loader-utils: 1.4.0
       make-dir: 3.1.0
       schema-utils: 2.7.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 8.9'
@@ -5564,7 +5567,7 @@ packages:
       find-cache-dir: 3.3.1
       schema-utils: 2.7.1
       serialize-javascript: 4.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
@@ -5730,7 +5733,7 @@ packages:
       p-limit: 2.3.0
       schema-utils: 1.0.0
       serialize-javascript: 4.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-log: 2.0.0
     dev: true
     engines:
@@ -5752,7 +5755,7 @@ packages:
     resolution:
       integrity: 
sha512-V5qQZVAr9K0xu7jXg1M7qTEwuxUgqr7dUOezGaNa7i+Xn9oXAU/d1fzqD9ObuwpVQOaorO5s70ckyi1woP9lVA==
   /core-js/2.6.12:
-    deprecated: 'core-js@<3 is no longer maintained and not recommended for 
usage due to the number of issues. Please, upgrade your dependencies to the 
actual version of core-js@3.'
+    deprecated: core-js@<3 is no longer maintained and not recommended for 
usage due to the number of issues. Please, upgrade your dependencies to the 
actual version of core-js@3.
     dev: true
     requiresBuild: true
     resolution:
@@ -5985,7 +5988,7 @@ packages:
       postcss-value-parser: 4.1.0
       schema-utils: 2.7.1
       semver: 6.3.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 8.9.0'
@@ -6524,10 +6527,6 @@ packages:
     dev: true
     resolution:
       integrity: sha1-44Mx8IRLukm5qctxx3FYWqsbxlo=
-  /dlv/1.1.3:
-    dev: false
-    resolution:
-      integrity: 
sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==
   /dns-equal/1.0.0:
     dev: true
     resolution:
@@ -6676,7 +6675,7 @@ packages:
   /dotenv-webpack/1.8.0_webpack@4.46.0:
     dependencies:
       dotenv-defaults: 1.1.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^1 || ^2 || ^3 || ^4
@@ -6857,6 +6856,12 @@ packages:
       node: '>= 0.8'
     resolution:
       integrity: sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=
+  /encoding/0.1.13:
+    dependencies:
+      iconv-lite: 0.6.2
+    dev: true
+    resolution:
+      integrity: 
sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==
   /end-of-stream/1.4.4:
     dependencies:
       once: 1.4.0
@@ -7603,7 +7608,7 @@ packages:
     dependencies:
       loader-utils: 2.0.0
       schema-utils: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 10.13.0'
@@ -8075,6 +8080,21 @@ packages:
     dev: true
     resolution:
       integrity: sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=
+  /gettext-parser/1.4.0:
+    dependencies:
+      encoding: 0.1.13
+      safe-buffer: 5.2.1
+    dev: true
+    resolution:
+      integrity: 
sha512-sedZYLHlHeBop/gZ1jdg59hlUEcpcZJofLq2JFwJT1zTqAU3l2wFv6IsuwFHGqbiT9DWzMUW4/em2+hspnmMMA==
+  /gettext-to-messageformat/0.3.1:
+    dependencies:
+      gettext-parser: 1.4.0
+    dev: true
+    engines:
+      node: '>=6.0'
+    resolution:
+      integrity: 
sha512-UyqIL3Ul4NryU95Wome/qtlcuVIqgEWVIFw0zi7Lv14ACLXfaVDCbrjZ7o+3BZ7u+4NS1mP/2O1eXZoHCoas8g==
   /github-slugger/1.3.0:
     dependencies:
       emoji-regex: 6.1.1
@@ -8679,7 +8699,7 @@ packages:
       tapable: 1.1.3
       toposort: 1.0.7
       util.promisify: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>=6.9'
@@ -8698,7 +8718,7 @@ packages:
       pretty-error: 2.1.2
       tapable: 1.1.3
       util.promisify: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>=6.9'
@@ -8827,6 +8847,14 @@ packages:
       node: '>=0.10.0'
     resolution:
       integrity: 
sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+  /iconv-lite/0.6.2:
+    dependencies:
+      safer-buffer: 2.1.2
+    dev: true
+    engines:
+      node: '>=0.10.0'
+    resolution:
+      integrity: 
sha512-2y91h5OpQlolefMPmUlivelittSWy0rP+oYVpn6A7GwVHNE8AWzoYOBNmlwks3LobaJxgHCYZAnyNo2GgpNRNQ==
   /icss-utils/4.1.1:
     dependencies:
       postcss: 7.0.35
@@ -9801,7 +9829,7 @@ packages:
       jest-get-type: 26.3.0
       jest-jasmine2: 26.6.3
       jest-regex-util: 26.0.0
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-util: 26.6.2
       jest-validate: 26.6.2
       micromatch: 4.0.2
@@ -9974,7 +10002,7 @@ packages:
       integrity: 
sha512-YyFjePHHp1LzpzYcmgqkJ0nm0gg/lJx2aZFzFy1S6eUqNjXsOqTK10zNRff2dNfssgokjkG65OlWNcIlgd3zew==
   /jest-pnp-resolver/1.2.2_jest-resolve@26.6.2:
     dependencies:
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
     dev: true
     engines:
       node: '>=6'
@@ -10022,7 +10050,7 @@ packages:
       node: '>= 10.14.2'
     resolution:
       integrity: 
sha512-pVwUjJkxbhe4RY8QEWzN3vns2kqyuldKpxlxJlzEYfKSvY6/bMvxoFrYYzUO1Gx28yKWN37qyV7rIoIp2h8fTg==
-  /jest-resolve/26.6.2_jest-resolve@26.6.2:
+  /jest-resolve/26.6.2:
     dependencies:
       '@jest/types': 26.6.2
       chalk: 4.1.0
@@ -10035,8 +10063,6 @@ packages:
     dev: true
     engines:
       node: '>= 10.14.2'
-    peerDependencies:
-      jest-resolve: '*'
     resolution:
       integrity: 
sha512-sOxsZOq25mT1wRsfHcbtkInS+Ek7Q8jCHUB0ZUTP0tc/c41QHriU/NunqMfCUWsL4H3MHpvQD4QR9kSYhS7UvQ==
   /jest-runner/26.6.3:
@@ -10055,7 +10081,7 @@ packages:
       jest-haste-map: 26.6.2
       jest-leak-detector: 26.6.2
       jest-message-util: 26.6.2
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-runtime: 26.6.3
       jest-util: 26.6.2
       jest-worker: 26.6.2
@@ -10088,7 +10114,7 @@ packages:
       jest-message-util: 26.6.2
       jest-mock: 26.6.2
       jest-regex-util: 26.0.0
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       jest-snapshot: 26.6.2
       jest-util: 26.6.2
       jest-validate: 26.6.2
@@ -10124,7 +10150,7 @@ packages:
       jest-haste-map: 26.6.2
       jest-matcher-utils: 26.6.2
       jest-message-util: 26.6.2
-      jest-resolve: 26.6.2_jest-resolve@26.6.2
+      jest-resolve: 26.6.2
       natural-compare: 1.4.0
       pretty-format: 26.6.2
       semver: 7.3.4
@@ -10813,6 +10839,13 @@ packages:
       node: '>=8'
     resolution:
       integrity: 
sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+  /make-plural/4.3.0:
+    dev: false
+    hasBin: true
+    optionalDependencies:
+      minimist: 1.2.5
+    resolution:
+      integrity: 
sha512-xTYd4JVHpSCW+aqDof6w/MebaMVNTVYBZhbB/vi513xXdiPT92JMVCo0Jq8W2UZnzYRFeVbQiQ+I25l13JuKvA==
   /makeerror/1.0.11:
     dependencies:
       tmpl: 1.0.4
@@ -11028,6 +11061,34 @@ packages:
       node: '>= 8'
     resolution:
       integrity: 
sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+  /messageformat-formatters/2.0.1:
+    dev: false
+    resolution:
+      integrity: 
sha512-E/lQRXhtHwGuiQjI7qxkLp8AHbMD5r2217XNe/SREbBlSawe0lOqsFb7rflZJmlQFSULNLIqlcjjsCPlB3m3Mg==
+  /messageformat-parser/4.1.3:
+    dev: false
+    resolution:
+      integrity: 
sha512-2fU3XDCanRqeOCkn7R5zW5VQHWf+T3hH65SzuqRvjatBK7r4uyFa5mEX+k6F9Bd04LVM5G4/BHBTUJsOdW7uyg==
+  /messageformat-po-loader/0.3.0_messageformat@2.3.0:
+    dependencies:
+      gettext-to-messageformat: 0.3.1
+      loader-utils: 1.4.0
+      messageformat: 2.3.0
+    dev: true
+    engines:
+      node: '>=6.0'
+    peerDependencies:
+      messageformat: 1.x | 2.x
+    resolution:
+      integrity: 
sha512-thu/A7hNl/iBHsRXUdmiy/nEFJZku3bsBMXL53HgHm+I0JaVU9lSpwuQAe7huCO4INGxgZtDoPAEpeb1ZeI5lg==
+  /messageformat/2.3.0:
+    dependencies:
+      make-plural: 4.3.0
+      messageformat-formatters: 2.0.1
+      messageformat-parser: 4.1.3
+    dev: false
+    resolution:
+      integrity: 
sha512-uTzvsv0lTeQxYI2y1NPa1lItL5VRI8Gb93Y2K2ue5gBPyrbJxfDi/EYWxh2PKv5yO42AJeeqblS9MJSh/IEk4w==
   /methods/1.1.2:
     dev: true
     engines:
@@ -11132,7 +11193,7 @@ packages:
       loader-utils: 1.4.0
       normalize-url: 1.9.1
       schema-utils: 1.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
@@ -11156,7 +11217,6 @@ packages:
     resolution:
       integrity: 
sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==
   /minimist/1.2.5:
-    dev: true
     resolution:
       integrity: 
sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==
   /minipass-collect/1.0.2:
@@ -11795,7 +11855,7 @@ packages:
     dependencies:
       cssnano: 4.1.10
       last-call-webpack-plugin: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^4.0.0
@@ -12803,7 +12863,7 @@ packages:
       update-notifier: 4.1.3
       url-loader: 4.1.1_file-loader@6.2.0+webpack@4.46.0
       validate-npm-package-name: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-bundle-analyzer: 3.9.0
       webpack-dev-server: 3.11.2_webpack@4.46.0
       webpack-fix-style-only-entries: 0.5.2
@@ -12825,24 +12885,6 @@ packages:
       preact-render-to-string: '*'
     resolution:
       integrity: 
sha512-Oc9HOjwX/3Zk1eXkmP7TMmtqbaROl7F0RWZ2Ni5Q/grmx3yBLJmarkUcOSKabkI/Usw2dU3RVju32Q3Pvy5qIw==
-  /preact-i18n/2.3.1-preactx_preact@10.5.12:
-    dependencies:
-      dlv: 1.1.3
-      preact: 10.5.12
-      preact-markup: 2.1.1_preact@10.5.12
-    dev: false
-    peerDependencies:
-      preact: '>=10'
-    resolution:
-      integrity: 
sha512-i/QGG3BQOWh4nFPXTnhazHGOq2STYMa9/0h6oiUkV+p/c5IDd0luPhRlXkAnEgGRZX3PjAEgx/tzWPQne61wuQ==
-  /preact-markup/2.1.1_preact@10.5.12:
-    dependencies:
-      preact: 10.5.12
-    dev: false
-    peerDependencies:
-      preact: '>=10'
-    resolution:
-      integrity: 
sha512-8JL2p36mzK8XkspOyhBxUSPjYwMxDM0L5BWBZWxsZMVW8WsGQrYQDgVuDKkRspt2hwrle+Cxr/053hpc9BJwfw==
   /preact-render-to-string/5.1.12_preact@10.5.12:
     dependencies:
       preact: 10.5.12
@@ -12861,6 +12903,7 @@ packages:
     resolution:
       integrity: 
sha512-KEN2VN1DxUlTwzW5IFkF13YIA2OdQ2OvgJTkQREF+AA2NrHRLaGbB68EjS4IeZOa1shvQ1FvEm3bSLta4sXBhg==
   /preact/10.5.12:
+    dev: false
     resolution:
       integrity: 
sha512-r6siDkuD36oszwlCkcqDJCAKBQxGoeEGytw2DGMD5A/GGdu5Tymw+N2OBXwvOLxg6d1FeY8MgMV3cc5aVQo4Cg==
   /prelude-ls/1.1.2:
@@ -12962,7 +13005,7 @@ packages:
     dependencies:
       chalk: 3.0.0
       progress: 2.0.3
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: ^1.3.0 || ^2 || ^3 || ^4 || ^5
@@ -13225,7 +13268,7 @@ packages:
     dependencies:
       loader-utils: 2.0.0
       schema-utils: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 10.13.0'
@@ -13873,7 +13916,7 @@ packages:
       request-promise-core: 1.1.4_request@2.88.2
       stealthy-require: 1.1.1
       tough-cookie: 2.5.0
-    deprecated: 'request-promise-native has been deprecated because it extends 
the now deprecated request package, see 
https://github.com/request/request/issues/3142'
+    deprecated: request-promise-native has been deprecated because it extends 
the now deprecated request package, see 
https://github.com/request/request/issues/3142
     dev: true
     engines:
       node: '>=0.12.0'
@@ -13903,7 +13946,7 @@ packages:
       tough-cookie: 2.5.0
       tunnel-agent: 0.6.0
       uuid: 3.4.0
-    deprecated: 'request has been deprecated, see 
https://github.com/request/request/issues/3142'
+    deprecated: request has been deprecated, see 
https://github.com/request/request/issues/3142
     dev: true
     engines:
       node: '>= 6'
@@ -13968,7 +14011,7 @@ packages:
     resolution:
       integrity: 
sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
   /resolve-url/0.2.1:
-    deprecated: 'https://github.com/lydell/resolve-url#deprecated'
+    deprecated: https://github.com/lydell/resolve-url#deprecated
     dev: true
     resolution:
       integrity: sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=
@@ -14552,7 +14595,7 @@ packages:
       minimatch: 3.0.4
       pretty-bytes: 5.5.0
       util.promisify: 1.1.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     peerDependencies:
       webpack: '*'
@@ -15099,7 +15142,7 @@ packages:
     dependencies:
       loader-utils: 2.0.0
       schema-utils: 2.7.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 8.9.0'
@@ -15321,7 +15364,7 @@ packages:
       serialize-javascript: 4.0.0
       source-map: 0.6.1
       terser: 4.8.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
       worker-farm: 1.7.0
     dev: true
@@ -15341,7 +15384,7 @@ packages:
       serialize-javascript: 4.0.0
       source-map: 0.6.1
       terser: 4.8.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
@@ -15757,7 +15800,6 @@ packages:
     resolution:
       integrity: 
sha512-kdMjTiekY+z/ubJCATUPlRDl39vXYiMV9iyeMuEuXZh2we6zz80uovNN2WlAxmmdE/Z/YQe+EbOEXB5RHEED3w==
   /typescript/4.1.5:
-    dev: true
     engines:
       node: '>=4.2.0'
     hasBin: true
@@ -16015,7 +16057,7 @@ packages:
     resolution:
       integrity: 
sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
   /urix/0.1.0:
-    deprecated: 'Please see https://github.com/lydell/urix#deprecated'
+    deprecated: Please see https://github.com/lydell/urix#deprecated
     dev: true
     resolution:
       integrity: sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=
@@ -16025,7 +16067,7 @@ packages:
       loader-utils: 2.0.0
       mime-types: 2.1.29
       schema-utils: 3.0.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 10.13.0'
@@ -16368,7 +16410,7 @@ packages:
       mime: 2.5.2
       mkdirp: 0.5.5
       range-parser: 1.2.1
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-log: 2.0.0
     dev: true
     engines:
@@ -16408,7 +16450,7 @@ packages:
       strip-ansi: 3.0.1
       supports-color: 6.1.0
       url: 0.11.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-dev-middleware: 3.7.3_webpack@4.46.0
       webpack-log: 2.0.0
       ws: 6.2.1
@@ -16427,7 +16469,7 @@ packages:
       integrity: 
sha512-A80BkuHRQfCiNtGBS1EMf2ChTUs0x+B3wGDFmOeT4rmJOHhHTCH2naNxIHhmkr0/UillP4U3yeIyv1pNp+QDLQ==
   /webpack-filter-warnings-plugin/1.2.1_webpack@4.46.0:
     dependencies:
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
     dev: true
     engines:
       node: '>= 4.3 < 5.0.0 || >= 5.10'
@@ -16482,7 +16524,7 @@ packages:
     dev: true
     resolution:
       integrity: 
sha512-kDUmfm3BZrei0y+1NTHJInejzxfhtU8eDj2M7OKb2IWrPFAeO1SOH2KuQ68MSZu9IGEHcxbkKKR1v18FrUSOmA==
-  /webpack/4.46.0_webpack@4.46.0:
+  /webpack/4.46.0:
     dependencies:
       '@webassemblyjs/ast': 1.9.0
       '@webassemblyjs/helper-module-context': 1.9.0
@@ -16506,14 +16548,12 @@ packages:
       tapable: 1.1.3
       terser-webpack-plugin: 1.4.5_webpack@4.46.0
       watchpack: 1.7.5
-      webpack: 4.46.0_webpack@4.46.0
       webpack-sources: 1.4.3
     dev: true
     engines:
       node: '>=6.11.5'
     hasBin: true
     peerDependencies:
-      webpack: '*'
       webpack-cli: '*'
       webpack-command: '*'
     peerDependenciesMeta:
@@ -16745,7 +16785,7 @@ packages:
       fast-json-stable-stringify: 2.1.0
       source-map-url: 0.4.1
       upath: 1.2.0
-      webpack: 4.46.0_webpack@4.46.0
+      webpack: 4.46.0
       webpack-sources: 1.4.3
       workbox-build: 5.1.4
     dev: true

-- 
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]