i18n(国際化)#
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.json は common. で始まるキーを作ります。
JSON 形式#
{
"greeting": "Hello {$name}",
"farewell": "Goodbye",
"nav": {
"home": "Home",
"about": "About"
}
}
平坦化されたキーは common.greeting、common.farewell、common.nav.home、common.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() 呼び出し内の翻訳キー補完を提供します。