mcpField
O
objectstack-i18n

objectstack-i18n

github.com/objectstack-ai开发76230 次安装今天更新
代码仓库

关于 objectstack-i18n

用于编写和校验 ObjectStack 多语言翻译的 Skill,涵盖符合架构的翻译包、运行时加载、变量插值,以及通过命令行检查覆盖率和文件新鲜度。

主要功能

  1. 01配置支持语言与回退语言
  2. 02编写经架构校验的翻译包
  3. 03翻译对象及应用元数据
  4. 04运行时加载并合并翻译
  5. 05检查覆盖率与生成文件新鲜度

适用场景

  1. 01为 ObjectStack 堆栈设置语言
  2. 02翻译对象字段、视图和操作
  3. 03发布前查找缺失翻译
  4. 04在 CI 中校验生成的翻译包

安装

复制下面的命令,在终端中运行。

$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 协议

翻译配置

Stack 级 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)
});
属性类型必填 / 默认值描述
defaultLocalestring必填默认 BCP-47 区域代码
supportedLocalesstring[]必填所有受支持的区域
fallbackLocalestring可选缺少翻译时使用的回退区域

BCP-47 区域代码:使用标准区域标签(例如 en-US、zh-CN、pt-BR、en-GB)。


编写翻译包(objects.*)

规范的编写方式:每种区域对应一个 TranslationData,使用 defineTranslationBundle 组装并注册到 stack 上。这与已提供的 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 是一个键);相同的键也适用于 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-47en、en-US、zh-CN、pt-BR
objects.* 中的对象键snake_caseobjects.project_task、objects.support_case
字段键snake_casefields.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 是警告。 为不存在的对象、字段、视图、选项卡、操作、参数、分区、验证规则、应用、 导航项、仪表板、微件或流程屏幕命名的键,会与实际存在的名称一同列出。在操作 条目下,还有三种情况属于错误:操作未在 outcomeMessages 中声明的结果;未声明的 resultDialog.fields 路径;以及操作未声明 resultDialog 时出现的 resultDialog title / description / acknowledge——翻译包只能翻译操作声明的 对话框,不能添加对话框。参数的内联选项键与字段的 options 共用选项警告;引用 field 且未声明内联 options 的参数不受检查。以已重命名内容为键的翻译包仍可解析—— 该标签只会在其源区域设置下静默显示,而所有相邻标签都能正常解析。


文件组织策略

布局属于编写约定:无论选择哪种布局,你自己的导入图都会将其组装为注册到堆栈上的 TranslationBundle——不会有任何机制替你加载目录树。

布局文件适用场景
捆绑式一个 crm.translation.ts,包含所有区域设置少于 5 个对象、2-3 种区域设置
按区域设置(推荐)src/translations/{en,zh-CN,ja-JP}.ts + index.ts5-20 个对象、3-5 种区域设置
按命名空间i18n/{locale}/{object}.json,由你自己的导入组装20 个以上对象、5 种以上区域设置

运行时编写:translation 条目

翻译不一定要作为文件发布。translation 元数据条目可在 Studio 中、通过元数据 API 或由 Agent 创建,其中包含某一种区域设置的内容:即上文所述的同一套 objects.* 分组,以及它所翻译的 locale。locale 为必填项,每个条目对应一种区域设置; 已发布的条目会在启动时及每次发布时加载,并叠加在文件翻译包之上(删除该条目后, 随软件发布的值会恢复)。格式只有一种,不会进行格式转换。使用 defineTranslation 编写;确切的 Zod 结构定义位于 node_modules/@objectstack/spec/src/system/translation.zod.ts 中的 TranslationItemSchema。


消息插值

简单格式(默认)

随软件提供的两个适配器(FileI18nAdapter 和内存回退实现)只会替换 双花括号 {{variable}} 占位符——单花括号会原样保留。(架构文档字符串提到了 {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(点分隔路径)、status(missing | redundant | stale)、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——因此请从 @objectstack/spec/contracts 导入类型,而不要读取 node_modules 中的源文件。)

以下方法由两个已发布的适配器实现:来自 @objectstack/service-i18n 的 FileI18nAdapter,以及在未安装 i18n 插件时由 @objectstack/core 注册的内存回退实现:

  • t(key, locale, params?) — 点路径解析(例如 objects.account.label),支持 {{param}} 插值和回退语言查找
  • 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),以及一个 <locale>.metadata-forms.generated.ts 配套文件,除非指定 --no-metadata-forms。默认语言环境会根据架构标签填充,其他语言环境则遵循 --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: [...] }) 注册其翻译包。自带生成包的包或插件——这是此代码库中最常见的形式——还需要额外一步:生成模块导出的是 objects 子树(NonNullable<TranslationData['objects']>),而不是 TranslationData,因此必须先将其包装成翻译包。

// 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,包含 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 → Verify your work。


参考资料

完整的 Zod 架构列表(含单行说明)请参见 references/_index.md — 链接指向 node_modules/@objectstack/spec/src/。请始终 Read 源码以确认准确的字段结构;不要凭记忆推测属性名称。

另请参阅

  • objectstack-data — 了解对象和字段的元数据结构
  • objectstack-ui — 了解视图、应用和操作的翻译
  • objectstack-automation — 了解工作流和流程消息的翻译

评论

支持 Markdown