1
/
5

hi18n (i18nライブラリ) の使い方: 翻訳IDを動的に決定する

Photo by Nathan Dumlao on Unsplash

hi18nとは

hi18n は現在Wantedlyで開発中の、TypeScript/JavaScript向け翻訳テキスト管理ライブラリ (i18nライブラリの一種) です。

GitHub - wantedly/hi18n: message internationalization meets immutability and type-safety
Installation: npm install @hi18n/core @hi18n/react-context @hi18n/react npm install -D @hi18n/cli # Or: yarn add @hi18n/core @hi18n/react-context @hi18n/react yarn add -D @hi18n/cli Put the following file named like src/locale/index.ts: And you can use th
https://github.com/wantedly/hi18n


基本の使い方は以下の記事で説明しています。

hi18n (i18nライブラリ) の使い方 | Wantedly Engineer Blog
hi18n は現在Wantedlyで開発中の、 TypeScript/JavaScript向け翻訳テキスト管理ライブラリ ( i18nライブラリの一種) です。 開発の動機や設計思想、詳しい特徴などは 別の記事で紹介しています が、大まかには以下の特徴があります。 翻訳IDや翻訳の引数に正しく型がつく。 Reactのような宣言的な設計との相性がよい。 JavaScriptの既存の開発環境 (Webpackなど) に自然に統合される。 たとえば、hi18nのために特別な設定をしなくても、ホットリロードが使え
https://www.wantedly.com/companies/wantedly/post_articles/399501

本稿では発展的な使い方として、条件に応じて異なるメッセージを出し分ける方法について説明します。

何が問題なのか

たとえばWebサイトのナビゲーションのためにメニューを用意することを考えます。メニューのレンダリングをループで以下のように書いたとします。

const menus = ["posts", "profile", "settings"];
menus.map((menu) => (
  <li><a href={`/${menu}`}>{menu}</a></li>
);

ここでメニューのタイトルをより説明的にするために、翻訳関数 t を使って以下のように書いたとします。

const menus = ["posts", "profile", "settings"];
menus.map((menu) => (
  <li><a href={`/${menu}`}>{t(`example/menu/${menu}/title`)}</a></li>
);

この場合、以下の3つの翻訳を定義すれば動作します。

  • example/menu/posts/title
  • example/menu/profile/title
  • example/menu/settings/title

しかし、このようにテンプレートリテラル等を使って動的に翻訳IDを生成する方法はhi18nでは推奨されていません。それは利用箇所をツールで正しく検出できないからです。

この状態でhi18nのCLIツールで同期コマンドを実行したとします。

yarn hi18n sync 'src/**/*.ts' 'src/**/*.tsx'

すると、定義していたはずの翻訳IDはコメントアウトされてしまいます。

export default new Catalog({
  // 実際には使っているのにコメントアウトされてしまう

  // "example/menu/posts/title": msg("投稿"),
  // "example/menu/profile/title": msg("プロフィール"),
  // "example/menu/settings/title": msg("設定"),
});

hi18nではt関数の引数の文字列リテラルを調べることで翻訳が使用済みかどうかを判定しているため、このようなケースでは誤検出が発生してしまうのです。

(もしこのようなケースを正しく扱おうとすると、 menu という変数がどのような値を取りうるのかを解析しなければなりません。この対応はコストのわりに利点が少ないため、hi18nでは一律でルールに沿っていないものを無視しています)

Step1: 文字列連結をやめる

hi18nに翻訳IDを正しく認識させるには、まず文字列の連結をやめる必要があります。連結済みの文字列をどこかに保管しておき、その文字列を参照して使います。これには大きく2つの戦略があります。

ひとつは、他のデータと一緒に保管する方法です。以下の例では、メニューの名前の配列を拡張し、メニューの情報を一括でオブジェクトとして保管するようにしています。

const menus = [
  {
    name: "posts",
    titleId: "example/menu/posts/title",
  },
  {
    name: "profile",
    titleId: "example/menu/profile/title",
  },
  {
    name: "settings",
    titleId: "example/menu/settings/title",
  },
];
menus.map(({ name, titleId }) => (
  <li><a href={`/${name}`}>{t(titleId)}</a></li>
);

もう一つは、別途翻訳IDのマップを用意する方法です。

const menus = ["posts", "profile", "settings"];
const menuTitleIds = {
  posts: "example/menu/posts/title",
  profile: "example/menu/profile/title",
  settings: "example/menu/settings/title",
};
menus.map((menu) => (
  <li><a href={`/${menu}`}>{t(menuTitleIds[menu])}</a></li>
);

どちらの方法でもOKです。このように文字列連結をやめることができたら次のステップに進めます。

Step 2: 文字列をマークする

Step1で、翻訳IDが文字列リテラルとして明示的に現れる状態になりました。しかし、その文字列リテラルが翻訳IDであるということはまだhi18nのCLIには認識できません。

hi18nのCLIに翻訳IDを認識させるために、文字列リテラルをtranslationId関数で囲みます。第一引数には、useI18n等に渡しているのと同じbookのインスタンスを渡す必要があります。

const menus = [
  {
    name: "posts",
    titleId: translationId(book, "example/menu/posts/title"),
  },
  {
    name: "profile",
    titleId: translationId(book, "example/menu/profile/title"),
  },
  {
    name: "settings",
    titleId: translationId(book, "example/menu/settings/title"),
  },
];
menus.map(({ name, titleId }) => (
  <li><a href={`/${name}`}>{t(titleId)}</a></li>
);

これでhi18nのCLIが正しく翻訳IDを検出できるようになりました。

Step 3: t.dynamicを使う

ここまででCLIは正しく動くようになりますが、以下の問題があります。

  • t関数で呼び出すときの型の不一致。
  • hi18nのESLint ruleを使っている場合、t関数の位置で不要な警告が発生してしまう。

そこで、翻訳IDがtranslationIdで登録済みであることをhi18nに伝えるために、tのかわりにt.dynamicを使うようにします。

const menus = [
  {
    name: "posts",
    titleId: translationId(book, "example/menu/posts/title"),
  },
  {
    name: "profile",
    titleId: translationId(book, "example/menu/profile/title"),
  },
  {
    name: "settings",
    titleId: translationId(book, "example/menu/settings/title"),
  },
];
menus.map(({ name, titleId }) => (
  <li><a href={`/${name}`}>{t.dynamic(titleId)}</a></li>
);

もしt関数ではなく <Translate> コンポーネントを使っている場合は、同等機能を提供する <Translate.Dynamic> を使うことができます。

ESLint ruleを使う

t関数を正しく使っているかをsyncコマンドを動かしながら調べるのは大変です。hi18nにはESLint pluginが同梱されており、syncコマンドがうまく動かないパターンに対して警告を出してくれます。

ESLintを導入していない場合は、まずESLintのセットアップを行う必要があります。

ESLintが導入できたら、ESLint pluginをインストールします。

npm install -D @hi18n/eslint-plugin
# または:
yarn add -D @hi18n/eslint-plugin

設定ファイルのextendsでプラグインの推奨設定を参照します。

// .eslintrc.jsの場合
module.exports = {
  extends: [/* ... */, "plugin:@hi18n/recommended"],
  /* ... */
};

これでhi18nの使い方に問題があるときにはESLintが警告してくれるようになります。

次に読む

hi18n (i18nライブラリ) の紹介 (1) 設計思想と基本方針 | Wantedly Engineer Blog
hi18n は現在Wantedlyで開発中の、 TypeScript/JavaScript向け翻訳テキスト管理ライブラリ ( i18nライブラリの一種) です。 本記事ではhi18nの重要な設計上の判断やその背景について説明します。 React向けのi18nライブラリとしては react-intl (FormatJS) や LinguiJS などが知られており、Wantedlyでも LinguiJSを使っています 。 このLinguiJSはよくできたi18nライブラリですが、現代的なJavaScriptに
https://www.wantedly.com/companies/wantedly/post_articles/400195
hi18n (i18nライブラリ) 入門&紹介シリーズ目次 | Wantedly Engineer Blog
hi18n は現在Wantedlyで開発中の、TypeScript/JavaScript向け翻訳テキスト管理ライブラリ (i18nライブラリの一種) です。https://github.com/...
https://www.wantedly.com/companies/wantedly/post_articles/406614
Invitation from Wantedly, Inc.
If this story triggered your interest, have a chat with the team?
Wantedly, Inc.'s job postings
2 Likes
2 Likes

Weekly ranking

Show other rankings
Like Masaki Hara's Story
Let Masaki Hara's company know you're interested in their content