Skip to content

i18n(国際化)#

View Markdown

ICU MessageFormat 2、ビルド時チェック、ロケール対応ルーティングを備えた、Ox Content の組み込み国際化です。

セットアップ#

vite.config.ts で i18n を有効にします。

// vite.config.ts
import { defineConfig } from "vite";
import { oxContent } from "@ox-content/vite-plugin";

export default defineConfig({
  plugins: [
    oxContent({
      i18n: {
        enabled: true,
        defaultLocale: "en",
        locales: [
          { code: "en", name: "English" },
          { code: "ja", name: "日本語" },
        ],
      },
    }),
  ],
});

オプション#

オプション 既定 説明
enabled boolean false i18n の有効 / 無効
dir string 'content/i18n' 辞書ディレクトリへのパス(プロジェクトルート相対)
defaultLocale string 'en' 既定ロケールタグ
locales LocaleConfig[] [] 利用可能なロケール
hideDefaultLocale boolean true URL で既定ロケールの接頭辞を隠す
check boolean true ビルド時に i18n チェックを実行する
functionNames string[] ['t', '$t'] ソースコードで検出する翻訳関数名

hideDefaultLocale#

true(既定)のとき、既定ロケールには URL 接頭辞が付きません。

  • /page は既定ロケール(en)を返します
  • /ja/page は日本語ロケールを返します

false のとき、すべてのロケールに接頭辞が付きます。

  • /en/page は英語を返します
  • /ja/page は日本語を返します

LocaleConfig#

interface LocaleConfig {
  /** BCP 47 locale tag (e.g., 'en', 'ja', 'zh-Hans') */
  code: string;
  /** Display name for this locale (e.g., 'English', '日本語') */
  name: string;
  /** Text direction. @default 'ltr' */
  dir?: "ltr" | "rtl";
}

RTL 対応の例:

locales: [
  { code: "en", name: "English" },
  { code: "ja", name: "日本語" },
  { code: "ar", name: "العربية", dir: "rtl" },
];

辞書の構造#

辞書は dir ディレクトリ内でロケールごとに整理します。

content/i18n/
  en/
    common.json
    navigation.json
    messages.yaml
  ja/
    common.json
    navigation.json
    messages.yaml

各ファイルが名前空間になります。たとえば common.jsoncommon. で始まるキーを作ります。

JSON 形式#

{
  "greeting": "Hello {$name}",
  "farewell": "Goodbye",
  "nav": {
    "home": "Home",
    "about": "About"
  }
}

平坦化されたキーは common.greetingcommon.farewellcommon.nav.homecommon.nav.about になります。

YAML 形式#

greeting: "Hello {$name}"
farewell: "Goodbye"
nav:
  home: "Home"
  about: "About"

ICU MessageFormat 2#

辞書の値は ICU MessageFormat 2 構文を使えます。

単純な変数#

Hello {$name}

複数形 / マッチ#

.input {$count :number}
.match $count
one {{You have {$count} item.}}
* {{You have {$count} items.}}

ローカル宣言#

.local $host = {$name}
.local $guest = {$other}
{{Welcome {$host} and {$guest}!}}

仮想モジュール#

プラグインは、翻訳ユーティリティ付きの virtual:ox-content/i18n モジュールを提供します。

import {
  t,
  createIntl,
  getLocaleFromPath,
  localePath,
  i18nConfig,
  dictionaries,
} from "virtual:ox-content/i18n";

t(key, params?, locale?)#

任意のパラメーター置換付きでキーを翻訳します。

t("common.greeting", { name: "World" }); // "Hello World"
t("common.greeting", { name: "World" }, "ja"); // "こんにちは World"

getLocaleFromPath(pathname)#

URL パス名からロケールコードを取り出します。

getLocaleFromPath("/ja/about"); // 'ja'
getLocaleFromPath("/about"); // 'en' (default locale)

localePath(pathname, locale)#

指定ロケール向けのローカライズパスを組み立てます。hideDefaultLocale を尊重します。

localePath("/about", "ja"); // '/ja/about'
localePath("/about", "en"); // '/about' (when hideDefaultLocale is true)

Intl ヘルパー#

仮想モジュールには、リッチなローカライズ UI 向けの Intl ベース整形ヘルパーがあります。

const ja = createIntl("ja-JP", { date: { timeZone: "Asia/Tokyo" } });
ja.date(new Date(), { dateStyle: "long" });
ja.number(1234.5, { style: "currency", currency: "JPY" });
ja.relativeTime(-1, "day", { numeric: "auto" });
ja.list(["docs", "api", "cli"]);
ja.displayName("en-US", "language");

i18nConfig#

解決済みの i18n 設定オブジェクトです。

const { enabled, defaultLocale, locales, hideDefaultLocale } = i18nConfig;

dictionaries#

読み込んだすべての辞書を、ロケールごとの平坦なキー・値マップとして持ちます。

// Record<string, Record<string, string>>
const { en, ja } = dictionaries;
console.log(en["common.greeting"]); // "Hello {$name}"

ビルド時チェック#

check が有効(既定)のとき、プラグインはビルド時に静的解析を行い、次を報告します。

チェック 重大度 説明
欠落キー Error ソースコードで使われているが、辞書にないキー
未使用キー Warning 辞書にあるが、ソースコードで使われていないキー
型の不一致 Error 同じキーで MF2 プレースホルダー変数がロケール間で違う
構文エラー Error 辞書の値の MF2 構文が不正

出力例#

[ox-content:i18n] error: Missing key 'common.title' in locale 'ja'
[ox-content:i18n] warning: Unused key 'common.legacy' in locale 'en'
[ox-content:i18n] error: Type mismatch for key 'common.greeting': locale 'en' uses {$name, $count}, locale 'ja' uses {$name}

翻訳キーの抽出#

チェッカーは、ソースファイルから翻訳キーの使用を自動で走査します。

TypeScript / JavaScript#

t("common.greeting");
$t("common.greeting");
this.t("common.greeting");
i18n.t("common.greeting");

Markdown#

{{t('common.greeting')}}
{{ $t('nav.home') }}

スキャナーは src/.ts.tsx.js.jsx と、content/.md.mdx を探します。

NAPI API#

プログラムから使う場合、次の関数が @ox-content/napi から使えます。

loadDictionaries(dir)#

ディレクトリから辞書を読み込み、メタデータを返します。

import { loadDictionaries } from "@ox-content/napi";

const result = loadDictionaries("content/i18n");
// { localeCount: 2, locales: ['en', 'ja'], errors: [] }

loadDictionariesFlat(dir)#

辞書を読み込み、ロケールごとの平坦なキー・値マップを返します。

import { loadDictionariesFlat } from "@ox-content/napi";

const dicts = loadDictionariesFlat("content/i18n");
// { en: { 'common.greeting': 'Hello {$name}', ... }, ja: { ... } }

validateMf2(message)#

ICU MessageFormat 2 文字列を検証します。

import { validateMf2 } from "@ox-content/napi";

const result = validateMf2("Hello {$name}");
// { valid: true, errors: [], astJson: '...' }

const invalid = validateMf2("Hello {$name");
// { valid: false, errors: ['...'], astJson: null }

checkI18n(dictDir, usedKeys)#

指定した辞書ディレクトリと使用キーに対して、すべての i18n チェックを実行します。

import { checkI18n } from "@ox-content/napi";

const result = checkI18n("content/i18n", ["common.greeting", "nav.home"]);
// { diagnostics: [...], errorCount: 0, warningCount: 1 }

extractTranslationKeys(source, filePath, functionNames?)#

TypeScript / JavaScript ソースから翻訳キーを抽出します。

import { extractTranslationKeys } from "@ox-content/napi";

const keys = extractTranslationKeys(`const msg = t('common.greeting');`, "src/App.tsx", [
  "t",
  "$t",
]);
// [{ key: 'common.greeting', filePath: 'src/App.tsx', line: 1, column: 18, endColumn: 35 }]

CLI と LSP#

CLI#

@ox-content/vite-plugin は、単体の i18n チェック用に oxct バイナリをインストールします。

vpx oxct i18n check --dict content/i18n --src src

LSP サーバー#

エディタ連携用の LSP サーバーがあり、t() 呼び出し内の翻訳キー補完を提供します。

Last updated: