关于 fcode-i18n
fcode-i18n 是 Factorial Code 的本地化技能,用于在工作区管理按语言划分的翻译。它支持运行时查找和表单 Schema 替换,并提供 CLI 命令同步语言文件。
主要功能
- 01支持 JavaScript 和 Python 翻译查找
- 02按语言或版本固定查找
- 03占位符插值与回退
- 04服务端替换表单 Schema 文本
- 05通过 CLI 同步和测试语言文件
适用场景
- 01本地化流程和模块的输出
- 02提供翻译后的表单 Schema
- 03使用指定语言测试工作区运行
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ factorialco/ factorial-code-skills --skill fcode-i18n安装后在 Claude Code 中输入/fcode-i18n确认已加载。
详细介绍
Factorial Code — i18n
一个工作区为它支持的每种语言保留一个 locale:存放在 i18n/ 下的翻译键 YAML 文件,由 CLI 像同步其他资源一样进行同步。fcode.i18n("key") 会根据当前执行所使用的 locale 查找键值——适用于进程代码、模块代码,以及(由服务器端替换的)表单架构。平台模型参见 fcode-core-concepts;CLI 流程参见 fcode-cli;表单嵌入参见 fcode-forms。
注意事项
- 绝不要为
fcode.i18n创建别名——只有在源代码中静态检测到fcode.i18n(时,翻译才会随执行一起提供。因此,const t = fcode.i18n; t("k")会在运行时抛出 "i18n is disabled"。始终直接调用它,并将键写成硬编码字符串(这与fcode.import模块名的规则相同)。 - 此辅助函数永远不会失败。 如果某个键在任何位置都没有翻译,它会返回键本身——输出中出现原样的
greetings.hello表示缺少翻译,而不是运行出错。对于未传入参数的占位符,会原样保留;非对象类型的args(包括数组)会被忽略。 - Locale 标识符区分大小写(
pt-BR≠pt-br),这是永久的平台级规则。有效标识符最多包含 20 个字母、数字、-或_,且必须以字母或数字开头(^[A-Za-z0-9][A-Za-z0-9_-]{0,19}$)——不能包含点号。在不区分大小写的文件系统(macOS/Windows 默认设置)上,CLI 会拒绝拉取仅大小写不同的两个 locale,因为它们的文件会合并为一个。 - 绝不要编辑
i18n/<locale>.inherited.yaml——该文件只读、已加入 gitignore,并会在拉取时重新生成。要覆盖继承的键,请将其写入自己的i18n/<locale>.yaml:覆盖按键逐项叠加,而不是按文件叠加,因此未提及的键仍会解析为父级文本。 locale是表单和 webhook 端点上的保留名称,与version_tag和async一样:它用于选择语言,并会在构建参数前被移除。名为locale的表单字段或 webhook 请求体字段不会传递到进程——业务数据请使用其他名称。- 表单令牌参数必须是由标量组成的扁平对象——
{ "max": "500" }可用,{ "max": { "chars": "500" } }不可用;包含嵌套花括号的令牌在提供的架构中会保持原样。 - 写错的
version标签会静默地根据当前文件解析——它不会让输出变空。如果发布后的文本看起来没有被冻结,请先检查固定的标签。为单次调用指定一个不存在的 locale 时,{ locale }也是同样的情况:查找行为如同未指定 locale。 - 固定的
version必须是内联字面字符串——要发布的快照会从源代码中读取,因此{ version: chosenTag }会根据当前文件解析。locale选项没有这项规则:其值可以是任意运行时表达式,在其他位置构建的选项也仍然有效。 - 未填写值的 YAML 键会被视为未翻译——它会回退到备用 locale,而不是解析为空字符串。
Locale 文件
每种 locale 对应一个 YAML 映射文件,包含键到文本的映射,路径为 i18n/<locale>.yaml。嵌套只是为了方便文件编写者,不代表数据模型:嵌套键通过点号寻址,因此下面两个文件表示同一种 locale —
# i18n/en.yaml
greetings:
hello: "Hi %{name}"
farewell: "See you"
# identical to the file above
"greetings.hello": "Hi %{name}"
"greetings.farewell": "See you"
— 这样,子工作区就能覆盖单个键,而无需重复父级的结构。%{name} 占位符会使用传递给辅助函数的参数填充。每个 locale 文件大小上限为 256 KB(每次执行都会携带合并后的完整集合)。
工作区继承的内容与其自有内容并列存放在 i18n/<locale>.inherited.yaml 中——该文件为只读且被 gitignore(CLI 会添加相应条目)。当多个父工作区定义了同一种 locale 时,继承文件会按照平台的解析顺序合并这些内容,并在自动生成的标头下改写为以点分隔键组成的扁平映射;只有一个父工作区时,文件会原样保留,包括注释。本地运行时会将你自己的文件叠加在该文件之上,方式与云端完全相同。
fcode.i18n 辅助函数
fcode.i18n(key, args, options)——在 JavaScript 和 Python 中名称与语义相同,可用于进程和模块(即使进程只能通过模块访问它,也仍能获取翻译):
const greeting = fcode.i18n("greetings.hello", { name: "Ada" }); // "Hi Ada" in `en`
fcode.i18n("greetings.farewell"); // no placeholders → no args
fcode.i18n("legal.terms", null, { version: "v1.0.0" }); // pinned to a published version
fcode.i18n("greetings.hello", { name: "Ada" }, { locale: "es" }); // another locale, this lookup only
const recipient = { name: "Ada", locale: "pt-BR" }; // ...and the value may be dynamic,
fcode.i18n("greetings.hello", { name: recipient.name }, { locale: recipient.locale }); // per recipient
fcode.i18n("legal.terms", null, { version: "v1.0.0", locale: "es" }); // both combine
const locale = fcode.i18n.locale; // the execution's locale
greeting = fcode.i18n("greetings.hello", {"name": "Ada"})
fcode.i18n("greetings.farewell")
fcode.i18n("legal.terms", None, {"version": "v1.0.0"})
fcode.i18n("greetings.hello", {"name": "Ada"}, {"locale": "es"})
recipient = {"name": "Ada", "locale": "pt-BR"}
fcode.i18n("greetings.hello", {"name": recipient["name"]}, {"locale": recipient["locale"]})
fcode.i18n("legal.terms", None, {"version": "v1.0.0", "locale": "es"})
locale = fcode.i18n.locale
version— locale 版本标签或别名,必须是内联字符串字面量(参见注意事项);版本控制见下文。locale— 在另一种 locale 中读取该次查找,其值可以是动态的(例如员工的语言),因此一次执行可以使用多种语言。若指定的 locale 未翻译某个键,则回退到执行所用的 locale,再回退到主要 locale;若该 locale 不存在,其行为就如同未指定此选项——不会比不使用该选项更差。fcode.i18n.locale仍会报告执行所用的 locale。表单模式标记不支持此选项——渲染时已经使用请求指定的语言。- 插值只会单次遍历自有属性:替换后的值即使包含
%{...},也不会再次扫描(一个参数无法引用另一个参数),且%{constructor}不会解析出任何内容。缺失的键和参数不会抛出错误(参见注意事项)。
翻译表单模式
表单模式由浏览器渲染,因此没有运行时来解析键。请在 parametersSchema.json 中将相同的调用写成字符串——可用于标题、描述、ui:placeholder、embedFormOptions.loadingOverlayContent 以及任何可见文本;平台会在提供模式之前替换它。浏览器收到的模式已经使用一种语言编写;翻译不会传到客户端。替换会在 preRenderProcess 之后运行,因此预渲染注入的文本也会被翻译。
{
"type": "object",
"properties": {
"reason": {
"type": "string",
"title": "fcode.i18n(\"form.reason.label\")",
"description": "fcode.i18n(\"form.reason.help\", { max: \"500\" })"
}
}
}
参数遵循宽松的 JavaScript 语法(接受单引号、无引号字段名和尾随逗号),但必须保持为一个仅含标量的扁平对象——嵌套值会导致整个标记无法替换(参见注意事项)。
读取者的 locale 来自嵌入配置:嵌入选项中的 locale,或 data-fcode-form-locale 属性;该值会通过 Fcode-Locale 标头发送给平台。更改它会重新获取模式,提交请求也会携带相同的标头,因此执行会使用表单渲染时所用的语言。嵌入机制见 fcode-forms。(嵌入选项中的 fallbackLocale 与此无关——仅当 locale 不属于 rjsf 自带的语言时,它才会选择 rjsf 内置验证消息所用的语言。)
如何选择执行区域设置
| 触发方式 | 选择方式 |
|---|---|
| 表单 | ?locale= 查询参数或 Fcode-Locale 标头(参数优先) |
| Webhook | ?locale= 查询参数或 Fcode-Locale 标头(参数优先) |
| 立即运行 | 运行对话框中的区域设置选择器 |
| 计划 | 创建或编辑计划时使用区域设置选择器 |
| 重新运行 | 重用原始执行中存储的区域设置 |
| 单次调用 | 辅助函数选项中的 locale——仅影响该次查找,值可以动态设置 |
公共端点上的格式错误区域设置会返回 400;有效但未知的区域设置只会回退。选定的区域设置会存储在执行记录中,因此即使工作区的默认设置后来发生变化,重新运行也会复现原始运行所用的语言。
未指定区域设置时,会使用工作区的主要区域设置——即 settings.json 中的 primaryLocale(可在 Settings → Details 中设置,也可编辑文件后运行 fcode settings:push;字段参考见 fcode-cli);如果未选择,则使用按字母顺序排列的第一个区域设置。主要区域设置也是键级回退:如果所选区域设置未翻译某个键,就从主要区域设置中解析;只有两者都缺少该键时,才会解析为键自身的名称。因此,即使区域设置只完成了部分翻译,也能解析每个键——但请确保主要区域设置完整。
CLI:同步和测试区域设置
区域设置与其他资源一样,属于 CLI 管理的资源:
fcode i18n:pull # fetch every locale, inherited ones included
fcode i18n:status # what changed locally vs the cloud
fcode i18n:add pt-BR # track a new local file
fcode i18n:push # create or update in the cloud
fcode i18n:remove pt-BR # stop tracking it locally
fcode i18n:reset # discard local changes
- 汇总命令
fcode pull/push/status会包含区域设置,因此常用的整个工作区命令已经涵盖这些内容。 - 没有提取命令——将硬编码字符串移入区域设置文件是 Agent 的工作(见下一节);CLI 只负责同步文件。
- 推送父工作区拥有的标识符会在此处创建覆盖项,并按键逐项叠加——不会编辑父工作区的文件。
primaryLocale位于settings.json中,并通过fcode settings:push同步。
本地运行会针对相同的 i18n/ 文件解析 fcode.i18n,叠加方式与云端完全一致,因此 fcode run my-process 的行为与生产环境一致。传入 --locale 可使用指定区域设置运行:
fcode run my-process --locale pt-BR
本地不会对 --locale 进行格式验证:拼写错误会静默地匹配不到任何文件,所有键都会解析为自身。如果本地运行显示的是原始键,请先检查标志的拼写(以及大小写)。
国际化现有代码
没有自动提取功能——对工作区进行国际化是一项由你执行的代码转换,CLI 是同步工具:
- 与用户商定范围:要支持哪些区域设置,以及哪个是主区域设置。如果尚未设置,请在
settings.json中写入primaryLocale,并运行fcode settings:push。 - 盘点面向用户的字符串。 需要翻译:表单架构中的标题、描述、占位符和
loadingOverlayContent;表单会显示的结果message字符串(这些是 Markdown——请在每个区域设置中保留**粗体**、链接或表格语法等格式);电子邮件主题和正文;最终用户可见的 webhook 响应正文。不要翻译:日志消息、面向开发者的错误、数据存储键、变量名、slug 和标识符。 - 为键命名,使用点分隔的命名空间:
<process-slug>.<area>.<name>(order-sync.form.title、order-sync.email.subject);多个流程共用的字符串放在common.*下。将动态部分提取为%{placeholders}——切勿拼接已翻译的片段。 - 替换每个字符串:代码中使用字面量
fcode.i18n("key", { args })调用(不得使用别名,键必须硬编码);架构中使用 token 字符串(仅限扁平标量参数)。 - 为每个区域设置填充
i18n/<locale>.yaml(新增区域设置时使用fcode i18n:add)。主区域设置必须涵盖每个键——其他所有区域设置都会以它为回退。 - 按区域设置测试:
fcode run <slug> --locale <loc>。如果输出中出现原始点分隔键,说明缺少翻译;如果出现字面量%{name},说明缺少参数。 - 推送:运行
fcode i18n:push(或聚合命令fcode push);如果primaryLocale已更改,还需运行fcode settings:push。
区域设置版本管理
区域设置的版本管理方式与流程和模块相同(模型见
fcode-core-concepts):发布会在不可变标签下创建 YAML 快照,
别名则是可移动的指针——但按区域设置区分:en 的 production
和 es 的 production 是两个不同的别名。
固定版本的调用({ version: "v1.0.0" })会在继承链中按文件解析:每个区域设置文件都会返回该标签下的快照;如果该标签下没有快照,则返回当前内容,并像往常一样按键逐层合并。因此,在版本创建后才创建的区域设置仍会提供其键;从未发布过该标签的父级仍会提供其文本;而拼写错误的标签会针对当前文件解析所有内容,而不会清空输出。{ version: "v1.0.0", locale: "es" } 也可以组合使用:快照会按请求的区域设置读取,并采用相同的回退规则。删除某个版本后,固定到该版本的调用会改为读取当前文件;删除某个区域设置时,也会一并删除其版本。
其中一层的解析方式有所不同:固定到某个版本的父工作区会返回该固定版本的快照;如果调用指定了它从未发布过的标签,则回退到该快照,而不是当前文件。其实时翻译不会传递到子级——参见 fcode-core-concepts。
工作区版本会随发布一同冻结翻译。创建工作区版本(fcode settings:versions:create,参见 fcode-cli)时,会为每个自有区域设置发布一个版本——先发布区域设置,确保下方的固定版本有可用目标——并重写已发布的快照,使不带选项的 fcode.i18n 调用固定到该标签,覆盖流程代码、模块代码和表单架构:
// Working copy (never modified)
fcode.i18n("greetings.hello", { name: "Ada" });
fcode.i18n("welcome", { name: "Ada" }, { locale: "es" });
// Published v1.0.0 snapshot
fcode.i18n("greetings.hello", { name: "Ada" }, { version: "v1.0.0" });
fcode.i18n("welcome", { name: "Ada" }, { version: "v1.0.0", locale: "es" });
选项中已指定 version 的调用——即使是动态值——都会被视为有意指定并保持不变;未指定版本的选项(仅含 locale、空对象、显式的 null/None)则会插入该标签,因此本地化调用会随发布冻结,同时保留原有区域设置——动态值({ locale: employee.locale })也会原样保留。
(请注意,这与模块导入的处理方式不对称:为兼容较旧的执行器,模块导入会固定在字符串形式中——fcode.import("m", "v1.0.0");不要为了让两者看起来一致而“修正”其中一种。)修正已发布版本中的拼写错误,需要再次发布——快照不可变。
REST API、SDK 和 MCP 工具
区域设置通过标识符寻址,且 PUT 会执行插入或更新,因此同步时无需确认工作区是否已存在该区域设置:
GET /{team}/rest/locales
GET /{team}/rest/locales/{locale}
PUT /{team}/rest/locales/{locale} body: { "content": "<yaml>" }
DELETE /{team}/rest/locales/{locale}
两个 SDK 都提供与 FcodeI18n 相同的接口:
import { FcodeI18n } from "@factorialco/fcode-sdk";
const i18n = new FcodeI18n();
await i18n.list(); // inherited included
await i18n.set("pt-BR", 'greetings:\n hello: "Olá %{name}"\n');
await i18n.delete("pt-BR");
from fcode_sdk import FcodeI18n
i18n = FcodeI18n()
i18n.list()
i18n.set("pt-BR", 'greetings:\n hello: "Olá %{name}"\n')
i18n.delete("pt-BR")
对父工作区拥有的标识符调用 set(),会在此处创建一个覆盖项——与 CLI push 执行的按键逐层合并方式相同。
MCP 服务提供 get_locales、get_locale、save_locale 和 delete_locale。save_locale 会完全替换区域设置的内容——若要添加键,请先调用 get_locale,再将合并后的 YAML 写回。