basic i18n support
This commit is contained in:
parent
7eeb90eddc
commit
d06f2384dc
12 changed files with 115 additions and 19 deletions
|
@ -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;
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue