使いどころ
create-react-app
を使って作成したプロジェクトを、
i18n-next
で多言語化したとき、翻訳データの*.json
を手書きするのが面倒。
*.tsx
や*.js
で翻訳する部分を自動で見つけて*.json
に追記してほしい!
今回の例として紹介するのは、フロントエンドだけで完結する、非常に簡単なSPAです。
i18n-next
は、locizeを導入できたり、バックエンド開発にも使えるので、知っておくとお得です。
ハマりポイント
先にハマりポイント解説します。
少し前はbabel-plugin-i18next-extract
が流行ったようです。しかし、webpack
の設定変更が必須となります。create-react-app
を使って制作しているアプリケーションの場合、webpack
の設定を変更することは、npm eject
というコマンドを使うと実現できますが、不可逆であり得策ではありません。そのため、babel-plugin-i18next-extract
を使うのはやめたほうが良さそうです。そこで、利用するのは、公式ドキュメントにも記載のある、i18next-parser
です。
#extraction-tools)にも記載のある、i18next-parser
です。
まずは、公式ドキュメントにも記載のある、i18next-parser
です。
npm install -g i18next-parser
設定
公式ドキュメントを参考に、i18next-parserの設定を書きます。
module.exports = {
locales: ['en', 'ja', 'cn'],
output: 'src/locales/$LOCALE/$NAMESPACE.json',
}
- locale: 欲しい分だけ言語を追記
- output: 位置がsrcの中でないと、tsxからimport出来ないので、デフォルト設定を上書き
npm の scripts を設定します。
"scripts": {
"i18next-extract": "i18next 'src/**/*.{tsx,ts}'"
}
ひとまず設定が終わったので、翻訳ファイルのjsonを書き出します。
npm run i18next-extract
すると、下記のような構成で出力されるはずです。
./
┃
┣━ src
┃ ┣━ locales
┃ ┃ ┣━ cn
┃ ┃ ┃ ┗━ translation.json
┃ ┃ ┣━ en
┃ ┃ ┃ ┗━ translation.json
┃ ┃ ┗━ jp
┃ ┃ ┗━ translation.json
コード変更
ここから、ソースコードに翻訳用の設定を足していきます。
まずは、トップレベルのReactファイルにi18nの初期化設定を書き込みます。
この時、先ほど出力されたtranslation.json
ファイルを設定します。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.sass';
import reportWebVitals from './reportWebVitals';
import App from "./components/App";
import { createBrowserHistory } from "history"
import { Router } from 'react-router';
import HeaderApp from './components/header';
// i18n
import i18next from 'i18next';
import { initReactI18next } from 'react-i18next';
import jaCommon from './locales/ja/translation.json'
import enCommon from './locales/en/translation.json'
import cnCommon from './locales/cn/translation.json'
i18next.use(initReactI18next).init({
debug: true,
resources : {
ja:{common:jaCommon},
en:{common:enCommon},
cn:{common:cnCommon},
},
lng: 'ja',
fallbackLng: 'ja',
keySeparator: false,
interpolation: { escapeValue: false },
});
const history = createBrowserHistory({ basename: '/' });
ReactDOM.render(
<React.StrictMode>
<Router history={history}>
<HeaderApp/>
<App></App>
</Router>
</React.StrictMode>,
document.getElementById('root')
);
reportWebVitals(console.log);
<App />
の定義は下記です。
import React from 'react';
import i18next from 'i18next';
import { useTranslation } from 'react-i18next';
import { useEffect } from 'react'
import ja_Trans from '../locales/ja/maincontent.json'
import en_Trans from '../locales/en/maincontent.json'
import cn_Trans from '../locales/cn/maincontent.json'
export function Translate() {
const { t } = useTranslation('maincontent');
useEffect(() => {
i18next.addResources('ja', 'maincontent', ja_Trans);
i18next.addResources('en', 'maincontent', en_Trans);
i18next.addResources('cn', 'maincontent', cn_Trans);
}, [])
return (
<React.StrictMode>
<p>{t('message')}</p>
</React.StrictMode>
);
}
class App extends React.Component {
render() {
return <Translate/>;
}
}
export default App;
これだけだと、まだ言語切り替えが出来ません。先ほどのindex.tsx
に書かれている<HeaderApp />
の詳細を書いていきます。
import React from 'react';
import { FunctionComponent } from "react";
import { LanugageSelector } from "./LanguageSelector";
import { useTranslation } from 'react-i18next'
import { useState } from 'react';
import { useEffect } from 'react';
const HeaderApp: FunctionComponent = () => {
const [t, i18n] = useTranslation();
const languages = [{ code: "ja", name: "JAPANESE" }, { code: "en", name: "ENGLISH" }, { code: "cn", name: "CHINESE" }];
const [currentLang, setCurrentLang] = useState("ja");
// Change language of i18n
useEffect(() => {
i18n.changeLanguage(currentLang);
}, [currentLang, i18n])
// Handler
const handleLanguageSelectionChange = (lang: string) => {
setCurrentLang(lang);
}
return (
<React.StrictMode>
<header>
<div>
<ul>
<LanugageSelector languages={languages} selectedLanguage={currentLang} onLanguageSelectionChange={handleLanguageSelectionChange} />
</ul>
</div>
</header>
</React.StrictMode>
);
}
export default HeaderApp;
<LanugageSelector />
の定義は下記です。
import React, { FunctionComponent } from "react";
export type LanguageSelectorProps = {
languages: { code: string, name: string }[],
selectedLanguage: string,
onLanguageSelectionChange: (lang: string) => void,
}
export const LanugageSelector: FunctionComponent<LanguageSelectorProps> = (props) => {
const langs = props.languages.map((lang) => {
return (lang.code === props.selectedLanguage) ? '' : <li><p onClick={() => props.onLanguageSelectionChange(lang.code)}>{lang.name}</p></li>
});
return (
<div className="i18n">
{langs}
</div>
);
}
この<LanguageSelector/>
により、<p>ENGLISH</p>
、<p>JAPANESE</p>
、<p>CHINESE</p>
が表示されます。これをクリックすると表示が切り替わる仕組みです。3項演算子で、「現在表示中の言語」は消しています。
再びのextract
ここまで書けたら、もう一度、先ほどのコマンドを実行してみます。
npm run i18next-extract
./
┃
┣━ src
┃ ┣━ locales
┃ ┃ ┣━ cn
┃ ┃ ┃ ┣━ maincontent.json
┃ ┃ ┃ ┗━ translation.json
┃ ┃ ┣━ en
┃ ┃ ┃ ┣━ maincontent.json
┃ ┃ ┃ ┗━ translation.json
┃ ┃ ┗━ jp
┃ ┃ ┃ ┣━ maincontent.json
┃ ┃ ┗━ translation.json
ここまで来たら、おめでとうございます!
あとは、ひたすら翻訳を書いていくだけです。
まとめ
npm start
の中でnpm run i18next-extract
を動かすにはどうするのでしょう?
Excelsior!