多言語対応
Castor の UI は简体中文(zh-CN)、English(en-US)、日本語(ja-JP)に対応しています。ユーザーはトップバーの言語切り替えから言語を選び、選択内容はブラウザに保存されます。初回アクセス時はブラウザの言語に合わせて自動で選ばれ、一致するものがなければ英語になります。
基本の規約:中国語の原文がそのままキー
コードには中国語の原文を直接書き、その原文を翻訳キーとしても使います。
const { t } = useTranslation()
t('保存')
t('共 {{count}} 条', { count })- 中国語には翻訳ファイルが不要です。訳文が見つからない場合はキーそのもの、つまり中国語がそのまま表示されます。
- 英語と日本語の訳文は JSON ファイルに「中国語 → 訳文」の形式で書きます。
こうすることで、コードを書くときに文言ごとにキー名を考える必要がなくなり、コードの読み心地も画面と一致します。
フロントエンド
翻訳ファイル
| 場所 | 内容 |
|---|---|
ページディレクトリ内の locales/en-US.json、locales/ja-JP.json | そのページの文言。例:apps/web/src/modules/admin/pages/users/locales/ |
apps/web/src/locales/en-US.json、ja-JP.json | 共通の文言 |
apps/web/src/locales/menus/en-US.json、ja-JP.json | メニュー名。メニューの code をキーに翻訳 |
すべての locales/*.json はビルド時に 1 つの名前空間にマージされます(locales/menus/ 配下のメニューのファイルは別の menu 名前空間に入ります)。同じディレクトリにある en-US.json と ja-JP.json には、同じキーが含まれていなければなりません。
ページの翻訳の例(locales/en-US.json):
{
"导入用户": "Import users",
"删除用户 {{name}}?": "Delete user {{name}}?"
}t() が必要な文言
共通コンポーネントは、渡された文字列のプロパティを自動で翻訳します。中国語をそのまま書き、訳文を補うだけで済みます。
PageHeader、PanelのタイトルDataTableの列のtitleFormFieldsのlabel、placeholder、options、rules内の文言FilterSelect、SegmentedTabs、StatusBadge、StatCard、RowActions、ConfirmAction、FormDialogなどのコンポーネントの文言プロパティtoast.success('固定中文')のような固定の文言
次の場合は必ず t() で囲みます。
- JSX に直接書いた中国語のテキスト
- ネイティブ要素の
aria-label、title、placeholder - 変数を含む文言:
t('删除用户 ?', { name })のように書き、中国語のテンプレート文字列は使わない - グラフの軸や凡例など、その他の表示経路
デモ用のコンテンツは翻訳しない
サンプルデータやサンプルドキュメントなどのデモ用コンテンツは UI の文言ではなくデータなので、コメントで印を付けておくとスキャン時にスキップされます。
- 1 行単位:直前の行に
// i18n-ignore-next-lineを書く - ファイル全体:ファイル内に
i18n-ignore-fileコメントを書く
メニュー名の翻訳
メニュー名は(中国語で)データベースに保存されています。フロントエンドはメニューの code をもとに apps/web/src/locales/menus/<lang>.json から訳名を探し、見つからない場合はデータベース上の名前を表示します。
{
"system_users": "Users",
"system_roles": "Roles"
}メニューを追加するときは、en-US.json と ja-JP.json の両方に対応する code を追加してください。フロントエンドのテストが、seed-rbac.ts のすべてのメニューコードに英語と日本語の訳名があることをチェックします。
バックエンドのエラーメッセージの翻訳
バックエンドのコードでは、これまでどおり中国語のエラーをスローします。
throw new ServiceError('用户名已存在')フロントエンドの request.ts は、すべてのリクエストに Accept-Language ヘッダーを付けます。バックエンドはレスポンスを送る前に、このヘッダーに従って JSON レスポンスの error、message、およびインポートのエラー行の error_rows[].reason を対応する言語に翻訳します。対応言語を指定していないリクエスト(curl や API トークンのクライアントなど)には英語で返します。訳文が登録されていない文言は中国語のまま返され、エラーにはなりません。
新しく追加したエラーや通知の文言は、apps/api/src/i18n/messages.ts に英語と日本語の訳文を登録してください。
// Exact messages
export const MESSAGES = {
'用户名已存在': { 'en-US': 'Username already exists', 'ja-JP': 'ユーザー名は既に存在します' },
}
// Messages built from template literals: regex on the Chinese text, $1… in the translation
export const PATTERNS = [
{ re: /^菜单编码 (.+) 已存在$/, 'en-US': 'Menu code $1 already exists', 'ja-JP': 'メニューコード $1 は既に存在します' },
]上記は説明用の例です。実際のエントリーはモジュールごとにグループ分けして messages.ts に書かれています。
インポート / エクスポートの列見出しは中国語のまま
インポート / エクスポートするファイルの列見出しは UI の言語によって変わらず、常に中国語です。列見出しを含むエラーメッセージでも、列見出しの部分は中国語のままです。
スキャンとテストによるガード
フロントエンドのスキャン
apps/web/scripts/i18n-scan.mjs は 3 種類の問題をスキャンします。
| 問題 | 意味 |
|---|---|
missing | 中国語の文字列に対して、どの locales/*.json にも英語または日本語の訳文がない |
jsx-text | 中国語が JSX テキストに直接書かれていて、t() を通していない |
template | テンプレート文字列に中国語が含まれている |
node apps/web/scripts/i18n-scan.mjs # src 全体をスキャン。問題があれば終了コード 1
node apps/web/scripts/i18n-scan.mjs src/modules/admin/pages/users # 特定のディレクトリだけをスキャン(apps/web からの相対パス)
node apps/web/scripts/i18n-scan.mjs --json src/modules/admin/pages/users # JSON で出力新しいページのスキャン結果は、問題 0 件でなければなりません。
テスト
| テスト | チェック内容 |
|---|---|
apps/web/test/i18n.test.ts | 各 locales/ ディレクトリの en-US と ja-JP のキーが同じであること。同じキーの訳文がファイル間で食い違っていないこと。seed-rbac.ts のすべてのメニューコードに英語と日本語の訳名があること。フロントエンドのソース全体のスキャンで問題がないこと |
apps/api/test/i18n-messages.test.ts | バックエンドのソースを解析して、ユーザーに返される可能性のある中国語の文言をすべて洗い出し、いずれも messages.ts に登録されていることをチェック |
これらのテストは pnpm test と pnpm verify に含まれているため、訳文の書き漏れがあると検証ゲートで失敗します。
コードコメントは英語で書く
コードコメントは、フロントエンド、バックエンド、スクリプト、テスト、scaffold が生成するコードを含め、すべて英語で書きます。UI の文言は引き続き中国語の原文をキーとして書きます。
コメントの中でコードに実在する中国語の文字列(エラーメッセージ、翻訳キーなど)に触れる必要がある場合は、引用符またはバッククォートで囲んでください。apps/web/test/english-comments.test.ts が apps/api、apps/web、apps/mcp、docs/templates 配下のコメントをチェックします。
ページを追加するときのチェックリスト
- 画面の文字は中国語の原文で書く。
- JSX テキスト、ネイティブ属性、変数を含む文言には
t()を使う。 - ページディレクトリに
locales/en-US.jsonとlocales/ja-JP.jsonを作成する。 - 新しいメニューは
apps/web/src/locales/menus/の 2 つのファイルに訳名を追加する。 - バックエンドで追加したエラーは
apps/api/src/i18n/messages.tsに登録する。 node apps/web/scripts/i18n-scan.mjs <ページディレクトリ>を実行し、問題が 0 件であることを確認する。
