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:
parent
20c1a60f97
commit
4fb1fd6067
@ -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",
|
||||
|
||||
@ -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);
|
||||
|
||||
47
packages/extension-ui/src/i18n/Backend.ts
Normal file
47
packages/extension-ui/src/i18n/Backend.ts
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
||||
6
packages/extension-ui/src/i18n/cache.ts
Normal file
6
packages/extension-ui/src/i18n/cache.ts
Normal 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;
|
||||
@ -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;
|
||||
@ -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 />
|
||||
|
||||
24
packages/extension-ui/src/util/getLanguageOptions.ts
Normal file
24
packages/extension-ui/src/util/getLanguageOptions.ts
Normal 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'
|
||||
}
|
||||
];
|
||||
}
|
||||
10
yarn.lock
10
yarn.lock
@ -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"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user