mcpField
F
fcode-i18n

fcode-i18n

github.com/factorialco开发1282 次安装今天更新
代码仓库

关于 fcode-i18n

fcode-i18n 是 Factorial Code 的本地化技能,用于在工作区管理按语言划分的翻译。它支持运行时查找和表单 Schema 替换,并提供 CLI 命令同步语言文件。

主要功能

  1. 01支持 JavaScript 和 Python 翻译查找
  2. 02按语言或版本固定查找
  3. 03占位符插值与回退
  4. 04服务端替换表单 Schema 文本
  5. 05通过 CLI 同步和测试语言文件

适用场景

  1. 01本地化流程和模块的输出
  2. 02提供翻译后的表单 Schema
  3. 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 是同步工具:

  1. 与用户商定范围:要支持哪些区域设置,以及哪个是主区域设置。如果尚未设置,请在 settings.json 中写入 primaryLocale,并运行 fcode settings:push。
  2. 盘点面向用户的字符串。 需要翻译:表单架构中的标题、描述、占位符和 loadingOverlayContent;表单会显示的结果 message 字符串(这些是 Markdown——请在每个区域设置中保留 **粗体**、链接或表格语法等格式);电子邮件主题和正文;最终用户可见的 webhook 响应正文。不要翻译:日志消息、面向开发者的错误、数据存储键、变量名、slug 和标识符。
  3. 为键命名,使用点分隔的命名空间:<process-slug>.<area>.<name>(order-sync.form.title、order-sync.email.subject);多个流程共用的字符串放在 common.* 下。将动态部分提取为 %{placeholders}——切勿拼接已翻译的片段。
  4. 替换每个字符串:代码中使用字面量 fcode.i18n("key", { args }) 调用(不得使用别名,键必须硬编码);架构中使用 token 字符串(仅限扁平标量参数)。
  5. 为每个区域设置填充 i18n/<locale>.yaml(新增区域设置时使用 fcode i18n:add)。主区域设置必须涵盖每个键——其他所有区域设置都会以它为回退。
  6. 按区域设置测试:fcode run <slug> --locale <loc>。如果输出中出现原始点分隔键,说明缺少翻译;如果出现字面量 %{name},说明缺少参数。
  7. 推送:运行 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 写回。

评论

支持 Markdown