Move i18n app to settings (discoverability) (#2679)
This commit is contained in:
parent
9076968aa6
commit
ea3f342da5
6
I18N.md
6
I18N.md
@ -7,7 +7,7 @@ The apps UI allows all strings to be translated. Additionally it has a basic UI
|
||||
To update translations, the following process is required.
|
||||
|
||||
- launch the apps UI, either locally or via [https://polkadot.js.org/apps](https://polkadot.js.org/apps)
|
||||
- explicitly navigate to the i18n page, [https://polkadot.js.org/apps/#/i18n](https://polkadot.js.org/apps/#/i18n)
|
||||
- explicitly navigate to the i18n page, [https://polkadot.js.org/apps/#/i18n](https://polkadot.js.org/apps/#/settings/i18n)
|
||||
|
||||
Here you will find a dropdown of all the available languages and all the modules that maps to the UI. On a single screen you will be able to see all the available strings for a specific module.
|
||||
|
||||
@ -17,4 +17,6 @@ Here you will find a dropdown of all the available languages and all the modules
|
||||
|
||||
## Adding a new language
|
||||
|
||||
The process is similar for the above, but does require a new folder with the language identifier to be added. Create `packages/apps/public/locales/<id>` folder with an empty `translation.json` (containing only `{}`). After addition of the folder, run `yarn build:i18n` and then the new language will be available for update as per the process in the previous section.
|
||||
The process is similar for the above, but does require a new folder with the language identifier to be added. Create [packages/apps/public/locales/<id>](https://github.com/polkadot-js/apps/tree/master/packages/apps/public/locales) folder with an empty `translation.json` (containing only `{}`). After addition of the folder, run `yarn build:i18n` and then the new language will be available for update as per the process in the previous section.
|
||||
|
||||
In addition to the language folder, the language also needs to be added to the dropdown for available languages, this can be found in [packages/apps-config/src/settings.languages.ts](https://github.com/polkadot-js/apps/blob/master/packages/apps-config/src/settings/languages.ts)
|
||||
|
||||
@ -1,6 +1 @@
|
||||
{
|
||||
"Generate {{lng}}/translation.json": "Generate {{lng}}/translation.json",
|
||||
"the language to display translations for": "the language to display translations for",
|
||||
"the module to display strings for": "the module to display strings for",
|
||||
"{{done}}/{{total}}, {{progress}}% done": "{{done}}/{{total}}, {{progress}}% done"
|
||||
}
|
||||
{}
|
||||
@ -9,6 +9,7 @@
|
||||
"Developer": "Developer",
|
||||
"Extensions": "Extensions",
|
||||
"General": "General",
|
||||
"Generate {{lng}}/translation.json": "Generate {{lng}}/translation.json",
|
||||
"Genesis Hash": "Genesis Hash",
|
||||
"Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched": "Genesis Hash refers to initial state of the chain, it cannot be changed once the chain is launched",
|
||||
"Manage your connection to Ledger S": "Manage your connection to Ledger S",
|
||||
@ -28,6 +29,7 @@
|
||||
"The RPC node can be selected from the pre-defined list or manually entered, depending on the chain you wish to connect to.": "The RPC node can be selected from the pre-defined list or manually entered, depending on the chain you wish to connect to.",
|
||||
"The color used to distinguish this network with others, use color code with 3 or 6 digits, like \"#FFF\" or \"#111111\"": "The color used to distinguish this network with others, use color code with 3 or 6 digits, like \"#FFF\" or \"#111111\"",
|
||||
"This is not a valid JSON object.": "This is not a valid JSON object.",
|
||||
"Translate": "Translate",
|
||||
"Unit": "Unit",
|
||||
"Unit decides the name of 1 unit token, e.g. \"DOT\" for Polkadot": "Unit decides the name of 1 unit token, e.g. \"DOT\" for Polkadot",
|
||||
"Update metadata": "Update metadata",
|
||||
@ -39,5 +41,8 @@
|
||||
"interface operation mode": "interface operation mode",
|
||||
"manage hardware connections": "manage hardware connections",
|
||||
"remote node/endpoint to connect to": "remote node/endpoint to connect to",
|
||||
"upgradable extensions": "upgradable extensions"
|
||||
"the language to display translations for": "the language to display translations for",
|
||||
"the module to display strings for": "the module to display strings for",
|
||||
"upgradable extensions": "upgradable extensions",
|
||||
"{{done}}/{{total}}, {{progress}}% done": "{{done}}/{{total}}, {{progress}}% done"
|
||||
}
|
||||
@ -566,6 +566,7 @@
|
||||
"To council": "",
|
||||
"To ensure optimal fund security using the same stash/controller is strongly discouraged, but not forbidden.": "",
|
||||
"Transfer": "",
|
||||
"Translate": "",
|
||||
"Treasury overview": "",
|
||||
"Type here what you would like your address to contain. This tool will generate the keys and show the associated addresses that best match your search. You can use \"?\" as a wildcard for a character.": "",
|
||||
"Type the amount you want to transfer. Note that you can select the unit on the right e.g sending 1 milli is equivalent to sending 0.001.": "",
|
||||
|
||||
@ -2,227 +2,11 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AppProps as Props } from '@polkadot/react-components/types';
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Columar, Column, Dropdown, Progress, Spinner } from '@polkadot/react-components';
|
||||
|
||||
import { useTranslation } from './translate';
|
||||
import StringInput from './StringInput';
|
||||
|
||||
interface Option {
|
||||
text: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Defaults {
|
||||
english: StringsMod;
|
||||
keys: Option[];
|
||||
languages: Languages;
|
||||
modules: Option[];
|
||||
}
|
||||
|
||||
type Progress = [[number, number], Record<string, [number, number]>];
|
||||
type Strings = Record<string, string>;
|
||||
type StringsMod = Record<string, Strings>;
|
||||
type Languages = Record<string, Strings>;
|
||||
|
||||
async function retrieveJson (url: string): Promise<any> {
|
||||
const response = await fetch(`locales/${url}`);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function retrieveEnglish (): Promise<StringsMod> {
|
||||
const paths: Array<string> = await retrieveJson('en/index.json');
|
||||
const strings: Strings[] = await Promise.all(paths.map((path) => retrieveJson(`en/${path}`)));
|
||||
|
||||
return strings.reduce((language: StringsMod, strings, index): StringsMod => {
|
||||
language[paths[index]] = strings;
|
||||
|
||||
return language;
|
||||
}, {});
|
||||
}
|
||||
|
||||
async function retrieveAll (): Promise<Defaults> {
|
||||
const _keys: string[] = await retrieveJson('index.json');
|
||||
const keys = _keys.filter((key) => key !== 'en');
|
||||
const english = await retrieveEnglish();
|
||||
const languages = await Promise.all(
|
||||
keys.map((lng) => retrieveJson(`${lng}/translation.json`))
|
||||
);
|
||||
|
||||
return {
|
||||
english,
|
||||
keys: keys.map((text) => ({ text, value: text })),
|
||||
languages: languages.reduce((languages: Languages, strings, index): Languages => {
|
||||
languages[keys[index]] = strings;
|
||||
|
||||
return languages;
|
||||
}, {}),
|
||||
modules: Object
|
||||
.keys(english)
|
||||
.map((text) => ({ text: text.replace('.json', '').replace('app-', 'page-'), value: text }))
|
||||
.sort((a, b) => a.text.localeCompare(b.text))
|
||||
};
|
||||
}
|
||||
|
||||
function calcProgress (english: StringsMod, language: Strings): Progress {
|
||||
const breakdown: Record<string, [number, number]> = {};
|
||||
let done = 0;
|
||||
let total = 0;
|
||||
|
||||
Object.keys(english).forEach((record): void => {
|
||||
const mod = english[record];
|
||||
const mtotal = Object.keys(mod).length;
|
||||
let mdone = 0;
|
||||
|
||||
Object.keys(mod).forEach((key): void => {
|
||||
if (language[key]) {
|
||||
mdone++;
|
||||
}
|
||||
});
|
||||
|
||||
done += mdone;
|
||||
total += mtotal;
|
||||
|
||||
breakdown[record] = [mdone, mtotal];
|
||||
});
|
||||
|
||||
return [[done, total], breakdown];
|
||||
}
|
||||
|
||||
function doDownload (strings: Strings): void {
|
||||
const sanitized = Object.keys(strings).sort().reduce((result: Strings, key): Strings => {
|
||||
const sanitized = strings[key].trim();
|
||||
|
||||
if (sanitized) {
|
||||
result[key] = sanitized;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
FileSaver.saveAs(
|
||||
new Blob([JSON.stringify(sanitized, null, 2)], { type: 'application/json; charset=utf-8' }),
|
||||
'translation.json'
|
||||
);
|
||||
}
|
||||
|
||||
function progressDisplay ([done, total]: [number, number] = [0, 0]): { done: number; progress: string; total: number } {
|
||||
return {
|
||||
done,
|
||||
progress: (total ? (done * 100 / total) : 100).toFixed(2),
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
function TranslateApp ({ className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [{ english, keys, languages, modules }, setDefaults] = useState<Defaults>({ english: {}, keys: [], languages: {}, modules: [] });
|
||||
const [lng, setLng] = useState<string>('zh');
|
||||
const [[modProgress, allProgress], setProgress] = useState<Progress>([[0, 0], {}]);
|
||||
const [record, setRecord] = useState<string>('app-accounts.json');
|
||||
const [strings, setStrings] = useState<Strings | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
retrieveAll().then(setDefaults);
|
||||
}, []);
|
||||
|
||||
useEffect((): void => {
|
||||
setStrings(languages[lng]);
|
||||
setProgress(calcProgress(english, languages[lng]));
|
||||
}, [english, languages, lng]);
|
||||
|
||||
const _setString = useCallback(
|
||||
(key: string, value: string): void => {
|
||||
setStrings((strings: Strings | null): Strings | null =>
|
||||
strings
|
||||
? { ...strings, [key]: value }
|
||||
: null
|
||||
);
|
||||
|
||||
const hasPrevVal = !!languages[lng][key];
|
||||
const sanitized = value.trim();
|
||||
|
||||
languages[lng][key] = value;
|
||||
|
||||
if (hasPrevVal !== !!sanitized) {
|
||||
setProgress(calcProgress(english, languages[lng]));
|
||||
}
|
||||
},
|
||||
[english, languages, lng]
|
||||
);
|
||||
|
||||
const _onDownload = useCallback(
|
||||
() => doDownload(strings || {}),
|
||||
[strings]
|
||||
);
|
||||
|
||||
if (!keys.length) {
|
||||
return <Spinner />;
|
||||
}
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
export default function TranslateApp (): React.ReactElement<{}> {
|
||||
return (
|
||||
<main className={className}>
|
||||
<header>
|
||||
<Columar>
|
||||
<Column>
|
||||
<Dropdown
|
||||
isFull
|
||||
label={t('the language to display translations for')}
|
||||
onChange={setLng}
|
||||
options={keys}
|
||||
value={lng}
|
||||
/>
|
||||
<Progress
|
||||
color='auto'
|
||||
total={modProgress[1]}
|
||||
value={modProgress[0]}
|
||||
/>
|
||||
{t('{{done}}/{{total}}, {{progress}}% done', { replace: progressDisplay(modProgress) })}
|
||||
</Column>
|
||||
<Column>
|
||||
<Dropdown
|
||||
isFull
|
||||
label={t('the module to display strings for')}
|
||||
onChange={setRecord}
|
||||
options={modules}
|
||||
value={record}
|
||||
/>
|
||||
<Progress
|
||||
color='auto'
|
||||
total={allProgress[record]?.[1]}
|
||||
value={allProgress[record]?.[0]}
|
||||
/>
|
||||
{t('{{done}}/{{total}}, {{progress}}% done', { replace: progressDisplay(allProgress[record]) })}
|
||||
</Column>
|
||||
</Columar>
|
||||
</header>
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='download'
|
||||
label={t('Generate {{lng}}/translation.json', { replace: { lng } })}
|
||||
onClick={_onDownload}
|
||||
/>
|
||||
</Button.Group>
|
||||
{record && strings && Object.keys(english[record]).map((key, index) =>
|
||||
<StringInput
|
||||
key={index}
|
||||
onChange={_setString}
|
||||
original={english[record][key]}
|
||||
tkey={key}
|
||||
tval={strings[key] || ''}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
<main>This app has been moved to <Link to='/settings/i18n'>#/settings/i18n</Link></main>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(TranslateApp)`
|
||||
.ui.progress:last-child {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
`);
|
||||
|
||||
@ -1,9 +0,0 @@
|
||||
// Copyright 2017-2020 @polkadot/app-i18n authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { useTranslation as useTranslationBase, UseTranslationResponse } from 'react-i18next';
|
||||
|
||||
export function useTranslation (): UseTranslationResponse {
|
||||
return useTranslationBase('app-i18n');
|
||||
}
|
||||
@ -1,4 +1,4 @@
|
||||
// Copyright 2017-2020 @polkadot/app-i18n authors & contributors
|
||||
// Copyright 2017-2020 @polkadot/app-settings authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
245
packages/page-settings/src/I18n/index.tsx
Normal file
245
packages/page-settings/src/I18n/index.tsx
Normal file
@ -0,0 +1,245 @@
|
||||
// Copyright 2017-2020 @polkadot/app-settings authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import FileSaver from 'file-saver';
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Button, Columar, Column, Dropdown, Progress, Spinner } from '@polkadot/react-components';
|
||||
import languageCache from '@polkadot/react-components/i18n/cache';
|
||||
import uiSettings from '@polkadot/ui-settings';
|
||||
|
||||
import { useTranslation } from '../translate';
|
||||
import StringInput from './StringInput';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
interface Option {
|
||||
text: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
interface Defaults {
|
||||
english: StringsMod;
|
||||
keys: Option[];
|
||||
modules: Option[];
|
||||
}
|
||||
|
||||
type Progress = [[number, number, number], Record<string, [number, number, number]>];
|
||||
type Strings = Record<string, string>;
|
||||
type StringsMod = Record<string, Strings>;
|
||||
|
||||
async function retrieveJson (url: string): Promise<any> {
|
||||
const response = await fetch(`locales/${url}`);
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
async function retrieveEnglish (): Promise<StringsMod> {
|
||||
const paths: Array<string> = await retrieveJson('en/index.json');
|
||||
const strings: Strings[] = await Promise.all(paths.map((path) => retrieveJson(`en/${path}`)));
|
||||
|
||||
return strings.reduce((language: StringsMod, strings, index): StringsMod => {
|
||||
language[paths[index]] = strings;
|
||||
|
||||
return language;
|
||||
}, {});
|
||||
}
|
||||
|
||||
async function retrieveAll (): Promise<Defaults> {
|
||||
const _keys: string[] = await retrieveJson('index.json');
|
||||
const keys = _keys.filter((lng) => lng !== 'en');
|
||||
const missing = keys.filter((lng) => !languageCache[lng]);
|
||||
const english = await retrieveEnglish();
|
||||
const translations = missing.length
|
||||
? await Promise.all(missing.map((lng) => retrieveJson(`${lng}/translation.json`)))
|
||||
: [];
|
||||
|
||||
missing.forEach((lng, index): void => {
|
||||
languageCache[lng] = translations[index];
|
||||
});
|
||||
|
||||
return {
|
||||
english,
|
||||
keys: keys.map((text) => ({ text, value: text })),
|
||||
modules: Object
|
||||
.keys(english)
|
||||
.map((text) => ({ text: text.replace('.json', '').replace('app-', 'page-'), value: text }))
|
||||
.sort((a, b) => a.text.localeCompare(b.text))
|
||||
};
|
||||
}
|
||||
|
||||
function calcProgress (english: StringsMod, language: Strings): Progress {
|
||||
const breakdown: Record<string, [number, number, number]> = {};
|
||||
let done = 0;
|
||||
let total = 0;
|
||||
|
||||
Object.keys(english).forEach((record): void => {
|
||||
const mod = english[record];
|
||||
const mtotal = Object.keys(mod).length;
|
||||
let mdone = 0;
|
||||
|
||||
Object.keys(mod).forEach((key): void => {
|
||||
if (language[key]) {
|
||||
mdone++;
|
||||
}
|
||||
});
|
||||
|
||||
done += mdone;
|
||||
total += mtotal;
|
||||
|
||||
breakdown[record] = [mdone, mtotal, 0];
|
||||
});
|
||||
|
||||
return [[done, total, 0], breakdown];
|
||||
}
|
||||
|
||||
function doDownload (strings: Strings): void {
|
||||
const sanitized = Object.keys(strings).sort().reduce((result: Strings, key): Strings => {
|
||||
const sanitized = strings[key].trim();
|
||||
|
||||
if (sanitized) {
|
||||
result[key] = sanitized;
|
||||
}
|
||||
|
||||
return result;
|
||||
}, {});
|
||||
|
||||
FileSaver.saveAs(
|
||||
new Blob([JSON.stringify(sanitized, null, 2)], { type: 'application/json; charset=utf-8' }),
|
||||
'translation.json'
|
||||
);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
function progressDisplay ([done, total, _]: [number, number, number] = [0, 0, 0]): { done: number; progress: string; total: number } {
|
||||
return {
|
||||
done,
|
||||
progress: (total ? (done * 100 / total) : 100).toFixed(2),
|
||||
total
|
||||
};
|
||||
}
|
||||
|
||||
function Translate ({ className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [{ english, keys, modules }, setDefaults] = useState<Defaults>({ english: {}, keys: [], modules: [] });
|
||||
const [lng, setLng] = useState<string>('zh');
|
||||
const [[modProgress, allProgress], setProgress] = useState<Progress>([[0, 0, 0], {}]);
|
||||
const [record, setRecord] = useState<string>('app-accounts.json');
|
||||
const [strings, setStrings] = useState<Strings | null>(null);
|
||||
|
||||
useEffect((): void => {
|
||||
retrieveAll().then(setDefaults);
|
||||
}, []);
|
||||
|
||||
useEffect((): void => {
|
||||
setStrings(languageCache[lng]);
|
||||
setProgress(calcProgress(english, languageCache[lng]));
|
||||
}, [english, lng]);
|
||||
|
||||
useEffect((): void => {
|
||||
setLng(
|
||||
keys.some(({ value }) => value === uiSettings.i18nLang)
|
||||
? uiSettings.i18nLang
|
||||
: 'zh'
|
||||
);
|
||||
}, [keys]);
|
||||
|
||||
const _setString = useCallback(
|
||||
(key: string, value: string): void => {
|
||||
setStrings((strings: Strings | null): Strings | null =>
|
||||
strings
|
||||
? { ...strings, [key]: value }
|
||||
: null
|
||||
);
|
||||
|
||||
const hasPrevVal = !!languageCache[lng][key];
|
||||
const sanitized = value.trim();
|
||||
|
||||
languageCache[lng][key] = value;
|
||||
|
||||
if (hasPrevVal !== !!sanitized) {
|
||||
const [progress, breakdown] = calcProgress(english, languageCache[lng]);
|
||||
|
||||
setProgress(([counters]): Progress => {
|
||||
progress[2] = Math.max(0, progress[0] - counters[0]);
|
||||
|
||||
return [progress, breakdown];
|
||||
});
|
||||
}
|
||||
},
|
||||
[english, lng]
|
||||
);
|
||||
|
||||
const _onDownload = useCallback(
|
||||
() => doDownload(strings || {}),
|
||||
[strings]
|
||||
);
|
||||
|
||||
if (!keys.length) {
|
||||
return <Spinner />;
|
||||
}
|
||||
|
||||
return (
|
||||
<main className={className}>
|
||||
<header>
|
||||
<Columar>
|
||||
<Column>
|
||||
<Dropdown
|
||||
isFull
|
||||
label={t('the language to display translations for')}
|
||||
onChange={setLng}
|
||||
options={keys}
|
||||
value={lng}
|
||||
/>
|
||||
<Progress
|
||||
color='auto'
|
||||
total={modProgress[1]}
|
||||
value={modProgress[0]}
|
||||
/>
|
||||
{t('{{done}}/{{total}}, {{progress}}% done', { replace: progressDisplay(modProgress) })}
|
||||
</Column>
|
||||
<Column>
|
||||
<Dropdown
|
||||
isFull
|
||||
label={t('the module to display strings for')}
|
||||
onChange={setRecord}
|
||||
options={modules}
|
||||
value={record}
|
||||
/>
|
||||
<Progress
|
||||
color='auto'
|
||||
total={allProgress[record]?.[1]}
|
||||
value={allProgress[record]?.[0]}
|
||||
/>
|
||||
{t('{{done}}/{{total}}, {{progress}}% done', { replace: progressDisplay(allProgress[record]) })}
|
||||
</Column>
|
||||
</Columar>
|
||||
</header>
|
||||
<Button.Group>
|
||||
<Button
|
||||
icon='download'
|
||||
label={t('Generate {{lng}}/translation.json', { replace: { lng } })}
|
||||
onClick={_onDownload}
|
||||
/>
|
||||
</Button.Group>
|
||||
{record && strings && Object.keys(english[record]).map((key, index) =>
|
||||
<StringInput
|
||||
key={index}
|
||||
onChange={_setString}
|
||||
original={english[record][key]}
|
||||
tkey={key}
|
||||
tval={strings[key] || ''}
|
||||
/>
|
||||
)}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(styled(Translate)`
|
||||
.ui.progress:last-child {
|
||||
margin: 0.25rem;
|
||||
}
|
||||
`);
|
||||
@ -12,6 +12,7 @@ import uiSettings from '@polkadot/ui-settings';
|
||||
import md from './md/basics.md';
|
||||
import { useTranslation } from './translate';
|
||||
import Developer from './Developer';
|
||||
import I18n from './I18n';
|
||||
import Metadata from './Metadata';
|
||||
import General from './General';
|
||||
import useCounter from './useCounter';
|
||||
@ -42,6 +43,10 @@ function SettingsApp ({ basePath, onStatusChange }: Props): React.ReactElement<P
|
||||
{
|
||||
name: 'developer',
|
||||
text: t('Developer')
|
||||
},
|
||||
{
|
||||
name: 'i18n',
|
||||
text: t('Translate')
|
||||
}
|
||||
], [numExtensions, t]);
|
||||
|
||||
@ -62,6 +67,9 @@ function SettingsApp ({ basePath, onStatusChange }: Props): React.ReactElement<P
|
||||
onStatusChange={onStatusChange}
|
||||
/>
|
||||
</Route>
|
||||
<Route path={`${basePath}/i18n`}>
|
||||
<I18n />
|
||||
</Route>
|
||||
<Route path={`${basePath}/metadata`}>
|
||||
<Metadata />
|
||||
</Route>
|
||||
|
||||
@ -2,9 +2,9 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
type Callback = (error: string | null, data: any) => void;
|
||||
import languageCache from './cache';
|
||||
|
||||
export const languageCache: Record<string, Record<string, string>> = {};
|
||||
type Callback = (error: string | null, data: any) => void;
|
||||
|
||||
export default class Backend {
|
||||
type = 'backend'
|
||||
@ -12,35 +12,20 @@ export default class Backend {
|
||||
static type: 'backend' = 'backend'
|
||||
|
||||
async read (lng: string, _namespace: string, responder: Callback): Promise<void> {
|
||||
const cached = languageCache[lng];
|
||||
if (!languageCache[lng]) {
|
||||
try {
|
||||
const response = await fetch(`locales/${lng}/translation.json`, {});
|
||||
|
||||
if (cached) {
|
||||
responder(null, cached);
|
||||
if (!response.ok) {
|
||||
return responder(`i18n: failed loading ${lng}`, response.status >= 500 && response.status < 600);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`locales/${lng}/translation.json`, {});
|
||||
const { ok, status } = response;
|
||||
|
||||
if (!ok) {
|
||||
responder(`i18n: failed loading ${lng}`, status >= 500 && status < 600);
|
||||
|
||||
return;
|
||||
languageCache[lng] = await response.json();
|
||||
} catch (error) {
|
||||
return responder(error.message, false);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
languageCache[lng] = data;
|
||||
|
||||
return responder(null, data);
|
||||
} catch (error) {
|
||||
responder(error.message, false);
|
||||
}
|
||||
}
|
||||
|
||||
create (): void {
|
||||
// no creation
|
||||
return responder(null, languageCache[lng]);
|
||||
}
|
||||
}
|
||||
|
||||
7
packages/react-components/src/i18n/cache.ts
Normal file
7
packages/react-components/src/i18n/cache.ts
Normal file
@ -0,0 +1,7 @@
|
||||
// Copyright 2017-2020 @polkadot/react-components authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
const languageCache: Record<string, Record<string, string>> = {};
|
||||
|
||||
export default languageCache;
|
||||
Loading…
Reference in New Issue
Block a user