objectstack-i18n について
ObjectStack の多言語翻訳バンドルを作成・検証するスキルです。スキーマに沿った翻訳、実行時の読み込み、変数の補間に加え、CLI でカバレッジと生成物の鮮度を確認できます。
主な機能
- 01対応ロケールとフォールバックの設定
- 02スキーマ検証付き翻訳バンドルの作成
- 03オブジェクトとアプリのメタデータ翻訳
- 04実行時の翻訳読み込みとマージ
- 05カバレッジと生成物の鮮度確認
活用シーン
- 01ObjectStack スタックのロケール設定
- 02オブジェクトの項目・ビュー・アクションの翻訳
- 03リリース前の翻訳漏れの発見
- 04CI での生成バンドルの検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ objectstack-ai/ objectstack --skill objectstack-i18nインストール後、Claude Code で/objectstack-i18nを入力して読み込まれたか確認します。
同梱ファイルreferences/_index.mdevals/bundles-locales-coverage.json
詳細
国際化 — ObjectStack I18n プロトコル
翻訳設定
スタックレベルの I18n 設定
objectstack.config.ts で i18n 設定を構成します。
import { defineStack } from '@objectstack/spec';
export default defineStack({
i18n: {
defaultLocale: 'en',
supportedLocales: ['en', 'zh-CN', 'ja-JP', 'es-ES'],
fallbackLocale: 'en',
},
// translations: [MyTranslations], ← register your bundles here (see below)
});
| プロパティ | 型 | 必須/デフォルト | 説明 |
|---|---|---|---|
defaultLocale | string | 必須 | デフォルトの BCP-47 ロケールコード |
supportedLocales | string[] | 必須 | サポートされるすべてのロケール |
fallbackLocale | string | 任意 | 翻訳がない場合のフォールバック |
BCP-47 ロケールコード:標準のロケールタグを使用してください(例:
en-US、zh-CN、pt-BR、en-GB)。
翻訳バンドルの作成(objects.*)
標準的な作成方法は、ロケールごとに 1 つの TranslationData を用意し、
defineTranslationBundle で組み立ててスタックに登録することです。これは、提供されている
examples/app-todo(src/translations/{en,zh-CN,ja-JP}.ts + index.ts)と同じ構成です。
// src/translations/en.ts — one TranslationData per locale
import { defineStack, defineTranslationBundle } from '@objectstack/spec';
import type { TranslationData } from '@objectstack/spec/system';
const en: TranslationData = {
objects: {
task: {
label: 'Task',
pluralLabel: 'Tasks',
fields: {
subject: { label: 'Subject', help: 'Brief title of the task' },
status: {
label: 'Status',
options: {
not_started: 'Not Started',
in_progress: 'In Progress',
completed: 'Completed',
},
},
due_date: { label: 'Due Date' },
},
_views: {
all_tasks: {
label: 'All Tasks',
emptyState: { title: 'No tasks yet', message: 'Create your first task' },
},
},
_sections: {
details: { label: 'Details' },
},
_actions: {
complete: {
label: 'Complete',
confirmText: 'Mark this task as completed?',
successMessage: 'Task completed',
},
},
},
},
apps: {
todo_app: { label: 'Todo Manager', description: 'Personal task management' },
},
messages: {
'common.save': 'Save',
'common.cancel': 'Cancel',
'welcome.user': 'Welcome, {{userName}}!',
},
};
// src/translations/zh-CN.ts — same shape, translated values
const zhCN: TranslationData = {
objects: {
task: {
label: '任务',
pluralLabel: '任务',
fields: {
subject: { label: '主题', help: '任务的简要标题' },
status: {
label: '状态',
options: { not_started: '未开始', in_progress: '进行中', completed: '已完成' },
},
due_date: { label: '截止日期' },
},
},
},
apps: {
todo_app: { label: '待办管理', description: '个人任务管理' },
},
messages: {
'common.save': '保存',
'common.cancel': '取消',
'welcome.user': '欢迎,{{userName}}!',
},
};
// src/translations/index.ts — assemble the locales into one bundle…
export const TodoTranslations = defineTranslationBundle({
en,
'zh-CN': zhCN,
});
// objectstack.config.ts — …and register it on the stack
export default defineStack({
i18n: { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'] },
translations: [TodoTranslations],
});
defineTranslationBundle は作成時に .parse() でバンドルを検証します —
単なる : TranslationBundle リテラルよりもこちらを優先してください。
オブジェクトレベルの翻訳構造
単一のオブジェクトの翻訳可能なコンテンツは、次のサブキーの下に
objects.{object_name} としてまとめられます。
| サブキー | 含まれるもの |
|---|---|
label / pluralLabel / description | オブジェクトレベルのテキスト(すべてのキーは任意) |
fields.{field_name} | 各フィールドの label、help、placeholder、options(オプション値 → ラベル) |
_views.{view_name} | label、description、emptyState.title / emptyState.message、bulkActions.{def_name}(label、confirmText、confirmLabel、params) |
_actions.{action_name} | label、description、confirmText、successMessage、params.{param_name}(その下の .options.{value} — そのパラメーターのインライン options にあるオプションの保存済み value)、outcomeMessages.{outcome}(アクション自身の outcomeMessages で宣言された結果)、resultDialog.title / .description / .acknowledge(resultDialog を宣言するアクションの下に限る)、resultDialog.fields.{path}(resultDialog.fields[] 内のフィールドのリテラルな path。ドットも含みます — client.secret は 1 つのキーです)。同じキーは globalActions.{action_name} の下にも存在します |
_sections.{section_name} | フォームセクションの label、description |
_tabs.{tab_name} | フィルタープリセットタブの label(ViewTabSchema.name) |
_validations.{rule_name} | message のみ — 書き込みが拒否されたときに返される文(廃止された validationMessages を置き換えます) |
objects と並ぶトップレベルのグループ:apps(ラベル、説明、ナビゲーション)、
messages、globalActions(objectName のないアクション — バインドされたアクションを示すキーは
エラーとなり、そのメッセージでは代わりに記述すべき objects.{owner}._actions.{action_name} キーが示されます)、
dashboards、datasets、pages、flows、metadataForms、settingsCommon。
settings はプラットフォーム専用です — 設定マニフェストの名前空間をキーとし、プラットフォームだけがこれを宣言するため、これを含むアプリバンドルはキー名を示して拒否されます。
正確な Zod の構造(および追加されている可能性のあるフィールド)については、
node_modules/@objectstack/spec/src/system/translation.zod.ts の
TranslationDataSchema、ObjectTranslationDataSchema、FieldTranslationSchema を参照してください。
命名規則
| コンテキスト | 規則 | 例 |
|---|---|---|
| ロケールコード | BCP-47 | en、en-US、zh-CN、pt-BR |
objects.* のオブジェクトキー | snake_case | objects.project_task、objects.support_case |
| フィールドキー | snake_case | fields.first_name、fields.due_date |
| オプション値 | 小文字 | options.status.in_progress |
| メッセージキー | ドット区切り | common.save、validation.required |
重要: 翻訳バンドル内のオブジェクト名とフィールドキーは、オブジェクトとフィールドのスキーマで定義された
snake_caseの名前と一致する必要があります。
オプションのサブキーには、表示ラベルではなく、オプションに保存される value を指定します。
options: [{ value: 'direct_mail', label: 'Direct Mail' }] の場合は、
options: { direct_mail: '直郵' } と記述します。'Direct Mail' と 'direct-mail' はどちらも
解析・出荷されますが、解決されず、何も表示されません。
os validate / os lint / os compile はこの対応関係をチェックします。translation-target-unknown はエラーとなり、実行に失敗します。translation-option-key-unknown は警告です。存在しないオブジェクト、フィールド、ビュー、タブ、アクション、パラメーター、セクション、検証ルール、アプリ、ナビゲーション項目、ダッシュボード、ウィジェット、フロースクリーンを示すキーは、存在する名前の一覧とともに表示されます。アクションエントリー内の次の3つの形状もエラーになります。アクションの outcomeMessages に宣言されていない結果、アクションが宣言していない resultDialog.fields のパス、そして resultDialog を宣言していないアクションにある resultDialog の title / description / acknowledge です。バンドルで翻訳できるのはアクションが宣言するダイアログであり、ダイアログを追加することはできません。パラメーター内のインラインオプションキーは、フィールドの options と同じオプション警告の対象です。field を参照し、インラインの options を宣言していないパラメーターは判定対象になりません。名前が変更された対象をキーとするバンドルも解析はされます。そのラベルだけがソースロケールで何も通知されずに表示され、隣接するほかのラベルはすべて解決されます。
ファイル構成の戦略
レイアウトは作成時の規約です。どの方式を選んでも、独自のインポートグラフで TranslationBundle を組み立て、スタックに登録します。ディレクトリツリーを自動で読み込むことはありません。
| レイアウト | ファイル | 使用する場面 |
|---|---|---|
| バンドル型 | すべてのロケールを含む1つの crm.translation.ts | オブジェクトが5個未満、ロケールが2~3個の場合 |
| ロケール別(推奨) | src/translations/{en,zh-CN,ja-JP}.ts + index.ts | オブジェクトが5~20個、ロケールが3~5個の場合 |
| 名前空間別 | i18n/{locale}/{object}.json。独自のインポートで組み立てます | オブジェクトが20個以上、ロケールが5個以上の場合 |
ランタイムでの作成:translation アイテム
翻訳はファイルとして同梱する必要はありません。translation メタデータアイテムは、Studio、メタデータ API、またはエージェントを通じて作成でき、上記で説明したものと同じ objects.* グループに、その翻訳対象の locale を加えた、1ロケール分の内容を保持します。locale は必須で、1アイテムにつき1ロケールです。公開済みアイテムは起動時と公開のたびに読み込まれ、ファイルバンドルに重ねて適用されます(アイテムを削除すると、同梱された値に戻ります)。形式は1つだけで、形式間の変換はありません。defineTranslation を使って作成してください。正確な Zod 形状は node_modules/@objectstack/spec/src/system/translation.zod.ts の TranslationItemSchema です。
メッセージの補間
シンプルな形式(デフォルト)
同梱されている両方のアダプター(FileI18nAdapter とインメモリーのフォールバック)は、二重波括弧の {{variable}} プレースホルダーだけを置換します。単一の波括弧は変更されず、そのまま通過します。(スキーマの docstring には {variable} 記法がありますが、ランタイムの実装はそのようにはなっていません。)
{
"messages": {
"welcome": "Welcome, {{userName}}!",
"pagination": "Showing {{start}} to {{end}} of {{total}} items"
}
}
使用例:
i18n.t('messages.welcome', 'en', { userName: 'Alice' });
// "Welcome, Alice!"
ICU MessageFormat には非対応
ICU MessageFormat エンジンはありません。補間は常にシンプルな {{variable}} 置換です(構想段階だった messageFormat 設定項目は削除されました)。シンプルな置換用のメッセージを作成してください。{count, plural, one {1 message} other {# messages}} のような ICU の複数形・選択文字列は評価されません。複数形を使うには、t() を呼び出す前にアプリケーションコードで形式を選択してください。
翻訳のカバレッジ
os i18n check
現在のカバレッジ確認方法は CLI です。
os i18n check # every locale found in the config
os i18n check --locales=zh-CN # scope to specific locales
os i18n check --strict --threshold=95 # CI gate: locale parity + minimum coverage
登録済みバンドルとソースメタデータを比較し、エクストラクターが走査するすべての対象(オブジェクトとそのサブキー、グローバルアクション、アプリ、ダッシュボード、ページ、フロースクリーン)について、ロケールごとの不足キーを報告します。プラットフォームのメタデータフォームは、その翻訳を含むスタックに限りカウントされます(--include-platform を指定すると監査対象になります)。デフォルトロケールの不足はエラーになり、--strict を指定すると残りもエラーに昇格します。--show-keys を指定するとすべての不足キーが一覧表示されます。os lint --i18n-strict を実行すると、このチェックも組み込まれます。
os i18n extract --check — カバレッジではなく最新性
生成されたバンドル(os i18n extract で生成された *.generated.ts)をコミットする場合、チェック項目はカバレッジだけではありません。
os i18n extract <config> --locales=zh-CN,ja-JP --fill=default \
--out=src/translations --check
--check は何も書き込みません。通常の抽出で生成される内容を再生成し、それが --out にコミット済みの内容と異なる場合は失敗します。その際、古いファイルまたは不足しているファイルをそれぞれ示し、再生成コマンドを表示します。
両方のチェックを使ってください。答える問いはそれぞれ異なります。 os i18n check が確認するのは、文字列が翻訳されているか(カバレッジ:人が行う作業)です。extract --check が確認するのは、生成済みバンドルがスキーマの生成結果と一致しているか(最新性:機械が生成する出力)です。ラベルの変更や仕様キーの削除があっても、カバレッジは 100% のままバンドルだけが古くなります。プラットフォーム自体のバンドルでは、スキーマから削除済みのキーが翻訳されていたことがありました。
通常の抽出と同じマージモードで実行されるため、再翻訳は必要ありません。最新のバンドルを再抽出すると、バイト単位で同一になります。比較対象がなければチェックできないため、--out が必要です。
差分とカバレッジのスキーマ
仕様では、ツール向けにカバレッジ結果をモデル化しています。TranslationCoverageResult(合計、coveragePercent、グループごとの breakdown)と TranslationDiffItem(ドットパスの key、missing | redundant | stale のいずれかの status、locale、任意の objectName、古さの検出に使う任意の sourceHash、AI による拡張用のフィールド aiSuggested と aiConfidence)です。完全な Zod の定義は node_modules/@objectstack/spec/src/system/translation.zod.ts にある TranslationCoverageResultSchema と TranslationDiffItemSchema です。
これらのスキーマは、オプションのコントラクトメソッド getCoverage() と suggestTranslations() を支えていますが、提供済みのアダプターには実装がありません。代わりに、カバレッジ確認のワークフローには os i18n check / os lint --i18n-strict を使用してください。
II18nService との統合
サービスコントラクト
II18nService は、バンドルを読み込み、フォールバック付きでキーを解決するカーネルサービス(名前は 'i18n')です。
import type { II18nService } from '@objectstack/spec/contracts';
(コントラクトのソース .ts は公開パッケージに含まれておらず、同梱されるのは src/**/*.zod.ts のみです。そのため、node_modules のソースを参照するのではなく、@objectstack/spec/contracts から型をインポートしてください。)
両方の提供済みアダプター(@objectstack/service-i18n の FileI18nAdapter と、i18n プラグインがない場合に @objectstack/core が登録するインメモリのフォールバック)が実装するメソッドは次のとおりです。
t(key, locale, params?)—{{param}}の補間とフォールバックロケールの検索を伴う、ドットパスによる解決(例:objects.account.label)getTranslations(locale)— 指定ロケールの全スナップショットloadTranslations(locale, data)— プログラムからの読み込み。ディープマージを行うため、複数のプラグインがそれぞれ独自のobjects.*スライスを提供できますgetLocales()/setSupportedLocales()/getDefaultLocale()/setDefaultLocale()
インメモリのフォールバックでは、ロケールコードも次の順で解決します(完全一致 → 大文字と小文字を区別しない一致 → ベース言語 zh-CN → zh → バリエーション zh → zh-CN)。
コントラクトではさらに、オプションのメソッド getFieldLabels、getCoverage、suggestTranslations も宣言されていますが、提供済みの実装にはありません。これらは、カスタムのワークベンチまたは TMS アダプター向けの拡張ポイントとして扱ってください。(getAppBundle / loadAppBundle は、それらが返していた o.* 形式とともに廃止されました。)
登録
このプラグインを自分で登録する必要はありません。スタック設定に
translations または i18n が含まれている場合、os serve が
I18nServicePlugin を登録します(カーネルのブートストラップについては
objectstack-platform → Runtime Boot Sequence を参照してください)。このプラグインは
GET /api/v1/i18n/locales、/translations/:locale、/labels/:object/:locale を
自動的に登録します。
翻訳ワークフローのベストプラクティス
1. メタデータからスケルトンを抽出する
スタック設定から、編集可能な翻訳ファイルのひな形を生成します。
os i18n extract --locales=zh-CN --out=./src/translations
これにより、<locale>.objects.generated.ts という TypeScript モジュール(JSON ではありません)と、
--no-metadata-forms を指定しない場合は <locale>.metadata-forms.generated.ts という
関連ファイルが生成されます。デフォルトロケールにはスキーマのラベルが入力され、その他のロケールには
--fill(empty | default | todo)の設定が適用されます。その他のオプションは
os i18n extract --help で確認できます。--filter、
--default-locale、--no-merge、--objects-only、--source-hashes、--dry-run、
--json などがあります。
⚠️ デフォルトで --objects-only が有効になっています。そのため、extract が書き出すのは objects /
globalActions サブツリーのみですが、os i18n check では app、navigation、
dashboard、widget、page、flow のキーも必要です。記載どおりにワークフローを実行すると、
extract でひな形が生成されなかった箇所がカバレッジレポートに表示されます。これらのグループを含めるには
--no-objects-only を指定するか、手動で作成してください。
2. 翻訳する
値を手動で入力します。
3. カバレッジを確認する
os i18n check --locales=zh-CN
ロケールの不足がある場合に CI で失敗させるには、--strict / --threshold=95 を追加します。
4. 登録する
スタックでは defineStack({ translations: [...] }) を使ってバンドルを登録します。独自の生成済みバンドルを
含むパッケージやプラグイン(このリポジトリで最も一般的な形式)には、もう 1 つ手順が必要です。生成された
モジュールがエクスポートするのは TranslationData ではなく、objects のサブツリー
(NonNullable<TranslationData['objects']>)であるため、バンドルとして使用する前にラップする必要があります。
// src/translations/index.ts
import { defineTranslationBundle } from '@objectstack/spec';
import type { TranslationData } from '@objectstack/spec/system';
import { withSourceFallback } from '@objectstack/platform-objects/apps';
import { enObjects } from './en.objects.generated.js';
import { zhCNObjects } from './zh-CN.objects.generated.js';
import { zhCNGeneratedSourceHashes } from './zh-CN.source-hashes.generated.js';
const enSource: TranslationData = { objects: enObjects };
export const StorageTranslations = defineTranslationBundle({
en: enSource,
// 4th argument = the `--source-hashes` companion. Without it a leaf whose SOURCE has
// moved goes on serving the superseded fill under a green `os i18n check` forever.
// The 3rd stays undefined: it judges hand-authored `apps` / `dashboards` / `pages`,
// which a fully generated set does not have.
'zh-CN': withSourceFallback({ objects: zhCNObjects }, enSource, undefined, zhCNGeneratedSourceHashes),
});
独自のオブジェクトを管理するプラグインは、スタックに登録するのではなく、自身の kernel:ready フックから
そのバンドルを読み込みます。i18n.loadTranslations(locale, data) はディープマージするため、
各プラグインがそれぞれの objects.* スライスを追加できます。
同梱されている例
examples/app-crm — バンドル形式(crm.translation.ts 1 ファイルに en と zh-CN を格納)。
examples/app-todo — ロケール別形式({en,zh-CN,ja-JP}.ts と index.ts)。
よくある落とし穴
❌ 廃止された o.* 形式
すべて objects.* を読み取ります。o.* 形式は削除されました。「Studio
形式」でも、別形式でもなく、完全になくなっています。どちらの方法でも拒否され、ファイルも同様です。
// WRONG — in a file bundle AND in a `translation` item
{ o: { account: { label: '客户' } } }
// CORRECT (TranslationData)
{ objects: { account: { label: '客户' } } }
関連するキーにも同じルールが適用されます。app → apps、nav →
apps.<app>.navigation.<id>.label、dashboard → dashboards、
_globalOptions → objects.<obj>.fields.<field>.options、_meta.locale →
トップレベルの locale、_actions.confirmMessage → _actions.confirmText。
❌ カバレッジレポートを無視する
リリース前に os i18n check を実行してください。古くなった内容を検出するのは extract --check です。
作業を確認する
os i18n check # coverage: which keys are missing, per locale
os validate # the bundle conforms to the protocol schema
os lint --i18n-strict を指定すると、カバレッジの不足が重大なエラーになります。ビルドとひな形生成の
ゲートについては、objectstack-platform → 作業を確認するを参照してください。
参考資料
Zod スキーマの一覧(各項目の簡単な説明付き)は
references/_index.md を参照してください。各項目から
node_modules/@objectstack/spec/src/ 内の該当箇所に移動できます。フィールドの正確な
形式を確認するには、必ずソースを Read してください。プロパティ名の記憶に頼らないでください。
関連項目
- objectstack-data — オブジェクトとフィールドのメタデータ構造を理解する
- objectstack-ui — ビュー、アプリ、アクションの翻訳
- objectstack-automation — ワークフローとフローのメッセージ翻訳