Design System トップへ戻る

i18n 多言語対応

翻訳ファイルの構造、キー命名規則、補間構文、複数形ルール、UI設計ガイドライン。すべてのプロダクトで一貫した多言語体験を実現する。

フォルダ構成

ロケールファイルは JSON 形式で i18n/locales/ に配置する。TypeScript ヘルパーは i18n.ts にシングルトンとして提供。

i18n/ ├── i18n.ts ← TypeScript ヘルパー(シングルトン) ├── i18n-guide.html ← このガイドページ └── locales/ ├── ja.json ← 日本語(デフォルト) ├── en.json ← 英語 ├── zh-Hans.json ← 簡体字中国語(将来) └── ko.json ← 韓国語(将来)

ロケールファイル構造

各ロケールファイルは $meta ブロックとネストされた翻訳キーで構成される。キーはドット区切りでアクセスする。

📋 $meta ブロック
フィールド説明
localestringBCP 47 ロケールコード"ja", "en", "zh-Hans"
namestring英語名"日本語"
nativeNamestring母語表記"日本語"
direction"ltr" | "rtl"テキスト方向"ltr"
pluralRulestring複数形ルール"other" (ja), "one_other" (en)
versionstring翻訳バージョン"1.0.0"
lastUpdatedstring最終更新日"2026-04-15"
// ja.json(構造例) { "$meta": { "locale": "ja", "pluralRule": "other", ... }, "common": { "save": "保存", "cancel": "キャンセル", "delete": "削除" }, "errors": { "validation": { "required": "{{field}}は必須です" } } }

キー命名規則

キーはドット区切りの階層構造。camelCase で統一し、画面やコンポーネントごとにグループ化する。

🔑 キー命名ルール
ルール説明
camelCaseすべてのキーは camelCasesettings.general.startOnBoot
画面名.セクション.項目3〜4階層以内に収めるdashboard.stats.totalRequests
_label / _accelメニュー項目のラベルとアクセラレータmenu.file._label"ファイル(F)"
_one / _other英語など複数形ルールのあるロケール用サフィックスminutesAgo_one / minutesAgo_other
共通キーは common.*OK / Cancel など複数画面で使うものcommon.save, common.cancel
a11y.* で分離スクリーンリーダー用テキストは専用名前空間a11y.skipToContent
✓ Do

画面・機能ごとに名前空間を分ける。
dashboard.activity.title
settings.notifications.enabled

✗ Don't

フラットに並べて衝突させる。
dashboardActivityTitle
settingsNotificationsEnabled

✓ Do

意味のあるキー名にする。
errors.validation.required
キー名だけで文脈がわかる。

✗ Don't

連番や略語を使う。
err.v.001
メンテナンスが困難になる。

補間(テンプレート変数)

{{変数名}} 構文で翻訳テキスト内に動的な値を埋め込む。変数名は英字の camelCase。

💬 補間デモ
出力結果
// 使い方 i18n.t('errors.validation.minLength', { field: 'メール', min: 8 }); // → "メールは8文字以上で入力してください" i18n.t('dashboard.activity.recovered', { service: 'API Gateway' }); // → "API Gateway が復旧しました — レスポンスタイムが正常化"

複数形

日本語には複数形がないため、1つのキーで済む。英語など複数形のある言語では _one / _other サフィックスで分岐する。

🔢 複数形ルール
pluralRule対象言語サフィックス説明
"other" JA 日本語、中国語、韓国語 なし(キーそのまま) 数量に関わらず同じ表現
"one_other" EN 英語、ドイツ語、フランス語 _one / _other count === 1 なら _one、それ以外は _other
// ja.json — 複数形なし "minutesAgo": "{{count}}分前" // en.json — _one / _other "minutesAgo_one": "{{count}} min ago" "minutesAgo_other": "{{count}} mins ago" // TypeScript — tp() で複数形を自動解決 i18n.tp('dashboard.activity.timeAgo.minutesAgo', 1); // → "1 min ago" i18n.tp('dashboard.activity.timeAgo.minutesAgo', 5); // → "5 mins ago"

テキスト伸長への対応

翻訳するとテキスト長が変化する。UIが壊れないよう、レイアウトに余裕を持たせる設計が必須。

📏 言語別テキスト伸長デモ
📐 テキスト伸長率ガイドライン
日本語 → 言語伸長率目安対策
英語 (EN)+20〜40%ボタンやラベルは min-width ではなく padding で余裕を確保
ドイツ語 (DE)+30〜50%カラム幅を固定しない。flex-shrink / text-overflow: ellipsis を活用
フランス語 (FR)+25〜40%ツールチップやメニューは最大幅を設けて折り返し可能に
中国語 (ZH)-10〜+10%日本語と近い長さだが漢字密度が異なる場合あり
韓国語 (KO)-5〜+15%日本語と同程度
アラビア語 (AR)+20〜30%RTL レイアウト対応が必須(後述)
✓ Do

ボタンは padding: 8px 20px で伸縮可能にする。テキストが長くなっても自然にフィット。

✗ Don't

ボタンに width: 120px と固定幅を指定する。ドイツ語で文字が溢れる。

RTL(右から左)対応

アラビア語やヘブライ語に対応する場合、レイアウトをミラーリングする必要がある。CSS 論理プロパティを活用する。

↔️ CSS 論理プロパティ置換表
物理プロパティ(避ける)論理プロパティ(推奨)
margin-leftmargin-inline-start
margin-rightmargin-inline-end
padding-leftpadding-inline-start
padding-rightpadding-inline-end
text-align: lefttext-align: start
text-align: righttext-align: end
left: 0inset-inline-start: 0
border-leftborder-inline-start
/* RTL 対応のサイドバー例 */ .sidebar { width: 220px; border-inline-end: 1px solid var(--border); /* LTR:右, RTL:左 */ padding-inline-start: 16px; /* LTR:左, RTL:右 */ } .nav-icon { margin-inline-end: 8px; /* アイコンとテキストの間隔 */ } /* html[dir="rtl"] での自動ミラーリング */ /* i18n.ts が document.documentElement.dir を自動設定 */

日付・数値・通貨フォーマット

Intl API を使い、ロケールに応じたフォーマットを適用する。翻訳ファイルにフォーマットをハードコードしない。

🌍 ロケール別フォーマットデモ
// 推奨: Intl API を使ってフォーマット const formatDate = (date: Date, locale: string) => new Intl.DateTimeFormat(locale, { year: 'numeric', month: 'long', day: 'numeric' }).format(date); const formatNumber = (n: number, locale: string) => new Intl.NumberFormat(locale).format(n); const formatCurrency = (amount: number, locale: string, currency: string) => new Intl.NumberFormat(locale, { style: 'currency', currency }).format(amount);

言語別フォントフォールバック

各言語に適したフォントスタックを定義し、文字化けや見た目の不統一を防ぐ。

🔤 言語別 font-family
言語font-family
JA 日本語'Inter', 'Hiragino Sans', 'Yu Gothic UI', 'Meiryo', system-ui, sans-serif
EN 英語・欧州'Inter', system-ui, -apple-system, sans-serif
🇨🇳 簡体字中国語'Inter', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif
🇰🇷 韓国語'Inter', 'Apple SD Gothic Neo', 'Malgun Gothic', system-ui, sans-serif
RTL アラビア語'Inter', 'SF Arabic', 'Segoe UI', 'Tahoma', sans-serif
/* ロケール変更時に font-family を切り替える */ :root { --font-sans: 'Inter', 'Hiragino Sans', system-ui, sans-serif; } /* ロケール別オーバーライド(i18n.ts の onLocaleChange で class を付与) */ [lang="zh-Hans"] { --font-sans: 'Inter', 'PingFang SC', 'Microsoft YaHei', system-ui, sans-serif; } [lang="ko"] { --font-sans: 'Inter', 'Apple SD Gothic Neo', 'Malgun Gothic', system-ui, sans-serif; } [lang="ar"] { --font-sans: 'Inter', 'SF Arabic', 'Segoe UI', sans-serif; }

TypeScript API

i18n.ts が提供するシングルトン API のリファレンス。フレームワーク非依存で Electron / Tauri / Web どこでも動く。

i18n シングルトン API
メソッド / プロパティ説明戻り値
i18n.init(options)初期化。loader 関数とデフォルトロケールを設定void
i18n.load(locale)ロケールを読み込み・切替。DOM の lang / dir も更新Promise<void>
i18n.t(key, params?)翻訳テキストを取得。見つからなければキーを返すstring
i18n.tp(key, count, params?)複数形を自動解決して翻訳を取得string
i18n.has(key)キーが存在するか確認boolean
i18n.locale現在のロケールstring
i18n.dirテキスト方向("ltr" | "rtl")string
i18n.locales読み込み済みロケール一覧string[]
i18n.meta現在ロケールのメタデータLocaleMeta
🛠 ヘルパー関数
関数説明
detectLocale(supported, default?)ブラウザ / OS の言語設定から最適なロケールを自動選択
extractKeys(obj, prefix?)JSON から全キーパスを配列で抽出($meta を除外)
compareLocales(base, target)2つのロケール間の翻訳カバレッジを比較。missing / extra / coverage% を返す
// 使用例: Electron / Tauri アプリでの初期化 import { i18n, detectLocale } from './i18n/i18n'; i18n.init({ defaultLocale: 'ja', fallbackLocale: 'ja', loader: async (locale) => { const mod = await import(`./i18n/locales/${locale}.json`); return mod.default; }, onLocaleChange: (locale) => { console.log(`Locale changed to: ${locale}`); } }); const locale = detectLocale(['ja', 'en']); await i18n.load(locale);

翻訳ワークフロー

新しいUIテキストを追加する際の手順。一貫性を保つために、必ずベースロケール(日本語)から開始する。

ベース(ja.json)に追加

新しいキーをまず ja.json に追加する。キーの命名規則に従い、適切な名前空間に配置。

英語版(en.json)を追加

同じキーを en.json に追加し、英語翻訳を記述。複数形がある場合は _one / _other サフィックスを忘れずに。

他ロケールを更新

対応しているすべてのロケールファイルに翻訳を追加。未翻訳のまま残すとフォールバックが効く。

カバレッジを確認

compareLocales() でカバレッジを確認。100% でなければ不足キーを特定して追加する。

UIで確認

各ロケールに切り替えて表示を確認。テキスト伸長でレイアウトが崩れていないかチェック。

$meta を更新

翻訳を更新したロケールの lastUpdatedversion を更新して完了。

翻訳カバレッジチェッカー

ベースロケールとターゲットロケールの JSON を貼り付けて、不足キーとカバレッジ率をリアルタイムで確認。

🔍 カバレッジチェッカー

ルールまとめ

多言語対応で守るべき基本ルール。

✓ Do

UIテキストはすべてロケールファイルに外出しする。コード内にハードコードしない。

✗ Don't

コード内に "保存しました" と直書きする。翻訳できなくなる。

✓ Do

日付・数値は Intl API でフォーマットする。ロケールが変わっても正しく表示される。

✗ Don't

日付を "2026/04/15" とハードコードする。US では "04/15/2026" が期待される。

✓ Do

CSS 論理プロパティ(margin-inline-start)を使い、RTL でも自動でミラーリング。

✗ Don't

margin-left / padding-right など物理プロパティで RTL 対応をサボる。

✓ Do

文字列連結ではなく補間を使う。"{{field}}は必須です" — 語順が異なる言語にも対応可能。

✗ Don't

field + "は必須です" と文字列結合する。英語では "is required" が後に来るとは限らない。

✓ Do

ボタンやラベルは padding ベースで伸縮可能に。テキスト伸長に自然に対応。

✗ Don't

固定幅 width: 100px でボタンを作る。ドイツ語に切り替えたら溢れる。

Senastra Design System — i18n Guide v1.0.0