basic i18n support
This commit is contained in:
parent
7eeb90eddc
commit
d06f2384dc
@ -43,6 +43,8 @@
|
||||
"dayjs": "^1.10.2",
|
||||
"delay": "^4.4.0",
|
||||
"fibers": "^5.0.0",
|
||||
"i18next": "^20.6.1",
|
||||
"i18next-browser-languagedetector": "^6.1.2",
|
||||
"json5-loader": "^4.0.1",
|
||||
"koa": "^2.13.0",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
@ -59,6 +61,7 @@
|
||||
"pug": "^3.0.0",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"react-i18next": "^11.12.0",
|
||||
"react-modal-hook": "^3.0.0",
|
||||
"react-redux": "^7.2.4",
|
||||
"react-router-dom": "^5.2.1",
|
||||
|
@ -2,11 +2,15 @@ import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { welcomeMessage } from '../misc/welcome-message';
|
||||
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export type HeaderProps = {
|
||||
hasTopLink?: boolean;
|
||||
};
|
||||
|
||||
export const Header: React.FC<HeaderProps> = ({hasTopLink, children}) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const message = React.useMemo(
|
||||
() => welcomeMessage[Math.floor(Math.random() * welcomeMessage.length)] , []);
|
||||
|
||||
@ -14,7 +18,7 @@ export const Header: React.FC<HeaderProps> = ({hasTopLink, children}) => {
|
||||
<header className={'xarticle card shadow-4 mt-5 mb-3'}>
|
||||
<div className="body">
|
||||
<h1 className="text-primary mb-0" style={{ fontSize: '2rem' }}>
|
||||
{hasTopLink ? <Link to="/">みす廃アラート</Link> : 'みす廃アラート'}
|
||||
{hasTopLink ? <Link to="/">{t('title')}</Link> : t('title')}
|
||||
</h1>
|
||||
<h2 className="text-dimmed ml-1">{message}</h2>
|
||||
{children}
|
||||
|
@ -1,15 +1,16 @@
|
||||
import React, { useCallback, useEffect, useReducer, useState } from 'react';
|
||||
import React, { useCallback, useEffect, useReducer } from 'react';
|
||||
import { alertModes } from '../../common/types/alert-mode';
|
||||
import { IUser } from '../../common/types/user';
|
||||
import { visibilities, Visibility } from '../../common/types/visibility';
|
||||
import { Visibility } from '../../common/types/visibility';
|
||||
import { useGetSessionQuery } from '../services/session';
|
||||
import { defaultTemplate } from '../../common/default-template';
|
||||
import { Card } from './Card';
|
||||
import { Theme } from '../misc/theme';
|
||||
import { API_ENDPOINT, LOCALSTORAGE_KEY_TOKEN } from '../const';
|
||||
import { API_ENDPOINT, LOCALSTORAGE_KEY_LANG, LOCALSTORAGE_KEY_TOKEN } from '../const';
|
||||
import { useDispatch } from 'react-redux';
|
||||
import { changeTheme, showModal } from '../store/slices/screen';
|
||||
import { changeLang, changeTheme, showModal } from '../store/slices/screen';
|
||||
import { useSelector } from '../store';
|
||||
import { languageName } from '../langs';
|
||||
|
||||
type SettingDraftType = Partial<Pick<IUser,
|
||||
| 'alertMode'
|
||||
@ -53,7 +54,7 @@ export const SettingPage: React.VFC = () => {
|
||||
];
|
||||
|
||||
const currentTheme = useSelector(state => state.screen.theme);
|
||||
const [currentLang, setCurrentLang] = useState<string>('ja-JP');
|
||||
const currentLang = useSelector(state => state.screen.lang);
|
||||
|
||||
const availableVisibilities: Visibility[] = [
|
||||
'public',
|
||||
@ -205,9 +206,14 @@ export const SettingPage: React.VFC = () => {
|
||||
</div>
|
||||
|
||||
<h2>言語設定</h2>
|
||||
<select name="currentLang" className="input-field" value={currentLang} onChange={e => setCurrentLang(e.target.value)}>
|
||||
<option value="ja-JP">日本語</option>
|
||||
<option value="en-US">英語</option>
|
||||
<select name="currentLang" className="input-field" value={currentLang} onChange={(e) => {
|
||||
dispatch(changeLang(e.target.value));
|
||||
}}>
|
||||
{
|
||||
(Object.keys(languageName) as Array<keyof typeof languageName>).map(n => (
|
||||
<option value={n} key={n}>{languageName[n]}</option>
|
||||
))
|
||||
}
|
||||
</select>
|
||||
</Card>
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
export const LOCALSTORAGE_KEY_TOKEN = 'token';
|
||||
export const LOCALSTORAGE_KEY_THEME = 'THEME';
|
||||
export const LOCALSTORAGE_KEY_THEME = 'theme';
|
||||
export const LOCALSTORAGE_KEY_LANG = 'lang';
|
||||
|
||||
export const API_ENDPOINT = `//${location.host}/api/v1/`;
|
||||
|
@ -1,7 +1,11 @@
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import i18n from 'i18next';
|
||||
import { initReactI18next } from 'react-i18next';
|
||||
|
||||
import { App } from './App';
|
||||
import { LOCALSTORAGE_KEY_TOKEN } from './const';
|
||||
import { resources } from './langs';
|
||||
|
||||
document.body.classList.add('dark');
|
||||
|
||||
@ -18,4 +22,17 @@ if (token) {
|
||||
document.cookie = '';
|
||||
}
|
||||
|
||||
console.log(resources);
|
||||
|
||||
i18n
|
||||
.use(initReactI18next)
|
||||
.init({
|
||||
resources,
|
||||
lng: localStorage['lang'] ?? 'ja_JP',
|
||||
interpolation: {
|
||||
escapeValue: false // react already safes from xss
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ReactDOM.render(<App/>, document.getElementById('app'));
|
||||
|
5
src/frontend/json5.d.ts
vendored
Normal file
5
src/frontend/json5.d.ts
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
declare module '*.json5' {
|
||||
const data: any;
|
||||
|
||||
export default data;
|
||||
}
|
5
src/frontend/langs/en_US.json5
Normal file
5
src/frontend/langs/en_US.json5
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
translation: {
|
||||
title: "Misskey Alert",
|
||||
}
|
||||
}
|
12
src/frontend/langs/index.ts
Normal file
12
src/frontend/langs/index.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import enUS from './en_US.json5';
|
||||
import jaJP from './ja_JP.json5';
|
||||
|
||||
export const resources = {
|
||||
'en_US': enUS,
|
||||
'ja_JP': jaJP,
|
||||
};
|
||||
|
||||
export const languageName = {
|
||||
'en_US': 'English',
|
||||
'ja_JP': '日本語',
|
||||
};
|
5
src/frontend/langs/ja_JP.json5
Normal file
5
src/frontend/langs/ja_JP.json5
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
translation: {
|
||||
title: "みす廃アラート",
|
||||
}
|
||||
}
|
@ -1,5 +1,7 @@
|
||||
import { createSlice, PayloadAction } from '@reduxjs/toolkit';
|
||||
import { LOCALSTORAGE_KEY_THEME } from '../../const';
|
||||
import i18n from 'i18next';
|
||||
|
||||
import { LOCALSTORAGE_KEY_LANG, LOCALSTORAGE_KEY_THEME } from '../../const';
|
||||
import { Theme } from '../../misc/theme';
|
||||
import { Modal } from '../../modal/modal';
|
||||
|
||||
@ -7,12 +9,14 @@ interface ScreenState {
|
||||
modal: Modal | null;
|
||||
modalShown: boolean;
|
||||
theme: Theme;
|
||||
language: string;
|
||||
}
|
||||
|
||||
const initialState: ScreenState = {
|
||||
modal: null,
|
||||
modalShown: false,
|
||||
theme: localStorage[LOCALSTORAGE_KEY_THEME] ?? 'system',
|
||||
language: localStorage[LOCALSTORAGE_KEY_LANG] ?? i18n.language ?? 'ja_JP',
|
||||
};
|
||||
|
||||
export const screenSlice = createSlice({
|
||||
@ -27,13 +31,18 @@ export const screenSlice = createSlice({
|
||||
state.modal = null;
|
||||
state.modalShown = false;
|
||||
},
|
||||
changeTheme: (state, action: PayloadAction<Theme>) => {
|
||||
changeTheme: (state, action: PayloadAction<ScreenState['theme']>) => {
|
||||
state.theme = action.payload;
|
||||
localStorage[LOCALSTORAGE_KEY_THEME] = action.payload;
|
||||
},
|
||||
changeLang: (state, action: PayloadAction<ScreenState['language']>) => {
|
||||
state.language = action.payload;
|
||||
localStorage[LOCALSTORAGE_KEY_LANG] = action.payload;
|
||||
i18n.changeLanguage(action.payload);
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const { showModal, hideModal, changeTheme } = screenSlice.actions;
|
||||
export const { showModal, hideModal, changeTheme, changeLang } = screenSlice.actions;
|
||||
|
||||
export default screenSlice.reducer;
|
||||
|
@ -6,10 +6,10 @@
|
||||
"rootDir": "./src/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
|
||||
"strict": true, /* Enable all strict type-checking options. */
|
||||
"strictPropertyInitialization": false, /* Enable strict checking of property initialization in classes. */
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"src/@types"
|
||||
], /* List of folders to include type definitions from. */
|
||||
"typeRoots": [
|
||||
"node_modules/@types",
|
||||
"src/@types"
|
||||
], /* List of folders to include type definitions from. */
|
||||
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
|
||||
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
|
||||
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
|
||||
|
33
yarn.lock
33
yarn.lock
@ -150,7 +150,7 @@
|
||||
"@babel/plugin-transform-react-jsx-development" "^7.14.5"
|
||||
"@babel/plugin-transform-react-pure-annotations" "^7.14.5"
|
||||
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.9.2":
|
||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.14.5", "@babel/runtime@^7.14.6", "@babel/runtime@^7.9.2":
|
||||
version "7.15.4"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
|
||||
integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
|
||||
@ -2525,6 +2525,13 @@ hosted-git-info@^2.1.4:
|
||||
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
|
||||
integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
|
||||
|
||||
html-parse-stringify@^3.0.1:
|
||||
version "3.0.1"
|
||||
resolved "https://registry.yarnpkg.com/html-parse-stringify/-/html-parse-stringify-3.0.1.tgz#dfc1017347ce9f77c8141a507f233040c59c55d2"
|
||||
integrity sha512-KknJ50kTInJ7qIScF3jeaFRpMpE8/lfiTdzf/twXyPBLAGrLRTmkz3AdTnKeh40X8k9L2fdYwEp/42WGXIRGcg==
|
||||
dependencies:
|
||||
void-elements "3.1.0"
|
||||
|
||||
http-assert@^1.3.0:
|
||||
version "1.5.0"
|
||||
resolved "https://registry.yarnpkg.com/http-assert/-/http-assert-1.5.0.tgz#c389ccd87ac16ed2dfa6246fd73b926aa00e6b8f"
|
||||
@ -2586,6 +2593,20 @@ human-signals@^2.1.0:
|
||||
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
|
||||
integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
|
||||
|
||||
i18next-browser-languagedetector@^6.1.2:
|
||||
version "6.1.2"
|
||||
resolved "https://registry.yarnpkg.com/i18next-browser-languagedetector/-/i18next-browser-languagedetector-6.1.2.tgz#68565a28b929cbc98ab6a56826ef2faf0e927ff8"
|
||||
integrity sha512-YDzIGHhMRvr7M+c8B3EQUKyiMBhfqox4o1qkFvt4QXuu5V2cxf74+NCr+VEkUuU0y+RwcupA238eeolW1Yn80g==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.6"
|
||||
|
||||
i18next@^20.6.1:
|
||||
version "20.6.1"
|
||||
resolved "https://registry.yarnpkg.com/i18next/-/i18next-20.6.1.tgz#535e5f6e5baeb685c7d25df70db63bf3cc0aa345"
|
||||
integrity sha512-yCMYTMEJ9ihCwEQQ3phLo7I/Pwycf8uAx+sRHwwk5U9Aui/IZYgQRyMqXafQOw5QQ7DM1Z+WyEXWIqSuJHhG2A==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.0"
|
||||
|
||||
iconv-lite@0.4.24:
|
||||
version "0.4.24"
|
||||
resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
|
||||
@ -4270,6 +4291,14 @@ react-dom@^17.0.2:
|
||||
object-assign "^4.1.1"
|
||||
scheduler "^0.20.2"
|
||||
|
||||
react-i18next@^11.12.0:
|
||||
version "11.12.0"
|
||||
resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-11.12.0.tgz#2a053321b9b7a876d5baa7af55a12d986117bffc"
|
||||
integrity sha512-M9BT+hqVG03ywrl+L7CK74ugK+4jIo7AeKJ17+g9BoqJz2+/aVbs8SIVXT4KMQ1rjIdcw+GcSRDy1CXjcz6tLQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.14.5"
|
||||
html-parse-stringify "^3.0.1"
|
||||
|
||||
react-is@^16.13.1, react-is@^16.6.0, react-is@^16.7.0, react-is@^16.8.1:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
@ -5420,7 +5449,7 @@ vary@^1.1.2, vary@~1.1.2:
|
||||
resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc"
|
||||
integrity sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=
|
||||
|
||||
void-elements@^3.1.0:
|
||||
void-elements@3.1.0, void-elements@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/void-elements/-/void-elements-3.1.0.tgz#614f7fbf8d801f0bb5f0661f5b2f5785750e4f09"
|
||||
integrity sha1-YU9/v42AHwu18GYfWy9XhXUOTwk=
|
||||
|
Loading…
Reference in New Issue
Block a user