- 国際化対応のつらみポイント
- prettier-plugin-sort-jsonを使う
- sort-package.jsonでソートする
- 参考URL
- Node.jsのAPIを使ってJavaScriptでソートする
- 所感
- まとめ
国際化対応のつらみポイント
自分の関わっているプロダクトでは国際化対応(internalization = i18n対応)を行なっている。
国際化対応はi18n
と名前に入っているライブラリで実施することが多い。1
大体どのライブラリでも、翻訳の流れは決まっている。
- テキスト定義をJSON形式の翻訳ファイルに記載する
- レンダリングしたい箇所で描画したい定義のキーを指定する
- 定義ファイルにキーがあれば指定の言語でレンダリングできる
ここまでは分かりやすくていいのだが、問題はアプリの運用保守期間が長くなったとき。 機能追加やヘルプテキストを差し込んでいく量に比例して定義量も増えてしまう。すると、次のようなつらみポイントが出てくる。
- キーをどこに書くか迷う
- キー名を探すのが大変
- キーの被りがないか探すのが大変
Multiple Translationのような機能を使いつつファイルを分ければいいかとも思ったが、結局keyのソートに迷う問題は解決しない。 大体定義はJSONの形で持つことが多いので、コミット前に翻訳ファイルをいい感じにソートしたい。
prettier-plugin-sort-jsonを使う
最新バージョンはPrettier v3以降でしか利用できないが、Gudahttさんが作ったprettier-plugin-sort-jsonを使うという手段がある。
https://www.npmjs.com/package/prettier-plugin-sort-json
方法
ライブラリをインストールする。
npm install --save-dev prettier-plugin-sort-json
prettierrc
のplugins
セクションに追記する。
"plugins": ["prettier-plugin-sort-json"]
あとは通常通りPrettierを実行する。
npx prettier --write JSONファイルのパス
huskyとlint-stagedを組み合わせてセットアップしておけば、コミット前に実行できる。
// package.json "lint-staged": { "*.json": "prettier --write" // コミット前にJSONファイルにPrettierタスクを実行する },
所感
単純で良いし、プラグインをセットするだけでいいので楽。 ただ、長期メンテをする際はライブラリのバージョンアップ対応が必要なのが面倒か。
参考URL
- https://www.npmjs.com/package/prettier-plugin-sort-json
- https://prettier.io/docs/en/plugins
- https://zenn.dev/risu729/articles/latest-husky-lint-staged
- 実際にやってみたもの
sort-package.jsonでソートする
package.jsonのkey情報でソートするライブラリがあるので、それを使ってJSONファイルもソートしてしまう案。
https://www.npmjs.com/package/sort-package-json
方法
ライブラリをインストールする。
npm install --save-dev sort-package-json
scriptsにsort-package-json
の実行と「JSONファイルに対してPrettierを実行する」タスクを登録する。
"scripts": { "prettify-json": "prettier --write \"**/*.json\"", // JSONファイルにはPrettierを上書き実行する "sort-json": "sort-package-json" // sort-package-jsonで並べ替えする }
sort-package-jsonの実行スクリプトをpackage.json
に登録する。
"scripts": { "format-json": "npm run sort-json && npm run prettify-json" // 2つのタスクを実行する = ソートして上書き }
あとはscriptsを実行すれば良い。
npm run format-json
lint-stagedを使うなら次のようにしておく。
// package.json "lint-staged": { "*.json": "npm run sort-json && npm run prettify-json" // コミット前にJSONファイルにPrettierタスクを実行する },
所感
package.jsonをソートする用のライブラリで翻訳ファイルをソートするのは直感的ではないなと思った。
翻訳ファイルのネストが深いのであればこちらを使うほうが良いかもしれない。
package.jsonはscripts
キーにさらに設定の名前のキーを入れて…のようにネストを深くするタイプである。
オブジェクトのネストが深いものをプログラムでソートするのは面倒なので、最初から対応しているものを使うのもいいかなと思った。
参考URL
Node.jsのAPIを使ってJavaScriptでソートする
Node.jsのFile Stream APIを使って翻訳ファイルを読み取り、Array.prototype.sort()
した結果をObjectに変換する。
その結果をPrettierでフォーマットしてから上書きしてやれば良い。
fs.readFileSync
を使って多言語定義ファイルを読み取り- PrettierのAPIでJSON形式に変換
- 結果を
fs.writeFileSync
で上書きする。
prettier.format
APIが非同期に動作するため、この結果を待って書き込むようにする。
こうしないと処理が終わる前に書き込みを試みてしまい、うまくいかない。
sortObjectKeysの処理で落ちてしまわないように、一度JSONをオブジェクト形式に変換しておく。 適当に定義を書いてコミットしても、変換をかけてから実行するので安全。
上書き前は文字列からJSONに戻す。
const fs = require('fs'); const prettier = require('prettier'); // 翻訳ファイルのパスを配列で指定する const FILE_PATHS = ['./src/i18n/translation/ja.json', './src/i18n/translation/en.json']; // オブジェクトのキーをソートする関数 const sortObjectKeys = (unsortedObj) => { return Object.keys(unsortedObj) .sort() // 引数がないのでUTF-16 コード単位でソートされる .reduce((obj, key) => { obj[key] = unsortedObj[key]; // ソートした結果でkeyを並び替える。valueはそのまま return obj; }, {}); } // ファイルが2つあるので繰り返し処理する for (const FILE_PATH of FILE_PATHS) { // ファイルを読み込む const rawJson = fs.readFileSync(FILE_PATH, 'utf8'); const json = JSON.parse(rawJson); // キーをソート const sortedJson = sortObjectKeys(json); // Prettierでフォーマット const formattedJson = prettier.format(JSON.stringify(sortedJson), { parser: 'json' }); // JSONのフォーマット完了を待ってからファイルに書き込む formattedJson.then((formattedJson) => { fs.writeFileSync(FILE_PATH, formattedJson); }); }
ファイルを作れたら、package.jsonにスクリプトを登録する。
// 配置先が`<rootDir>/scripts/sort-json.js`だった場合 "scripts": { "sort-json": "node scripts/sort-json.js", // 追加 }
実行すればソートされた状態で上書きされる。
npm run sort-json
lint-stagedを使うなら次のようにしておく。
"scripts": { "./src/i18n/translation/*.json": "npm run sort-json" }
所感
ライブラリのメンテを考慮しなくて良いのは気楽。 ただ、Node.jsのバージョンアップでAPIが変わってしまうとメンテは必要。JavaScript系はメンテを仲良くやる必要があるので、どの手段を取っても手入れは必要か。
ソートロジックにテストを書けると良いが、ESModules⇆CommonJSの壁にぶち当たってしまう。 また、ファイルを読み取って書き込むというところはテストでは再現できない。ソートロジックとJSON変換くらいしかテストできなさそう。
参考URL
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
- https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce
- https://nodejs.org/api/fs.html#fsreadfilesyncpath-options
- https://nodejs.org/api/fs.html#fswritefilesyncfile-data-options
- https://prettier.io/docs/en/api.html#prettierformatsource-options
- 実際にやってみたもの
まとめ
意外と簡単にソートできてよかった。やることが単純なのでいいのかもしれない。 ネストが深くなればなるほどソートが面倒になるので、翻訳ファイルもシンプルに保つようになりそう。
ライブラリ系は配信やメンテが止まってしまうと代替手段を探すなり、メンテするなりが必要。 APIを使って自分でソートしても良いが、これもメンテは必要。今はGitHub Copilotなんかもあるしスクリプトくらいなら自分で作ってもいいかもしれない。