React react-i18next 「1つの」 json file から各言語のテキストを展開する
map を使わず読み込む場合
en.json
{ "welcome": "Welcome to i18next", "hello": "Hello", "changeLang": "Change Language" }
ja.json
{ "welcome": "i18next へようこそ!", "hello": "こんにちわ", "changeLang": "言語を切り替える" }
App.js/import
import React, { useState, useEffect } from 'react'; import i18n from 'i18next' import { initReactI18next } from 'react-i18next' import { useTranslation } from 'react-i18next'
App.js/ヘッダー部分
i18n.use(initReactI18next).init({ resources: { en: { translation: enJson, }, ja: { translation: jaJson, }, }, lng: 'en', fallback: 'en', interpolation: {escapeValue: false}, });
App.js/ Function App 前半部分
function App() { const [t, i18n] = useTranslation(); const [lang, setLang] = useState('en'); useEffect( () => { i18n.changeLanguage(lang) } ,[lang, i18n]);
App.js/ Function App return 部分
return ( <div className="App"> <p>{t('welcome')}</p> <div> <button onClick={ () => setLang('en') }> English </button> <button onClick={ () => setLang('ja') }> Japanese </button> </div> </div> ); } export default App;
result
これで切り替えが動く
json ファイルからまとめて map する場合
locale.json
[ { "name": "en", "welcome": "Welcome to i18next", "hello": "Hello", "changeLang": "Change Language" }, { "name": "zh", "welcome": "欢迎光临 i18next", "hello": "你好", "changeLang": "用中文" }, { "name": "ja", "welcome": "i18next へようこそ!", "hello": "こんにちわ", "changeLang": "言語を切り替える" }, { "name": "ko", "welcome": "i18next에 오신 것을 환영합니다", "hello": "안녕하세요", "changeLang": "한국어 변경" }, { "name": "es", "welcome": "Bienvenida a i18next", "hello": "Hola", "changeLang": "usar español" } ]
App.js/ json の読み込み確認
import locales from './locales/lang.json' console.log('Locales read:') console.table(locales)
これで json ファイルの中に 一つの配列が入っていて、その中に
name: "en", welcome: "welcome to i..." と入っていることを読み取ることができた。
あとはこのデータをいかに react-i18next に渡すかになる。
init の失敗例
これで失敗して苦戦した。
i18n.use(initReactI18next).init({ resources: { locales.map( (locale) => { console.log(locale.name); {locale.name}:{ translation: {locale} } } ) }, lng: 'en', fallback: 'en', interpolation: {escapeValue: false}, });
これだと動かない。
i18n は object で受け取ることになってるのに、配列で渡しているからだ。
全体配列から1オブジェクト事に
object で json ファイル自体を書き直す手もあるが、ファイルはそのままでも、 取り出し方を工夫すれば object として渡せる。
array.reduce() を使って配列一つ一つに処理を施し、object として作る。
object はこれ { }
i18n.use(initReactI18next).init({ resources: locales.reduce( (stack, locale) => { stack[locale.name] = { translation: locale } return stack }, {}), lng: 'en', fallback: 'en', interpolation: { escapeValue: false } });
これで 前述の locale.json を当てる
[ { hoge: ua, }, { huu: gya, } , ]
の形ででかい配列にオブジェクトが5つ入ってるやつ。
stack[local.name] が en が入り
locale として今回使われている
配列の 0 こ目の object が当たる。
その object を i18next さんの translation に当ててひと言語分完了。
次に stack[local.name] に zh
locale として 1 こ目の json...
と当たっていき、全てが当てはめられる。
5 言語のボタンも map で書く
明日やる
React で ./src/locales/ の中身を全て読み取ろうとしたが無理だった - react は複数の json を読み込めない!
why
前回の記事で 5言語の切り替えを実装したが、毎回
import xxJson from './locales/xx.json' xx: { translation: xxJson, }, <button onClick={ () => setLang('xx') }> XXXXXX
これを書かないと言語を追加できないクソ設計なので直す
やったこと
react-i18next で json ファイルを map しようとした
fs で map しようとするけどどの代替ライブラリを使ってもできなかった
Reactでfsは使えません。 React動かすのは大抵ブラウザ上でしょ?
説明ページの中身をよく読んで、これはNode.js上で動かす事を想定しているのか、 JavaScript上で動かす事を想定しているのか推測してください。
teratail で完璧な回答があった.....
解決策
一つの json file にデータを置いて map する
map する先の json は 1 file!
それの実行は次回の記事
記事のURL をここに入れろ!!!
失敗した取り組み
fs をまず使ってみるがダメ
path と fs のライブラリを使ってフォルダから読み込んで cll に出力してみる
両方 Node の根幹だから import は必要ない。
const localesDir = path.join(process.cwd(), 'locales') console.log(localesDir);
これで
/locales
path が 作れてる
fs.readdirSync を...
あれ?
fsWEBPACK_IMPORTED_MODULE_3_default.a.readdirSync is not a function
ググってみると
se brfs , https://github.com/browserify/brfs
fs.readFileSync() and fs.readFile() static asset browserify transform
ブラウザだから使えないようなことが書いてある
fs 以外で使えるものをいろいろググってみるが全滅
brfs は毎回ビルドになる
browserify というシステムの brfs
コンパイラで毎回ビルドするのはやってることと違うので拒否
browserify-fs を使うが同じエラーが出る
この fs 互換で行けるか?
import fs from 'browserify-fs' const fileNames = fs.readdirSync(localesDir);
browserify_fsWEBPACK_IMPORTED_MODULE_3_default.a.readdirSync is not a function
readdirSync がない.... 同じエラーだ...
brfs の方だと bundle しないといちいちビルドしないと実行できなくなるってことになる、それは開発効率下がるからしたくない
list-react-files を試すが fs を結局使っている
list-react-files というライブラリがあるらしい
npm i list-react-files import listReactFiles from 'list-react-files' listReactFiles(__dirname).then(files => console.log(files))
Promise か....展開して... あれ?
glob.js:547 Uncaught (in promise) TypeError: fs.readdir is not a function at Glob.push../node_modules/glob/glob.js.Glob._readdir (glob.js:547) at Glob.push../node_modules/glob/glob.js.Glob._processGlobStar (glob.js:625) at Glob.push../node_modules/glob/glob.js.Glob._process (glob.js:362) at new Glob (glob.js:170) at glob (glob.js:75) at index.js:29 at new Promise (<anonymous>) at listReactFiles (index.js:28)
listReactFiles は glob を使っていて、glob も fs.readFiles を使っているからダメらしい。
indeed, it throws Unhandled Rejection (TypeError): fs.readdir is not a function – Mugen Jun 18 '19 at 13:34
コメントに書いてある通りやんけ
list-files を使う
リプライがないけど、最後の回答を試してみる
import find from 'list-files' find(function(result) { console.log(result); } ,{ dir: localesDir, name: 'json' });
index.js:19 Uncaught TypeError: exec is not a function
exec を 使うらしい
const exec = require('child_process').exec;
実際に使うと
dentifier 'exec' has already been declared (14:7)
で
import zhJson from './locales/zh.json'
を指摘されてしまう。exec がすでにされているってことらしい
exec の基礎が何もわかっていない....
fs でのエラーハンドリング
いろいろ試したけどファイルからの json ファイル名の取得すらできない....
そもそもエラーハンドリングをしないと使えない?
https://www.codegrepper.com/code-examples/javascript/list+all+files+in+dir+react
fs.readdir(localesDir, function (err, files) { //handling error if (err) { return console.log('Unable to scan directory: ' + err); } //listing all files using forEach files.forEach(function (file) { // Do whatever you want to do with the file console.log(file); }); });
fsWEBPACK_IMPORTED_MODULE_3_default.a.readdir is not a function
まず readdir が fs にないって言われてしまってどうしようもない...
React react-i18next 挨拶サイト 5言語の切り替えをできるようにした
why
前回の記事で
日英の切り替えだけ作って満足していたら
ブラッシュアップに 対応言語に、中国語(簡体字)、中国語(繁体字)、スペイン語、韓国語、ポルトガル語を追加しろ それができれば、設計の最小限のレベルだ
と言われたので。
問題点
import xxJson from './locales/xx.json' xx: { translation: xxJson, }, <button onClick={ () => setLang('xx') }> XXXXXX
言語ひとつ追加するたびにこれ毎回全箇所書くのシステムと呼べないだろ
言語追加するために button を何個も作らないといけない。map すれば楽になりそう。
button が多い。select にすればコンパクトになるけど、select で発火するのを実装する必要がある。クリックの回数が増える。
基礎機能
react-i18next-try の Repository を流用
繁体字どうするの
繁体字は中国語のコードの zh 以外に何があるのかわからなかったが、
書体の問題なので、国-書体
として
zh-hans, zh-hant, にするべきらしい。
言語コードまとめ
これを参考にして
es, ko, pt, のスペイン、コリア、ポルトガル、を使用する。
とりあえず中国語を追加する
前提として前回の記事で日英切り替えができる状態になっている
locales/zh.json を作成
Google Translate で翻訳する
{ "welcome": "欢迎光临 i18next", "changeLang": "用中文" }
作成
import zhJson from './locales/zh.json' zh: { translation: zhJson, },
App.js で import して zh に当てると
ボタン固定で作成できた。しかし現在の言語をハイライトするか、もしくは非表示にしないと違和感があるな...
参考に厚労省のサイト見たら現在の言語が一番上に出ている設計になっていた。
切り替えると警告が出た
The following pages are translated by a machine translation system. Note that the machine translation system doesn't guarantee 100% correctness. Some proper nouns might not be translated correctly
機械翻訳機能、すごい!
挨拶一覧
Japanese, ja, こんにちわ, ようこそ, English, en, Hello, Welcome to, Chinese "welcome": "欢迎光临 i18next", "hello": "你好", "changeLang": "用中文" Spanish "welcome": "Bienvenida a i18next", "hello": "Hola", "changeLang": "usar español" Korean "welcome": "i18next에 오신 것을 환영합니다", "hello": "안녕하세요", "changeLang": "한국어 변경"
韓国とスペインと
とりあえずこれで 5言語挨拶できた
まとめ
前述の通り、言語ごとのテキストをまとめて 展開して読み込めるようにする必要がある
次回以降の記事でやる。
React react-i18next で日英切り替え Ark 生物紹介サイトを作る
why
前回の記事で、react-i18next で二言語の切り替えができるようになった
これを使って何かを作りたい!
Hatena ではなく、自分で攻略サイトを作りたい
Next.js で react-i18next が使えそうにないことからガッツリはつくれないが、
生物紹介の2言語版ならできるだろう
1 ページ構成で bootstrap で右上に切り替えボタンを表示する予定
Result
問題点
map してないから全部 return のとこに書かないといけない。構造化して map できるか試してない
json から 引っ張って画像を出せるか試してない
json の string に書くのがめっちゃめんどくさい
Markdown の読み取りのライブラリと matter での配列変換はあるんだから、そこから持ってくれば json なんかを試すより楽にテキストが書けるか...
と思うけどそもそも fs でファイルを読み取るのがフロントではできない、Next がいる。
json ではなく markdown から引っ張ってきて当てられるかいろいろ組まないと、テキストを入れる手間がやばい、もしくは cms と組み合わせるか、
それをするには http ちゃんと使えないと無理。
git init
久しぶりに React のみで立ち上げたので 再び npm と git で立ち上げるやり方を思い出す
npm create-react-app ark-ab-dinos
CRA で React App 作成
cd ark-ab-dinos
そのプロジェクトに移動
GitHub で Repository を作成
git@github.com:kaede0902/ark-ab-dinos.git
ssh url 発行して copy
git init
git remote add origin git@github.com:kaede0902/ark-ab-dinos.git
git add .gitignore
git add .
初期化して git のリモートとローカルの接続
テキスト、辞書ファイルとして locale/ を作る
src/locales/en.json
と ja.json を作る
i18n react-i18next をセット
i18n, initReactI18next, useTransition,
切り替え用の
useEffect, useState,
をセットする
前回の記事と同様。てか App.js を copy
i18next, react-i18next を npm i
中身を書き換える
locales を書き換えた
タイトルを変えていない
title を toggle できるようにする
useEffect( () => { i18n.changeLanguage(lang) document.title = lang } ,[lang, i18n]);
これで ja, en にタブの名前がなったが、これではおかしい
useEffect( () => { i18n.changeLanguage(lang) document.title = t('welcome') } ,[lang, i18n]);
useEffect で doc.title に t 関数で welcome のテキストを表示するようにできたが
react_devtools_backend.js:2430 src/App.js Line 31:6: React Hook useEffect has a missing dependency: 't'. Either include it or remove the dependency array react-hooks/exhaustive-deps
警告が出てしまう。
直前で t を定義してるんだが...
動いているのでとりあえずこれでよしとする。
問題が発生したらシンプルに
document.title = `Ark Abberation Dinos (${lang})`
こうする。
[
タイトルを動的に一応反映できた。ja en で三項演算子使って適切なタイトルのテキスト仕込めば行けそう。
"rava": "ラヴェジャー", "rava_desc": "野生では出血攻撃を持つ。ラプターやオオカミと同様にパックボーナスで攻防が倍増する仲間にしても単体ではオオカミと比べると火力が低く、ラプターの倍の硬さの強さしかない。しかしこのサイズで 500kg 以上も掲載量があり、 Zipline を昇り降りできるのがかなり有用。"
これと
"rava": "Ravager", "rava_desc": "Only wild one has bleed attack."
これを json に仕込むことで
生物紹介を出せた!!!
React react-i18next での言語切り替えを試した
why
Next.js と react-intl での言語切り替えがうまくいかなかった、react-18next があると聞いた
t, translate 関数での二言語の切り替えが react-intl より簡単そうに見えた
結果
Next.js ではこのコードは使えなかった
React.js 単体では動いた
実装
とりあえず1 からトップだけ作ってみることにする。
ボタンでの言語切り替え。
この「素振り」を参考にする
問題点
TS の React.FC の型がどうして参考記事で使われているのか理解できていない
() => setLang(lang === 'en' ? 'ja' : 'en')
のロジックは理解できたが、これを自分で思いつける自信がない
公式の GitHub の サンプルを見たが、どうして class component も使うのか理解できない
install
npm i i18next react-i18next
いつも通り npm i
t('trans_key')と setLang でのトグル切り替えを仕込む
<p>{t('welcome')}</p> <div> <button onClick={ () => setLang(lang === 'en' ? 'ja' : 'en') }> {t('changeLang')} </button> </div>
最初に jsx に
{ t('translation_key') }
と t 関数と引数の key となる string で t 関数を用いた変換された key を書く
これはあくまで 変換の際に辞書ファイルを検索するための key であり、実際のメッセージではない。
次に btn で クリックされたときに
() => setLang(lang === 'en' ? 'ja' : 'en')
setLang
が即時関数で発動し、
引数には lang
という変数の中身が 初期値の ja から en になっていた場合であれば
en からの変更なので ja に
en じゃない場合は ja になるので、ja からの変更なので en に
変更する。
i18n の import と辞書の配列の作成
先ほどの index.js の前半 で i18n の import と 変換用の辞書のリソースを入れる
先ほど作った t(welcome), t(changLang)
はここからとってくる。
json だし その技術があれば CMS で外部からも持って来れそうだな...
import i18n from 'i18next' import { initReactI18next } from 'react-i18next'
i18n と initReactI18next を import して初期値を入れる。
i18n.use(initReactI18next).init({ resources: { en: { translation: { 'welcome': 'Welcom to i18next', 'changeLang': 'Change Language', }, }, ja: { translation: { 'welcome': 'i18next にようこそ。' 'changeLang': '言語を切り替える', }, }, }, lng: 'en', fallback: 'en', interpolation: {escapeValue: false}, });
検索先のリソースとして
welcome, changeLang をそれぞれの言語で入れる。
lng で最初の言語、fallback が何かあったときの言語だろう
inter polation と escape value は不明
Hook を使って言語の切り替え関数を作る
index のさらに上部に 本命の useTranslation
での 作成した辞書から引っ張ってくるライブラリの import と、react-hooks での state と副作用での切り替え機能を作る
記事では ts のため React.FC を使っているが、function のみで実装できた。
import { useTranslation } from 'react-i18next' import React, { useState, useEffect } from 'react';
useTranslation と useState, useEffect を import
function App() { const [t, i18n] = useTranslation(); const [lang, setLang] = useState('en');
traslate, i18n, lang, setLang の hook を作成
useEffect( () => { i18n.changeLanguage(lang) } ,[lang, i18n]);
lang か i18n が変化したときに、 i18n の changeLanguage を使って 現状の lang の値にする。
JS で type を使っていてエラーが出る
key を間違えていたが、React.FC がないと思って js に React.FC を使ったところ
react_devtools_backend.js:2430 ./pages/index.js:27:10 Syntax error: Const declarations require an initialization value 25 | }); 26 | > 27 | const Home: React.FC = () => { | ^ 28 | const [t, i18n] = useTranslation(); 29 | const [lang, setLang] = useState('en'); 30 |
const Home: React.FC だと初期値がない?からだめとエラーが出た
react_devtools_backend.js:2430 ./pages/index.js:27:8 Syntax error: Unexpected token, expected ";" 25 | }); 26 | > 27 | let Home: React.FC = () => { | ^ 28 | const [t, i18n] = useTranslation(); 29 | const [lang, setLang] = useState('en'); 30 |
let にしても : React.FC が受け入れられない
Next との食い合わせが悪いのか...???
Syntax error: Const declarations require an initialization value 25 | }); 26 | > 27 | const Home:React.FC = (lang) => { | ^ 28 | const [t, i18n] = useTranslation(); 29 | const [lang, setLang] = useState('en');
中身を入れてもダメ、どうすればいいのか詰まったが、
js なのに tsの記述があるのは不自然
とご指摘をいただいた。
確かに元のコードは tsx だ。
調べなおしたらそもそも React.FC は React の type だった。
React で無事に動かす
key を統一して 無事にトグルすることができた。
次やるべきこと
これを Next.js でも結局できるのか試す。
React.js のみでは 複数ページのルーティングが手間だったり meta 周りの実装に苦労するのでやはり最終的には Next で動かさなければしっかりとしたものはつくれないだろう
TS の型を入れると何が嬉しいのかわかるようにする。
ここからやりたいこと
レスポンシブにして json ファイルにデータを分けて、かねてより作りたかった Ark Aberration 生物図鑑のサイトを公開する
json ファイルに辞書配列を分ける
i18n.use(initReactI18next).init({ resources: { en: { translation: { "welcome": "Welcom to i18next", "changeLang': "Change Language" }, }, ja: { translation: { "welcome": "i18next にようこそ。", "changeLang": "言語を切り替える" }, }, }, lng: 'en', fallback: 'en', interpolation: {escapeValue: false}, });
この App.js に書いている resources の鍵と翻訳を別のファイルに分ける。
src/locales/ を作成
en.json を作って...
json は single quote がダメらしい。Shift 押したくないんだがな...
これでよし
{ "welcome": "i18next にようこそ。", "changeLang": "言語を切り替える" }
ja.json も作成。
json ファイルを読み込む
import enJson from './locales/en.json' import jaJson from './locales/ja.json'
enJson, jaJson, という形で App.js で読み込み
resources: { en: { translation: enJson, }, ja: { translation: jaJson, }, },
初期化のリソースの英語の訳を enJson, 日本語を jaJson, に当てる
これで無事動いた。
あとは key で t() ちゃんが検索してくれるので react-intl みたいに id をつける必要もなし!!!やったね!t ちゃん!!!
これで攻略ブログ作るぞ〜〜〜!!
Next.js getInitialProps で http で UserAgent を取得する
jsx 三項演算子の分岐でさらに条件式を使う方法
why
サーバーサイドでは動かないコードを render 部分で処理させる必要があったので使うことになった
三項演算子 での 処理
三項演算子での 条件が true の場合の処理で js を使いたい場合、
{ (true) ? ( { console.log(hoge) } <div> true </div> ) : ( <div>no window</div> ) }
このように
{ console.log() } <div> someTextHere <div>
と書くと
internal server error 505 で
実行されないが
{ (true) ? ( <div> { console.log(hoge) } true </div> ) : ( <div>no window</div> ) }
このように
<div> { console.log() } messageTextHere <div>
と html タグの内部に入れると実行される。
しかし、html タグの内部に書いても
{ (true) ? ( <div> { const hoge = 'hogemsg' console.log(hoge) } true </div> ) : ( <div>no window</div> ) }
変数定義してそれを出力する処理はできない。
なのでここで処理しなくてはならない場合は、変数定義というクッションをおかずに直接処理を console か html に書き出せば行ける
<div> { 1 * 3 } </div>
このように。内部計算ならできるようだ。
しかし jsx では if は使えないので
<div> { (false)?'y':'n' } </div>
三項演算子ならできた。