objectstack-i18n
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseInternationalization — ObjectStack I18n Protocol
国际化——ObjectStack I18n协议
Expert instructions for designing internationalization (i18n) and localization (l10n)
strategies using the ObjectStack specification. This skill covers translation bundle
structures, locale configuration, object-first translation patterns, coverage detection,
and integration with the I18nService.
使用ObjectStack规范设计国际化(i18n)和本地化(l10n)策略的专业指南。本指南涵盖翻译包结构、区域配置、对象优先翻译模式、覆盖率检测以及与I18nService的集成。
When to Use This Skill
何时使用本技能
- You are configuring i18n for a new ObjectStack project.
- You need to create translation bundles for multiple locales.
- You are designing object-first translation structures (per-object translation files).
- You need to detect missing translations (coverage analysis).
os i18n check - You are extending the service contract with AI translation suggestions (TMS / machine-translation integrations).
- You are implementing locale-specific formatting (dates, numbers, currency).
Related: workspace regional defaults (,
timezone,locale) live in the tenant-scopedcurrencysettings, are resolved onto each request'slocalization, and are exposed atExecutionContext; a currency field falls back toGET /api/v1/auth/me/localizationwhen it omits its own (ADR-0053).localization.currency - You need to understand translation file organization strategies (bundled, per_locale, per_namespace).
- 你正在为新的ObjectStack项目配置i18n。
- 你需要为多区域创建翻译包。
- 你正在设计对象优先的翻译结构(每个对象对应一个翻译文件)。
- 你需要检测缺失的翻译(覆盖率分析)。
os i18n check - 你正在通过AI翻译建议扩展服务协议(翻译管理系统TMS/机器翻译集成)。
- 你正在实现特定区域的格式(日期、数字、货币)。
相关:工作区区域默认设置(、
timezone、locale)存储在租户范围的currency配置中,会解析到每个请求的localization,并通过ExecutionContext暴露;当货币字段未指定自身值时,会回退到GET /api/v1/auth/me/localization(ADR-0053)。localization.currency - 你需要了解翻译文件组织策略(捆绑式、按区域划分、按命名空间划分)。
Core Concepts
核心概念
Translation Architecture Overview
翻译架构概述
-
Runtime format —(
objects.*): each locale is authored as oneTranslationDatavalue. All translatable content for an object (label, fields, options, views, sections, actions) is grouped underTranslationData, with global groups (objects.{object_name},apps,messages,globalActions,dashboards,settings) at the top level.metadataForms -
Bundle registration: per-locale files are assembled withinto a
defineTranslationBundle({ en, 'zh-CN': … })(locale code →TranslationBundle) and registered viaTranslationData. This is the format the runtime resolvers,defineStack({ translations: [...] }),os i18n extract, and the example apps all use.os i18n check -
Coverage detection:compares registered bundles against source metadata to report missing keys per locale.
os i18n check -
Runtime authoring —: a
TranslationItemmetadata item authored in the Studio / metadata API carries the sametranslationgroups plus theobjects.*it translates. There is only one shape; see "Authoring at Runtime" below.locale
-
运行时格式——(
objects.*):每个区域对应一个TranslationData值。对象的所有可翻译内容(标签、字段、选项、视图、章节、操作)都归类在TranslationData下,全局分组(objects.{object_name}、apps、messages、globalActions、dashboards、settings)位于顶层。metadataForms -
包注册:按区域划分的文件通过组装成
defineTranslationBundle({ en, 'zh-CN': … })(区域代码→TranslationBundle),并通过TranslationData注册。这是运行时解析器、defineStack({ translations: [...] })、os i18n extract以及示例应用所使用的格式。os i18n check -
覆盖率检测:将已注册的包与源元数据进行比较,报告每个区域缺失的键。
os i18n check -
运行时编写——:在Studio/元数据API中编写的
TranslationItem元数据项包含与上述相同的translation分组,再加上它所对应的objects.*。格式唯一;详见下方“运行时编写”部分。locale
Translation Configuration
翻译配置
Stack-Level I18n Config
栈级I18n配置
Configure i18n settings in your :
<!-- os:check -->
objectstack.config.tstypescript
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)
});| Property | Type | Required / Default | Description |
|---|---|---|---|
| | required | Default BCP-47 locale code |
| | required | All supported locales |
| | optional | Fallback when translation missing |
BCP-47 Locale Codes: Use standard locale tags (e.g.,,en-US,zh-CN,pt-BR).en-GB
在中配置i18n设置:
<!-- os:check -->
objectstack.config.tstypescript
import { defineStack } from '@objectstack/spec';
export default defineStack({
i18n: {
defaultLocale: 'en',
supportedLocales: ['en', 'zh-CN', 'ja-JP', 'es-ES'],
fallbackLocale: 'en',
},
// translations: [MyTranslations], ← 在此处注册你的包(见下文)
});| 属性 | 类型 | 必填/默认值 | 描述 |
|---|---|---|---|
| | 必填 | 默认BCP-47区域代码 |
| | 必填 | 所有支持的区域 |
| | 可选 | 翻译缺失时的回退区域 |
BCP-47区域代码:使用标准区域标签(例如、en-US、zh-CN、pt-BR)。en-GB
File Organization Strategies
文件组织策略
1. Bundled (Single File)
1. 捆绑式(单文件)
All locales in one file. Best for small projects with few objects.
src/translations/
crm.translation.ts # { en: {...}, "zh-CN": {...} }When to use: Fewer than 5 objects, 2-3 locales, < 200 translation keys total.
所有区域放在一个文件中。最适合对象较少的小型项目。
src/translations/
crm.translation.ts # { en: {...}, "zh-CN": {...} }适用场景:少于5个对象,2-3个区域,翻译键总数少于200个。
2. Per-Locale (Recommended)
2. 按区域划分(推荐)
One file per locale containing all namespaces. Recommended when a single locale file stays under ~500 lines.
src/translations/
en.ts # TranslationData for English
zh-CN.ts # TranslationData for Chinese
ja-JP.ts # TranslationData for JapaneseWhen to use: Medium projects (5-20 objects), 3-5 locales, organized by language.
每个区域对应一个包含所有命名空间的文件。当单个区域文件行数少于约500行时推荐使用。
src/translations/
en.ts # 英文的TranslationData
zh-CN.ts # 中文的TranslationData
ja-JP.ts # 日文的TranslationData适用场景:中型项目(5-20个对象),3-5个区域,按语言组织。
3. Per-Namespace (Enterprise)
3. 按命名空间划分(企业级)
One file per namespace (object) per locale. Aligns with Salesforce DX and ServiceNow conventions.
i18n/
en/
account.json # ObjectTranslationData
contact.json
common.json # messages + app labels
zh-CN/
account.json
contact.json
common.jsonWhen to use: Large projects (20+ objects), 5+ locales, team collaboration, CI/CD pipelines.
These are authoring conventions: your import graph assembles whichever layout you choose into thevalues you register on the stack.TranslationBundle'sFileI18nAdapterloads only flat top-levellocalesDirfiles (subdirectories are skipped) — a per-namespace tree must be assembled by your own imports or build step.{locale}.json
每个命名空间(对象)每个区域对应一个文件。与Salesforce DX和ServiceNow的约定一致。
i18n/
en/
account.json # ObjectTranslationData
contact.json
common.json # messages + 应用标签
zh-CN/
account.json
contact.json
common.json适用场景:大型项目(20个以上对象),5个以上区域,团队协作,CI/CD流水线。
这些是编写约定:你的导入图会将你选择的任何布局组装成注册到栈中的值。TranslationBundle的FileI18nAdapter仅加载顶层的扁平localesDir文件(子目录会被跳过)——按命名空间划分的目录结构必须通过你自己的导入或构建步骤来组装。{locale}.json
Authoring Translation Bundles (objects.*
)
objects.*编写翻译包(objects.*
)
objects.*The canonical authoring path: one per locale, assembled with
and registered on the stack. This mirrors the shipped
example apps ( + ):
<!-- os:check -->
TranslationDatadefineTranslationBundlesrc/translations/{en,zh-CN}.tsindex.tstypescript
// 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标准编写流程:每个区域对应一个,通过组装并注册到栈中。这与示例应用的实现方式一致( + ):
<!-- os:check -->
TranslationDatadefineTranslationBundlesrc/translations/{en,zh-CN}.tsindex.tstypescript
// src/translations/en.ts — 每个区域对应一个TranslationData
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 — 相同结构,翻译后的值
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 — 将多个区域组装成一个包…
export const TodoTranslations = defineTranslationBundle({
en,
'zh-CN': zhCN,
});
// objectstack.config.ts — …并注册到栈中
export default defineStack({
i18n: { defaultLocale: 'en', supportedLocales: ['en', 'zh-CN'] },
translations: [TodoTranslations],
});defineTranslationBundle.parse(): TranslationBundleObject-Level Translation Structure
对象级翻译结构
All translatable content for a single object is aggregated under
with these sub-keys:
objects.{object_name}| Sub-key | Holds |
|---|---|
| Object-level text ( |
| |
| |
| |
| Form section / tab |
Top-level groups alongside : (label, description, navigation),
, (object-less actions), , ,
, .
objectsappsmessagesglobalActionsdashboardssettingsmetadataFormssettingsCommonValidation messages are not a translation group.was removed in spec 17.0.0 (#4667) — nothing ever read it, so a translated rule message was stored and never shown. Author the message on the rule itself (validationMessages), which the engine returns on every rejected write.object.validations[].message
For the exact Zod shape (and any field that may have been added since), read
—
, , and .
node_modules/@objectstack/spec/src/system/translation.zod.tsTranslationDataSchemaObjectTranslationDataSchemaFieldTranslationSchema单个对象的所有可翻译内容都聚合在下,包含以下子键:
objects.{object_name}| 子键 | 包含内容 |
|---|---|
| 对象级文本( |
| 每个字段的 |
| |
| |
| 表单章节/标签页的 |
与同级的顶层分组:(标签、描述、导航)、、(无对象关联的操作)、、、、。
objectsappsmessagesglobalActionsdashboardssettingsmetadataFormssettingsCommon验证消息不属于翻译分组。在规范17.0.0中被移除(#4667)——从未有组件读取它,因此翻译后的规则消息会被存储但不会显示。请直接在规则本身中编写消息(validationMessages),引擎会在每次写入被拒绝时返回该消息。object.validations[].message
如需了解精确的Zod结构(以及可能新增的字段),请查看中的、和。
node_modules/@objectstack/spec/src/system/translation.zod.tsTranslationDataSchemaObjectTranslationDataSchemaFieldTranslationSchemaNaming Conventions
命名约定
| Context | Convention | Example |
|---|---|---|
| Locale codes | BCP-47 | |
Object keys in | | |
| Field keys | | |
| Option values | lowercase | |
| Message keys | dot-separated | |
Critical: Object names and field keys in translation bundles must match thenames defined in your Object and Field schemas.snake_case
Option sub-keys are the option's stored , never its display label:
for write
— both and
parse, ship, and resolve to nothing.
valueoptions: [{ value: 'direct_mail', label: 'Direct Mail' }]options: { direct_mail: '直邮' }'Direct Mail''direct-mail'os validateos lintos compiletranslation-target-unknowntranslation-option-key-unknown| 上下文 | 约定 | 示例 |
|---|---|---|
| 区域代码 | BCP-47 | |
| | |
| 字段键 | | |
| 选项值 | 小写 | |
| 消息键 | 点分隔 | |
关键注意事项:翻译包中的对象名称和字段键必须与对象和字段 schema 中定义的名称完全匹配。snake_case
选项子键是选项存储的****,绝不是其显示标签:对于,应编写——和都会被解析、打包,但无法匹配到任何内容。
valueoptions: [{ value: 'direct_mail', label: 'Direct Mail' }]options: { direct_mail: '直邮' }'Direct Mail''direct-mail'os validateos lintos compiletranslation-target-unknowntranslation-option-key-unknownAuthoring at Runtime: the translation
Item
translation运行时编写:translation
项
translationTranslations do not have to ship as files. A metadata item —
created in the Studio, through the metadata API, or by an agent — is one
locale's worth of the same groups documented above, plus the
it translates. There is exactly one shape; nothing converts between
formats.
<!-- os:check -->
translationobjects.*localetypescript
import { defineTranslation } from '@objectstack/spec/system';
export default defineTranslation({
locale: 'zh-CN',
objects: {
account: {
label: '客户',
pluralLabel: '客户',
fields: {
name: { label: '客户名称', help: '公司或组织的法定名称' },
industry: { label: '行业', options: { tech: '科技', finance: '金融' } },
status: { options: { active: '活跃', inactive: '停用' } },
},
_views: { all_accounts: { label: '全部客户' } },
_sections: { basic_info: { label: '基本信息' } },
_actions: {
merge: { label: '合并客户', confirmText: '此操作无法撤销,确认合并?' },
},
},
},
apps: { crm: { label: '客户关系管理', navigation: { home: { label: '首页' } } } },
messages: { 'common.save': '保存' },
});Rules that differ from a file bundle:
- is required. A file bundle names its locales as map keys; an item carries its own. An item whose locale cannot be resolved is skipped by the runtime sync — a silent skip, which is why the field is mandatory rather than inferred from the item name.
locale - One locale per item. Author and
zh-CNas two items.ja-JP - Published items are loaded at boot and on every publish (no restart), and layer over the file bundles — an authored value wins over a shipped one for the same key; deleting the item restores the shipped value.
Exact Zod shape: —
.
node_modules/@objectstack/spec/src/system/translation.zod.tsTranslationItemSchema翻译内容不一定必须以文件形式发布。元数据项——在Studio中创建、通过元数据API创建或由Agent创建——包含上述分组的单个区域内容,再加上它所对应的。格式唯一;无需在不同格式间转换。
<!-- os:check -->
translationobjects.*localetypescript
import { defineTranslation } from '@objectstack/spec/system';
export default defineTranslation({
locale: 'zh-CN',
objects: {
account: {
label: '客户',
pluralLabel: '客户',
fields: {
name: { label: '客户名称', help: '公司或组织的法定名称' },
industry: { label: '行业', options: { tech: '科技', finance: '金融' } },
status: { options: { active: '活跃', inactive: '停用' } },
},
_views: { all_accounts: { label: '全部客户' } },
_sections: { basic_info: { label: '基本信息' } },
_actions: {
merge: { label: '合并客户', confirmText: '此操作无法撤销,确认合并?' },
},
},
},
apps: { crm: { label: '客户关系管理', navigation: { home: { label: '首页' } } } },
messages: { 'common.save': '保存' },
});与文件包不同的规则:
- 为必填项。文件包通过映射键命名区域;而元数据项自身携带区域信息。如果区域无法解析,运行时同步会跳过该元数据项——静默跳过,因此该字段为必填项,而非从项名称推断。
locale - 每个项对应一个区域。和
zh-CN需分别编写为两个项。ja-JP - 已发布的项会在启动时和每次发布后加载(无需重启),并覆盖文件包——对于相同的键,编写的值会优先于打包的值;删除项会恢复打包的值。
精确的Zod结构:中的。
node_modules/@objectstack/spec/src/system/translation.zod.tsTranslationItemSchemaRetired: the o.*
dialect
o.*已废弃:o.*
方言
o.*A second object-first shape keyed on (with , ,
, , , , , ,
, and ) was once documented for
Studio-authored translations. No resolver ever read it, so items authored
that way saved successfully and rendered nothing. It was removed in #3778 —
those keys are now rejected at save time with a message naming the group to
use instead. Never author them, in files or at runtime.
o.{object_name}appnavdashboardreportsnotificationserrors_globalOptions_metanamespace_actions.confirmMessage曾有一种以为键的对象优先结构(包含、、、、、、、、和),用于Studio编写的翻译。从未有解析器读取过该结构,因此以该方式编写的项会成功保存但无法显示。该结构已在#3778中移除——现在保存时会拒绝这些键,并提示应使用的分组。请勿在文件或运行时编写该结构。
o.{object_name}appnavdashboardreportsnotificationserrors_globalOptions_metanamespace_actions.confirmMessageMessage Interpolation
消息插值
Simple Format (Default)
简单格式(默认)
Both shipped adapters ( and the in-memory fallback) substitute
double-brace placeholders only — single braces pass through
unchanged. (The schema docstring mentions notation, but that is not
what the runtime implements.)
FileI18nAdapter{{variable}}{variable}json
{
"messages": {
"welcome": "Welcome, {{userName}}!",
"pagination": "Showing {{start}} to {{end}} of {{total}} items"
}
}Usage:
typescript
i18n.t('messages.welcome', 'en', { userName: 'Alice' });
// "Welcome, Alice!"两个已发布的适配器(和内存回退适配器)仅替换双大括号占位符——单大括号会原样保留。(schema文档中提到了表示法,但运行时并未实现。)
FileI18nAdapter{{variable}}{variable}json
{
"messages": {
"welcome": "Welcome, {{userName}}!",
"pagination": "Showing {{start}} to {{end}} of {{total}} items"
}
}用法:
typescript
i18n.t('messages.welcome', 'en', { userName: 'Alice' });
// "Welcome, Alice!"No ICU MessageFormat
不支持ICU MessageFormat
There is no ICU MessageFormat engine — interpolation is always simple
substitution (the aspirational config knob was
removed in #3494). Author messages for simple substitution; ICU plural/select
strings like will not be
evaluated. To pluralize, select the form in application code before calling .
{{variable}}messageFormat{count, plural, one {1 message} other {# messages}}t()没有ICU MessageFormat引擎——插值始终为简单的替换(预期的配置项已在#3494中移除)。请编写适合简单替换的消息;ICU复数/选择字符串如不会被解析。如需复数形式,请在调用前在应用代码中选择对应的形式。
{{variable}}messageFormat{count, plural, one {1 message} other {# messages}}t()Translation Coverage
翻译覆盖率
os i18n check
os i18n checkos i18n check
os i18n checkThe working coverage path is the CLI:
bash
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 coverageIt compares registered bundles against source metadata and reports missing
object/field/option/view/action keys per locale. Missing keys in the default
locale are errors; promotes non-default gaps to errors and
lists every missing key. folds the same
gate into linting.
--strict--show-keysos lint --i18n-strict主要的覆盖率检查方式是CLI:
bash
os i18n check # 检查配置中的所有区域
os i18n check --locales=zh-CN # 仅检查指定区域
os i18n check --strict --threshold=95 # CI校验:区域一致性 + 最低覆盖率要求它会将已注册的包与源元数据进行比较,报告每个区域缺失的对象/字段/选项/视图/操作键。默认区域中缺失的键会被视为错误;会将非默认区域的缺失提升为错误,会列出所有缺失的键。会将相同的校验整合到代码检查中。
--strict--show-keysos lint --i18n-strictos i18n extract --check
— freshness, not coverage
os i18n extract --checkos i18n extract --check
— 检查新鲜度,而非覆盖率
os i18n extract --checkIf you commit generated bundles ( produced by
), coverage is only half the gate:
*.generated.tsos i18n extractbash
os i18n extract <config> --locales=zh-CN,ja-JP --fill=default \
--out=src/translations --check--check--outUse both gates — they answer different questions. asks are
the strings translated? (coverage: human work). asks are the
generated bundles still what the schema produces? (freshness: machine output).
Renaming a label, adding an object, or removing a spec key leaves coverage at
100% while the bundles quietly go stale — which is exactly how the platform's
own bundles ended up carrying translations for keys the schema had already
deleted, plus fields with no entry in any locale.
os i18n checkextract --checkIt runs in the same merge mode as a normal extract, so it never asks for
re-translation: an up-to-date bundle re-extracts byte-identically. Requires
— there is nothing to compare against without it.
--out如果你提交了生成的包(由生成的),覆盖率只是校验的一部分:
os i18n extract*.generated.tsbash
os i18n extract <config> --locales=zh-CN,ja-JP --fill=default \
--out=src/translations --check--check--out同时使用两种校验——它们解决不同的问题。检查字符串是否已翻译?(覆盖率:人工工作)。检查生成的包是否仍与schema一致?(新鲜度:机器输出)。重命名标签、添加对象或移除规范键会使覆盖率保持100%,但包会悄悄过期——这正是平台自身的包最终包含schema已删除的键的翻译,以及某些字段在所有区域都没有条目的原因。
os i18n checkextract --check它以与正常提取相同的合并模式运行,因此不会要求重新翻译:最新的包重新提取后会与原文件完全一致。需要指定——没有输出目录则无法进行比较。
--outDiff & Coverage Schemas
差异与覆盖率Schema
The spec models coverage results for tooling:
(totals, , per-group ) and —
(dot path), (), , optional
for stale detection, and AI-enrichment fields (,
). Full Zod shape:
—
, .
TranslationCoverageResultcoveragePercentbreakdownTranslationDiffItemkeystatusmissing | redundant | stalelocalesourceHashaiSuggestedaiConfidencenode_modules/@objectstack/spec/src/system/translation.zod.tsTranslationCoverageResultSchemaTranslationDiffItemSchemaThese schemas back the optional contract methods and
, which no shipped adapter implements — point coverage
workflows at / instead.
getCoverage()suggestTranslations()os i18n checkos lint --i18n-strict规范为工具定义了覆盖率结果模型:(总计、、按分组的)和——(点路径)、()、、用于过期检测的可选,以及AI增强字段(、)。完整的Zod结构:
中的、。
TranslationCoverageResultcoveragePercentbreakdownTranslationDiffItemkeystatusmissing | redundant | stalelocalesourceHashaiSuggestedaiConfidencenode_modules/@objectstack/spec/src/system/translation.zod.tsTranslationCoverageResultSchemaTranslationDiffItemSchema这些schema支持可选的合约方法和,但没有已发布的适配器实现这些方法——请将覆盖率工作流指向 / 。
getCoverage()suggestTranslations()os i18n checkos lint --i18n-strictAI-Powered Translation Suggestions
AI驱动的翻译建议
II18nService.suggestTranslations(locale, items)aiSuggestedaiConfidence- Translation Management Systems (TMS) like Phrase, Crowdin, Lokalise
- Machine translation APIs (Google Translate, DeepL)
- Internal translation memory databases
Best Practice: Review and approve machine suggestions before committing them.
II18nService.suggestTranslations(locale, items)aiSuggestedaiConfidence- 翻译管理系统(TMS)如Phrase、Crowdin、Lokalise
- 机器翻译API(Google Translate、DeepL)
- 内部翻译记忆数据库
最佳实践:在提交前审核并批准机器生成的建议。
Integration with II18nService
与II18nService集成
Service Contract
服务合约
II18nService'i18n'typescript
import type { II18nService } from '@objectstack/spec/contracts';(The contract's source is not part of the published package — only
ships — so import the type from
rather than reading source.)
.tssrc/**/*.zod.ts@objectstack/spec/contractsnode_modulesMethods implemented by both shipped adapters ( from
, and the in-memory fallback
registers when no i18n plugin is present):
FileI18nAdapter@objectstack/service-i18n@objectstack/core- — dot-path resolution (e.g.
t(key, locale, params?)) withobjects.account.labelinterpolation and fallback-locale lookup{{param}} - — full snapshot for a locale
getTranslations(locale) - — programmatic load; deep-merges, so multiple plugins can each contribute their own
loadTranslations(locale, data)sliceobjects.* - /
getLocales()/getDefaultLocale()setDefaultLocale()
The in-memory fallback additionally resolves locale codes
(exact → case-insensitive → base language → → variant → ).
zh-CNzhzhzh-CNThe contract also declares optional methods — ,
— that no shipped implementation provides. Treat them
as extension points for a custom workbench or TMS adapter. ( /
were removed in #3778 along with the shape they returned.)
getCoveragesuggestTranslationsgetAppBundleloadAppBundleo.*II18nService'i18n'typescript
import type { II18nService } from '@objectstack/spec/contracts';(合约的源文件不属于发布包的一部分——仅发布——因此请从导入类型,而非读取源文件。)
.tssrc/**/*.zod.ts@objectstack/spec/contractsnode_modules两个已发布的适配器(来自的,以及当没有i18n插件时注册的内存回退适配器)均实现以下方法:
@objectstack/service-i18nFileI18nAdapter@objectstack/core- — 点路径解析(例如
t(key, locale, params?)),支持objects.account.label插值和回退区域查找{{param}} - — 获取某个区域的完整快照
getTranslations(locale) - — 程序化加载;深度合并,因此多个插件可各自贡献自己的
loadTranslations(locale, data)片段objects.* - /
getLocales()/getDefaultLocale()setDefaultLocale()
内存回退适配器还支持区域代码解析(精确匹配→不区分大小写匹配→基础语言→→变体→)。
zh-CNzhzhzh-CN合约还声明了可选方法——、——没有已发布的实现提供这些方法。将它们视为自定义工作台或TMS适配器的扩展点。( / 已随结构一起在#3778中移除。)
getCoveragesuggestTranslationsgetAppBundleloadAppBundleo.*Plugin Setup
插件设置
typescript
import { ObjectKernel } from '@objectstack/core';
import { I18nServicePlugin } from '@objectstack/service-i18n';
const kernel = new ObjectKernel();
kernel.use(new I18nServicePlugin({
defaultLocale: 'en',
localesDir: './i18n',
fallbackLocale: 'en',
registerRoutes: true, // Auto-register REST endpoints
basePath: '/api/v1/i18n',
}));
await kernel.bootstrap();
const i18n = kernel.getService<II18nService>('i18n');loads only flat, top-levellocalesDirfiles from the directory (subdirectories are skipped).{locale}.json(the default) self-registersregisterRoutes: true,GET {basePath}/locales, and/translations/:localeonce an HTTP server is available./labels/:object/:locale
typescript
import { ObjectKernel } from '@objectstack/core';
import { I18nServicePlugin } from '@objectstack/service-i18n';
const kernel = new ObjectKernel();
kernel.use(new I18nServicePlugin({
defaultLocale: 'en',
localesDir: './i18n',
fallbackLocale: 'en',
registerRoutes: true, // 自动注册REST端点
basePath: '/api/v1/i18n',
}));
await kernel.bootstrap();
const i18n = kernel.getService<II18nService>('i18n');仅从目录中加载顶层的扁平localesDir文件(子目录会被跳过)。{locale}.json(默认值)会在HTTP服务器可用时自动注册registerRoutes: true、GET {basePath}/locales和/translations/:locale端点。/labels/:object/:locale
Translation Workflow Best Practices
翻译工作流最佳实践
1. Extract Skeletons from Metadata
1. 从元数据提取骨架
Scaffold ready-to-edit translation files from your stack config:
bash
os i18n extract --locales=zh-CN --out=./src/translationsThis writes TypeScript modules (not JSON) — the
default locale is filled from schema labels, other locales follow
(). Other flags: , (regex
over object/app names or key paths), , .
<locale>.objects.generated.ts--fillempty | default | todo--default-locale--filter--dry-run--json从栈配置中生成可编辑的翻译文件骨架:
bash
os i18n extract --locales=zh-CN --out=./src/translations这会写入 TypeScript模块(而非JSON)——默认区域会填充schema中的标签,其他区域遵循选项()。其他标志:、(对象/应用名称或键路径的正则表达式)、、。
<locale>.objects.generated.ts--fillempty | default | todo--default-locale--filter--dry-run--json2. Translate
2. 翻译
Fill in the values manually. (AI suggestion is a contract-only concept —
has no CLI and no shipped implementation.)
suggestTranslations()手动填充翻译值。(AI建议仅为合约概念——没有CLI也没有已发布的实现。)
suggestTranslations()3. Verify Coverage
3. 验证覆盖率
bash
os i18n check --locales=zh-CNAdd / in CI to fail on locale gaps.
--strict--threshold=95bash
os i18n check --locales=zh-CN在CI中添加 / ,当区域存在缺失时触发失败。
--strict--threshold=954. Commit & Register
4. 提交并注册
Commit the translation files, import them into your bundle, and register it via
.
defineStack({ translations: [...] })提交翻译文件,将它们导入到包中,并通过注册。
defineStack({ translations: [...] })CRM I18n Blueprint
CRM I18n蓝图
Reference implementation shape:
- Bundle entry: (or
src/translations/index.ts)crm.translation.ts - Locale files:
src/translations/{en,zh-CN,ja-JP,es-ES}.ts
Use this structure for metadata apps:
| Layer | CRM Pattern |
|---|---|
| Stack config | |
| Translation assembly | One |
| Locale content | Object-scoped translations ( |
| Naming integrity | Translation object/field keys exactly match metadata machine names |
For new locales, copy one locale file as a baseline, then run
before release.
os i18n check参考实现结构:
- 包入口:(或
src/translations/index.ts)crm.translation.ts - 区域文件:
src/translations/{en,zh-CN,ja-JP,es-ES}.ts
元数据应用使用以下结构:
| 层级 | CRM模式 |
|---|---|
| 栈配置 | |
| 翻译组装 | 一个 |
| 区域内容 | 对象范围的翻译( |
| 命名一致性 | 翻译的对象/字段键与元数据的机器名称完全匹配 |
对于新区域,复制一个现有区域文件作为基线,然后在发布前运行。
os i18n checkCommon Pitfalls
常见陷阱
❌ The Retired o.*
Shape
o.*❌ 已废弃的o.*
结构
o.*Everything reads . The dialect was removed in #3778 — it is
not a "Studio format", not a secondary format, just gone. Files registered in
that shape resolve to nothing; runtime items in that shape are rejected at
save time.
objects.*o.*typescript
// WRONG — in a file bundle AND in a `translation` item
{ o: { account: { label: '客户' } } }
// CORRECT (TranslationData)
{ objects: { account: { label: '客户' } } }Same rule for its sibling keys: → , →
, → ,
→ , →
top-level , and → .
appappsnavapps.<app>.navigation.<id>.labeldashboarddashboards_globalOptionsobjects.<obj>.fields.<field>.options_meta.localelocale_actions.confirmMessage_actions.confirmText所有组件都读取。方言已在#3778中移除——它不是“Studio格式”,也不是次要格式,已完全删除。以该结构注册的文件无法解析;运行时以该结构编写的项会在保存时被拒绝。
objects.*o.*typescript
// 错误——在文件包和`translation`项中均不允许
{ o: { account: { label: '客户' } } }
// 正确(TranslationData)
{ objects: { account: { label: '客户' } } }其兄弟键的替换规则:→,→,→,→,→顶层,→。
appappsnavapps.<app>.navigation.<id>.labeldashboarddashboards_globalOptionsobjects.<obj>.fields.<field>.options_meta.localelocale_actions.confirmMessage_actions.confirmText❌ Mismatched Object Names
❌ 对象名称不匹配
Translation keys must match metadata exactly:
typescript
// Metadata
{ name: 'project_task' }
// Translation (WRONG)
{ objects: { projectTask: { label: '项目任务' } } }
// Translation (CORRECT)
{ objects: { project_task: { label: '项目任务' } } }翻译键必须与元数据完全匹配:
typescript
// 元数据
{ name: 'project_task' }
// 翻译(错误)
{ objects: { projectTask: { label: '项目任务' } } }
// 翻译(正确)
{ objects: { project_task: { label: '项目任务' } } }❌ Hardcoded Option Values
❌ 硬编码选项值
Always use lowercase machine values for options:
typescript
// Metadata
options: [
{ value: 'in_progress', label: 'In Progress' },
]
// Translation (WRONG)
options: { 'In Progress': '进行中' }
// Translation (CORRECT)
options: { in_progress: '进行中' }始终使用小写的机器值作为选项键:
typescript
// 元数据
options: [
{ value: 'in_progress', label: 'In Progress' },
]
// 翻译(错误)
options: { 'In Progress': '进行中' }
// 翻译(正确)
options: { in_progress: '进行中' }❌ Ignoring Coverage Reports
❌ 忽略覆盖率报告
Stale translations can cause confusion. Always run before releases.
os i18n check过期的翻译会导致混淆。发布前务必运行。
os i18n checkQuick-Start Template
快速入门模板
One compact per-locale file — assemble locales with and
register via as shown in
"Authoring Translation Bundles" above:
<!-- os:check -->
defineTranslationBundledefineStack({ translations: [...] })typescript
// src/translations/zh-CN.ts
import type { TranslationData } from '@objectstack/spec/system';
export const zhCN: TranslationData = {
objects: {
account: {
label: '客户',
pluralLabel: '客户',
fields: {
name: { label: '客户名称' },
email: { label: '邮箱', placeholder: '输入邮箱地址' },
status: {
label: '状态',
options: {
active: '活跃',
inactive: '停用',
},
},
},
_views: {
all_accounts: { label: '全部客户' },
},
},
},
apps: {
crm: { label: '客户关系管理' },
},
messages: {
'common.save': '保存',
'common.cancel': '取消',
},
};一个简洁的按区域划分的文件——如“编写翻译包”部分所示,使用组装区域,并通过注册:
<!-- os:check -->
defineTranslationBundledefineStack({ translations: [...] })typescript
// src/translations/zh-CN.ts
import type { TranslationData } from '@objectstack/spec/system';
export const zhCN: TranslationData = {
objects: {
account: {
label: '客户',
pluralLabel: '客户',
fields: {
name: { label: '客户名称' },
email: { label: '邮箱', placeholder: '输入邮箱地址' },
status: {
label: '状态',
options: {
active: '活跃',
inactive: '停用',
},
},
},
_views: {
all_accounts: { label: '全部客户' },
},
},
},
apps: {
crm: { label: '客户关系管理' },
},
messages: {
'common.save': '保存',
'common.cancel': '取消',
},
};Verify your work
验证你的工作
After editing a bundle:
*.translation.tsbash
os i18n check # translation coverage vs the default locale (missing-key report)
os validate # the bundle conforms to the protocol schema (no artifact)编辑包后:
*.translation.tsbash
os i18n check # 翻译覆盖率与默认区域对比(缺失键报告)
os validate # 包符合协议schema(无错误)or: os build # the same schema gate, plus emits dist/
或:os build # 相同的schema校验,同时生成dist/
`os i18n check` lists keys missing per locale; `os lint --i18n-strict` turns
coverage gaps into hard errors. In a scaffolded project the schema gate is
`npm run validate`. See objectstack-platform → **Verify your work**.
---
`os i18n check`列出每个区域缺失的键;`os lint --i18n-strict`将覆盖率缺失转为严重错误。在脚手架项目中,schema校验的命令是`npm run validate`。详见objectstack-platform → **验证你的工作**。
---References
参考资料
See references/_index.md for the full list of Zod
schemas (with one-line descriptions) — pointers into
. Always the source for exact field
shapes; do not rely on memory of property names.
node_modules/@objectstack/spec/src/Read请查看references/_index.md获取完整的Zod schema列表(含单行描述)——指向中的文件。如需了解精确的字段结构,请始终查看源代码;不要依赖对属性名称的记忆。
node_modules/@objectstack/spec/src/See Also
另请参阅
- objectstack-data — For understanding object and field metadata structure
- objectstack-ui — For view, app, and action translations
- objectstack-automation — For workflow and flow message translations
- objectstack-data — 了解对象和字段元数据结构
- objectstack-ui — 了解视图、应用和操作的翻译
- objectstack-automation — 了解工作流和流程消息的翻译