0
0
Fork 0

basic i18n support

This commit is contained in:
Xeltica 2021-09-14 09:22:04 +09:00
parent 7eeb90eddc
commit d06f2384dc
12 changed files with 115 additions and 19 deletions

View file

@ -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}

View file

@ -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>

View file

@ -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/`;

View file

@ -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
View file

@ -0,0 +1,5 @@
declare module '*.json5' {
const data: any;
export default data;
}

View file

@ -0,0 +1,5 @@
{
translation: {
title: "Misskey Alert",
}
}

View 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': '日本語',
};

View file

@ -0,0 +1,5 @@
{
translation: {
title: "みす廃アラート",
}
}

View file

@ -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;