Add i18n language change dropdown (#467)

* add dropdown and make it work

* clean up

* Update packages/extension-ui/src/util/languages.ts

* lint

* remove lang detection

* rename and clean up getLanguage options util
This commit is contained in:
Thibaut Sardan 2020-09-30 21:19:29 +02:00 committed by GitHub
parent 20c1a60f97
commit 4fb1fd6067
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 117 additions and 19 deletions

View File

@ -14,7 +14,6 @@
"@polkadot/util": "^3.4.1",
"@polkadot/util-crypto": "^3.4.1",
"i18next": "^19.7.0",
"i18next-browser-languagedetector": "^5.0.1",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-dropzone": "^11.1.0",

View File

@ -7,7 +7,7 @@ import { HashRouter } from 'react-router-dom';
import { Fonts, View } from './components';
import './i18n';
import './i18n/i18n';
export default function createView (Entry: React.ComponentType, rootId = 'root'): void {
const rootElement = document.getElementById(rootId);

View File

@ -0,0 +1,47 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import languageCache from './cache';
type Callback = (error: string | null, data: unknown) => void;
type LoadResult = [string | null, Record<string, string> | boolean];
const loaders: Record<string, Promise<LoadResult>> = {};
export default class Backend {
type = 'backend'
static type: 'backend' = 'backend'
async read (lng: string, _namespace: string, responder: Callback): Promise<void> {
if (languageCache[lng]) {
return responder(null, languageCache[lng]);
}
// eslint-disable-next-line @typescript-eslint/no-misused-promises
if (!loaders[lng]) {
loaders[lng] = this.createLoader(lng);
}
const [error, data] = await loaders[lng];
return responder(error, data);
}
async createLoader (lng: string): Promise<LoadResult> {
try {
const response = await fetch(`locales/${lng}/translation.json`, {});
if (!response.ok) {
return [`i18n: failed loading ${lng}`, response.status >= 500 && response.status < 600];
} else {
languageCache[lng] = await response.json() as Record<string, string>;
return [null, languageCache[lng]];
}
} catch (error) {
return [(error as Error).message, false];
}
}
}

View File

@ -0,0 +1,6 @@
// Copyright 2017-2020 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
const languageCache: Record<string, Record<string, string>> = {};
export default languageCache;

View File

@ -1,13 +1,15 @@
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
import i18n from 'i18next';
import LanguageDetector from 'i18next-browser-languagedetector';
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import uiSettings from '@polkadot/ui-settings';
i18n
.use(LanguageDetector)
import Backend from './Backend';
i18next
.use(initReactI18next)
.use(Backend)
.init({
backend: {},
debug: false,
@ -16,6 +18,7 @@ i18n
escapeValue: false
},
keySeparator: false,
lng: uiSettings.i18nLang,
load: 'languageOnly',
nsSeparator: false,
react: {
@ -24,6 +27,13 @@ i18n
returnEmptyString: false,
returnNull: false
})
.catch(console.error);
.catch((error: Error): void =>
console.log('i18n: failure', error)
);
export default i18n;
uiSettings.on('change', (settings): void => {
i18next.changeLanguage(settings.i18nLang
).catch(console.error);
});
export default i18next;

View File

@ -3,7 +3,7 @@
import { Theme, ThemeProps } from '../types';
import React, { useCallback, useContext, useState, useEffect } from 'react';
import React, { useCallback, useContext, useEffect, useState, useMemo } from 'react';
import styled, { ThemeContext } from 'styled-components';
import settings from '@polkadot/ui-settings';
@ -11,6 +11,7 @@ import FullScreenIcon from '../assets/fullscreen.svg';
import { ActionText, Checkbox, Dropdown, Menu, MenuDivider, MenuItem, Svg, Switch, ThemeSwitchContext, themes } from '../components';
import useTranslation from '../hooks/useTranslation';
import { windowOpen } from '../messaging';
import getLanguageOptions from '../util/getLanguageOptions';
interface Option {
text: string;
@ -33,6 +34,7 @@ function MenuSettings ({ className, reference }: Props): React.ReactElement<Prop
const [prefix, setPrefix] = useState(`${settings.prefix === -1 ? 42 : settings.prefix}`);
const themeContext = useContext<Theme>(ThemeContext);
const setTheme = useContext(ThemeSwitchContext);
const languageOptions = useMemo(() => getLanguageOptions(), []);
useEffect(() => {
settings.set({ camera: camera ? 'on' : 'off' });
@ -51,6 +53,13 @@ function MenuSettings ({ className, reference }: Props): React.ReactElement<Prop
[setTheme]
);
const _onChangeLang = useCallback(
(value: string): void => {
settings.set({ i18nLang: value });
},
[]
);
return (
<Menu
className={className}
@ -92,6 +101,19 @@ function MenuSettings ({ className, reference }: Props): React.ReactElement<Prop
value={`${prefix}`}
/>
</MenuItem>
<MenuDivider />
<MenuItem
className='setting'
title={t<string>('Language')}
>
<Dropdown
className='dropdown'
label=''
onChange={_onChangeLang}
options={languageOptions}
value={settings.i18nLang}
/>
</MenuItem>
{isPopup && (
<>
<MenuDivider />

View File

@ -0,0 +1,24 @@
// Copyright 2019-2020 @polkadot/extension-ui authors & contributors
// SPDX-License-Identifier: Apache-2.0
interface Option {
info?: string;
isDisabled?: boolean;
isHeader?: boolean;
text: React.ReactNode;
value: string | number;
}
export default function getLanguageOptions (): Option[] {
return [
// default/native
{
text: 'English',
value: 'en'
},
{
text: '汉语',
value: 'zh'
}
];
}

View File

@ -3073,7 +3073,6 @@ __metadata:
enzyme: ^3.11.0
enzyme-adapter-react-16: ^1.15.4
i18next: ^19.7.0
i18next-browser-languagedetector: ^5.0.1
react: ^16.13.1
react-dom: ^16.13.1
react-dropzone: ^11.1.0
@ -11913,15 +11912,6 @@ fsevents@^1.2.7:
languageName: node
linkType: hard
"i18next-browser-languagedetector@npm:^5.0.1":
version: 5.0.1
resolution: "i18next-browser-languagedetector@npm:5.0.1"
dependencies:
"@babel/runtime": ^7.5.5
checksum: 4e80cc054cb1d1600c421d53153b7752aaca07a3ca9f394d64962aa34f901c3e61d2a409d76436c1d76ea8bb1e6ab99a4888dcfbfc3a8de0a489abcd2bb6563d
languageName: node
linkType: hard
"i18next-scanner@npm:^2.11.0":
version: 2.11.0
resolution: "i18next-scanner@npm:2.11.0"