fcode-i18n
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseFactorial Code — i18n
Factorial Code — i18n
A workspace keeps one locale per language it speaks: a YAML file of
translation keys under , synced by the CLI like any other resource.
resolves a key against the locale the current execution is
using — in process code, in module code, and (substituted server-side) in form
schemas. Platform model in ; CLI flow in ; form
embedding in .
i18n/fcode.i18n("key")fcode-core-conceptsfcode-clifcode-forms每个工作区针对其支持的每种语言维护一个locale(区域设置):目录下的一个翻译键YAML文件,通过CLI与其他资源同步。会根据当前执行使用的区域设置解析键值——该逻辑适用于流程代码、模块代码,以及(服务端替换的)表单schema中。平台模型相关内容见;CLI流程见;表单嵌入见。
i18n/fcode.i18n("key")fcode-core-conceptsfcode-clifcode-formsGotchas
注意事项
- Never alias — translations are only shipped to an execution when
fcode.i18nis statically detected in the source, sofcode.i18n(throws "i18n is disabled" at runtime. Always call it literally, with the key as a hardcoded string (same class of rule asconst t = fcode.i18n; t("k")module names).fcode.import - The helper never fails. A key with no translation anywhere resolves to
the key itself — a raw in output means a missing translation, never a broken run. A placeholder you pass no argument for is left exactly as written; a non-object
greetings.hello(including arrays) is ignored.args - Locale identifiers are case-sensitive (≠
pt-BR) and this is permanent platform-wide. Valid: up to 20 letters, numbers,pt-bror-, starting with a letter or number (_) — no dots. On a case-folding filesystem (macOS/Windows defaults) the CLI refuses to pull two locales differing only in case, since their files would collapse into one.^[A-Za-z0-9][A-Za-z0-9_-]{0,19}$ - Never edit — read-only, gitignored, regenerated on pull. To override an inherited key, write it into your own
i18n/<locale>.inherited.yaml: overrides layer key by key, never file by file, so keys you don't mention keep resolving to the parent's text.i18n/<locale>.yaml - is a reserved name on form and webhook endpoints, like
localeandversion_tag: it selects the language and is stripped before the parameters are built. A form field or webhook body field namedasyncnever reaches the process — use another name for business data.locale - Form-token arguments must be a flat object of scalars —
works,
{ "max": "500" }does not; a token with nested braces is left untouched in the served schema.{ "max": { "chars": "500" } } - A mistyped tag resolves against the current files silently — it never blanks output. When released text looks un-frozen, check the pinned tag before anything else.
version - A YAML key written without a value counts as untranslated — it falls through to the fallback locale rather than resolving to an empty string.
- 切勿给 设置别名 — 只有当源码中被静态检测到
fcode.i18n调用时,翻译内容才会被加载到执行环境中,因此fcode.i18n(会在运行时抛出 "i18n is disabled" 错误。请始终直接调用该方法,且键值必须是硬编码字符串(规则与const t = fcode.i18n; t("k")模块名的要求一致)。fcode.import - 助手方法不会抛出错误。任何未找到翻译的键值都会直接返回键本身——输出中出现原始的 意味着翻译缺失,而非运行失败。未传入对应参数的占位符会保持原样;非对象类型的
greetings.hello(包括数组)会被忽略。args - 区域设置标识符区分大小写(≠
pt-BR),这是全平台统一的规则。合法标识符要求:最多20个字符,可包含字母、数字、pt-br或-,以字母或数字开头(正则规则:_)——不能包含点号。在大小写不敏感的文件系统(macOS/Windows默认)中,CLI会拒绝拉取仅大小写不同的两个区域设置文件,因为它们会被合并为一个文件。^[A-Za-z0-9][A-Za-z0-9_-]{0,19}$ - 切勿编辑 — 该文件为只读状态,已被Git忽略,会在拉取时重新生成。如需覆盖继承的键值,请写入自己的
i18n/<locale>.inherited.yaml文件:覆盖是逐个键生效的,而非整个文件,因此未提及的键值仍会解析为父级文本。i18n/<locale>.yaml - 是表单和Webhook端点的保留名称,与
locale、version_tag类似:它用于选择语言,会在参数构建前被移除。名为async的表单字段或Webhook请求体字段永远不会被流程接收——请使用其他名称存储业务数据。locale - 表单令牌参数必须是扁平的标量对象 — 可用,
{ "max": "500" }不可用;包含嵌套大括号的令牌会在返回的schema中保持原样。{ "max": { "chars": "500" } } - 拼写错误的 标签会静默解析为当前文件内容 — 不会清空输出内容。当发布后的文本看起来未冻结时,请首先检查固定的标签是否正确。
version - 未设置值的YAML键视为未翻译 — 会回退到备用区域设置的对应值,而非解析为空字符串。
Locale files
区域设置文件
One YAML mapping of keys to text per locale, at . Nesting
is a convenience for whoever writes the file, not a data model: nested keys are
addressed with dots, so these two files are the same locale —
i18n/<locale>.yamlyaml
undefined每个区域设置对应一个YAML键值映射文件,路径为 。嵌套结构仅为编写文件提供便利,并非数据模型:嵌套键需用点号寻址,因此以下两个文件是完全相同的区域设置——
i18n/<locale>.yamlyaml
undefinedi18n/en.yaml
i18n/en.yaml
greetings:
hello: "Hi %{name}"
farewell: "See you"
```yamlgreetings:
hello: "Hi %{name}"
farewell: "See you"
```yamlidentical to the file above
与上方文件完全一致
"greetings.hello": "Hi %{name}"
"greetings.farewell": "See you"
— which is what lets a child workspace override a single key without repeating
the parent's structure. `%{name}` placeholders are filled from the arguments
passed to the helper. A locale file is capped at 256 KB (the whole merged set
travels with each execution).
What the workspace **inherits** sits alongside what it owns, in
`i18n/<locale>.inherited.yaml` — read-only, gitignored (the CLI adds the
entry). When several parent workspaces define the same locale, the inherited
file holds their merge in the platform's resolution order, rewritten as a flat
mapping of dotted keys under a generated header; with a single parent the file
is kept verbatim, comments included. A local run layers your own file over it
exactly as the cloud does."greetings.hello": "Hi %{name}"
"greetings.farewell": "See you"
——这也使得子工作区可以覆盖单个键值,无需重复父级的结构。`%{name}` 占位符会被传入助手方法的参数填充。单个区域设置文件大小上限为256 KB(合并后的完整翻译集会随每次执行一同传输)。
工作区**继承**的内容会与自有内容并存于 `i18n/<locale>.inherited.yaml` 文件中——该文件为只读状态,已被Git忽略(CLI会添加忽略规则)。当多个父工作区定义了同一区域设置时,继承文件会按照平台解析顺序合并它们的内容,并将其重写为带点号的扁平键值映射,顶部带有生成的头部;若只有单个父级,则文件会保留原样,包括注释。本地运行时会将自有文件覆盖在继承文件之上,与云端逻辑完全一致。The fcode.i18n
helper
fcode.i18nfcode.i18n
助手方法
fcode.i18nfcode.i18n(key, args, options)javascript
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
const locale = fcode.i18n.locale; // the execution's localepython
greeting = fcode.i18n("greetings.hello", {"name": "Ada"})
fcode.i18n("greetings.farewell")
fcode.i18n("legal.terms", None, {"version": "v1.0.0"})
locale = fcode.i18n.locale- The only option today is — a locale version tag or alias; see versioning below.
version - Interpolation is a single pass over own properties: a substituted value
containing is never rescanned (one argument can't reach another), and
%{...}resolves nothing.%{constructor} - Missing key → the key itself; missing argument → placeholder left as written. Nothing here ever throws while translations were shipped — the one exception is calling through an alias (see Gotchas).
fcode.i18n(key, args, options)javascript
const greeting = fcode.i18n("greetings.hello", { name: "Ada" }); // 在`en`区域设置中返回 "Hi Ada"
fcode.i18n("greetings.farewell"); // 无占位符→无需传入args
fcode.i18n("legal.terms", null, { version: "v1.0.0" }); // 固定到已发布的版本
const locale = fcode.i18n.locale; // 获取当前执行的区域设置python
greeting = fcode.i18n("greetings.hello", {"name": "Ada"})
fcode.i18n("greetings.farewell")
fcode.i18n("legal.terms", None, {"version": "v1.0.0"})
locale = fcode.i18n.locale- 当前唯一可用的选项是 — 区域设置版本标签或别名;详情见下方版本控制部分。
version - 插值仅对自有属性进行单次遍历:替换后的值中若包含 不会被重新扫描(一个参数无法引用另一个参数),且
%{...}不会解析任何内容。%{constructor} - 缺失键→返回键本身;缺失参数→占位符保持原样。只要翻译内容已加载,此处不会抛出任何异常——唯一的例外是通过别名调用(见注意事项)。
Translating form schemas
翻译表单Schema
Form schemas are rendered by the browser, so there is no runtime to resolve
keys in. Write the same call as a string in — in
titles, descriptions, , ,
any visible text — and the platform substitutes it before serving the
schema. The browser receives a schema already written in one language;
translations never reach the client. Substitution runs after ,
so text a pre-render injects is translated too.
parametersSchema.jsonui:placeholderembedFormOptions.loadingOverlayContentpreRenderProcessjson
{
"type": "object",
"properties": {
"reason": {
"type": "string",
"title": "fcode.i18n(\"form.reason.label\")",
"description": "fcode.i18n(\"form.reason.help\", { max: \"500\" })"
}
}
}Arguments follow relaxed JavaScript syntax (single quotes, unquoted field
names, trailing commas all accepted) but must stay a flat object of
scalars — a nested value leaves the whole token unsubstituted. A missing key
resolves to the key itself, visible but never form-breaking.
The reader's locale comes from the embed: in the embed options or the
attribute, sent to the platform as the
header. Changing it refetches the schema, and the submit carries the same
header, so the execution runs in the language the form was rendered in.
Embedding mechanics in . ( in the embed options
plays no part here — it only selects the language of rjsf's built-in
validation messages when isn't one it ships.)
localedata-fcode-form-localeFcode-Localefcode-formsfallbackLocalelocale表单Schema由浏览器渲染,因此没有运行时环境可解析键值。请在 中将相同调用以字符串形式写入——包括标题、描述、、 等所有可见文本——平台会在返回Schema前完成替换。浏览器收到的Schema已经是单语言版本;翻译内容永远不会到达客户端。替换会在 之后执行,因此预渲染注入的文本也会被翻译。
parametersSchema.jsonui:placeholderembedFormOptions.loadingOverlayContentpreRenderProcessjson
{
"type": "object",
"properties": {
"reason": {
"type": "string",
"title": "fcode.i18n(\"form.reason.label\")",
"description": "fcode.i18n(\"form.reason.help\", { max: \"500\" })"
}
}
}参数遵循宽松的JavaScript语法(支持单引号、未引号的字段名、 trailing逗号),但必须是扁平的标量对象——嵌套值会导致整个令牌不被替换。缺失的键会直接返回键本身,可见但不会破坏表单。
读者的区域设置来自嵌入参数:嵌入选项中的或属性,会以请求头的形式发送到平台。修改该值会重新拉取Schema,提交时会携带相同的请求头,因此执行会使用表单渲染时的语言。嵌入机制详情见。(嵌入选项中的在此处不起作用——它仅在不在RJSF内置支持的语言列表中时,选择RJSF内置验证消息的语言。)
localedata-fcode-form-localeFcode-Localefcode-formsfallbackLocalelocaleHow the execution locale is chosen
执行区域设置的选择规则
| Trigger | How to choose |
|---|---|
| Form | |
| Webhook | |
| Run now | Locale selector in the run dialog |
| Schedule | Locale selector when creating or editing the schedule |
| Rerun | Reuses the original execution's stored locale |
A malformed locale on the public endpoints is a ; an unknown-but-valid
one merely falls back. The chosen locale is stored on the execution, which is
why a rerun reproduces the original run's language even if the workspace's
default has moved since.
400When nothing names a locale, the workspace's primary locale is used —
in (set it under Settings → Details, or edit the
file and ; field reference in ) — and when none is
chosen, the first locale alphabetically. The primary locale is also the
key-level fallback: a key the chosen locale hasn't translated resolves from
the primary, and only a key missing from both resolves to its own name. So a
partially translated locale still resolves every key — but keep the primary
complete.
primaryLocaleteam.jsonfcode team:pushfcode-cli| 触发方式 | 选择方式 |
|---|---|
| 表单 | |
| Webhook | |
| 立即运行 | 运行对话框中的区域设置选择器 |
| 调度任务 | 创建或编辑调度时的区域设置选择器 |
| 重新运行 | 复用原始执行存储的区域设置 |
公开端点上的格式错误区域设置会返回错误;未知但格式合法的区域设置会直接回退到备用值。选择的区域设置会存储在执行记录中,因此即使工作区的默认区域设置已更改,重新运行仍会重现原始运行的语言。
400当未指定区域设置时,会使用工作区的主区域设置——即中的(可在设置→详情中修改,或编辑文件后执行;字段参考见);若未设置主区域设置,则使用按字母顺序排列的第一个区域设置。主区域设置同时也是键级别的备用选项:所选区域设置未翻译的键值会从主区域设置中解析,只有当两者都缺失时才会返回键本身。因此即使区域设置仅部分翻译,仍能解析所有键值——但请确保主区域设置的翻译完整。
team.jsonprimaryLocalefcode team:pushfcode-cliCLI: syncing and testing locales
CLI:同步与测试区域设置
Locales are a CLI resource like any other:
sh
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- Aggregate /
fcode pull/pushinclude locales, so the usual whole-workspace commands already cover them.status - There is no extract command — moving hardcoded strings into locale files is the agent's job (next section); the CLI only syncs the files.
- Pushing an identifier a parent workspace owns creates an override here, layered key by key — it never edits the parent's file.
- lives in
primaryLocaleand syncs withteam.json.fcode team:push
Local runs resolve against the same files, layered
exactly as the cloud does, so behaves like production.
Pass to run in a specific one:
fcode.i18ni18n/fcode run my-process--localesh
fcode run my-process --locale pt-BR--locale区域设置与其他资源一样,是CLI的管理对象:
sh
fcode i18n:pull # 获取所有区域设置,包括继承的内容
fcode i18n:status # 对比本地与云端的变更
fcode i18n:add pt-BR # 新增本地区域设置文件
fcode i18n:push # 在云端创建或更新区域设置
fcode i18n:remove pt-BR # 停止跟踪本地区域设置
fcode i18n:reset # 丢弃本地变更- 聚合命令 /
fcode pull/push会包含区域设置,因此常规的全工作区命令已覆盖这些操作。status - 没有提取命令 — 将硬编码字符串迁移到区域设置文件是开发者的工作(下一节);CLI仅负责同步文件。
- 推送父工作区已拥有的标识符会在此处创建覆盖 — 按逐个键的方式分层,不会修改父级文件。
- 存储在
primaryLocale中,通过team.json同步。fcode team:push
本地运行时会根据相同的文件解析,分层逻辑与云端完全一致,因此的行为与生产环境一致。可传入参数指定运行区域:
i18n/fcode.i18nfcode run my-process--localesh
fcode run my-process --locale pt-BR本地不会验证的格式:拼写错误会静默匹配不到任何文件,所有键值都会返回自身。如果本地运行显示原始键值,请首先检查该参数的拼写(和大小写)是否正确。
--localeInternationalizing existing code
国际化现有代码
There is no automated extraction — internationalizing a workspace is a code
transformation you perform, with the CLI as the sync vehicle:
- Agree scope with the user: which locales, and which is primary. If
unset, write in
primaryLocaleandteam.json.fcode team:push - Inventory the user-facing strings. Translate: form-schema titles,
descriptions, placeholders and ; result
loadingOverlayContentstrings a form displays; email subjects and bodies; webhook response bodies end users see. Do not translate: log messages, developer-facing errors, datastore keys, variable names, slugs and identifiers.message - Name the keys in dotted namespaces: (
<process-slug>.<area>.<name>,order-sync.form.title), with strings shared across processes underorder-sync.email.subject. Extract dynamic parts ascommon.*— never concatenate translated fragments.%{placeholders} - Replace each string: in code with a literal
call (never aliased, key hardcoded); in schemas with the token string (flat scalar args only).
fcode.i18n("key", { args }) - Populate for every locale (
i18n/<locale>.yamlfor new ones). The primary locale must cover every key — it is the fallback all the others lean on.fcode i18n:add - Test per locale: . A raw dotted key in the output is a missing translation; a literal
fcode run <slug> --locale <loc>is a missing argument.%{name} - Push: (or aggregate
fcode i18n:push), plusfcode pushiffcode team:pushchanged.primaryLocale
没有自动提取工具——工作区国际化是需要开发者手动完成的代码转换,CLI仅作为同步工具:
- 与用户确认范围:支持哪些区域设置,哪个是主区域设置。若未设置,请在中写入
team.json并执行primaryLocale。fcode team:push - 盘点面向用户的字符串:需要翻译的内容包括:表单Schema的标题、描述、占位符和;表单显示的结果
loadingOverlayContent字符串;邮件主题和正文;终端用户可见的Webhook响应体。请勿翻译:日志消息、面向开发者的错误信息、数据存储键、变量名、短标识符等。message - 命名键值:使用带点号的命名空间:(如
<流程短标识>.<区域>.<名称>、order-sync.form.title),跨流程共享的字符串使用order-sync.email.subject前缀。将动态部分提取为common.*——切勿拼接翻译片段。%{占位符} - 替换每个字符串:在代码中使用字面量的调用(切勿使用别名,键值必须硬编码);在Schema中使用令牌字符串(仅支持扁平标量参数)。
fcode.i18n("key", { args }) - 为每个区域设置填充(新区域设置使用
i18n/<locale>.yaml)。主区域设置必须覆盖所有键值——它是其他区域设置的备用依赖。fcode i18n:add - 按区域设置测试:。输出中出现原始带点号的键值意味着翻译缺失;出现字面量
fcode run <标识> --locale <区域>意味着缺失对应参数。%{name} - 推送变更:执行(或聚合命令
fcode i18n:push),若fcode push有变更则需额外执行primaryLocale。fcode team:push
Versioning locales
区域设置版本控制
Locales are versioned like processes and modules (model in
): publishing snapshots the YAML under an immutable tag,
and aliases are movable pointers — but per locale: on
and on are two different aliases.
fcode-core-conceptsproductionenproductionesA pinned call () resolves per file in the
inheritance chain: each locale file answers with its snapshot at that tag, or
with its current content when it has no snapshot at that tag, layered key by
key as usual. So a locale created after the version was cut still contributes
its keys, a parent that never published the tag still contributes its text,
and a mistyped tag resolves everything against the current files rather than
blanking output. Deleting a version sends the calls pinned to it back to the
current files; deleting a locale deletes its versions with it.
{ version: "v1.0.0" }A workspace version freezes translations with the release. Creating one
(, see ) publishes a version of every
owned locale — locales first, so the pins below have a target — and rewrites
the published snapshots so bare calls pin the tag, in process
code, module code, and form schemas:
fcode team:versions:createfcode-clifcode.i18njavascript
// Working copy (never modified)
fcode.i18n("greetings.hello", { name });
fcode.i18n("legal.terms");
// Published v1.0.0 snapshot
fcode.i18n("greetings.hello", { name }, { version: "v1.0.0" });
fcode.i18n("legal.terms", null, { version: "v1.0.0" }); // None in PythonCalls already passing options are considered intentional and left untouched.
(Note the asymmetry with module imports, which are pinned in the string form —
— for compatibility with older executors; don't
"fix" one to look like the other.) Fixing a released typo means publishing
again — snapshots are immutable.
fcode.import("m", "v1.0.0")区域设置的版本控制与流程和模块一致(模型见):发布操作会将YAML文件快照存储到不可变的标签下,别名是可移动的指针——但按区域设置独立管理:区域的别名与区域的别名是两个独立的对象。
fcode-core-conceptsenproductionesproduction固定版本的调用()会按继承链中的每个文件解析:每个区域设置文件会返回该标签下的快照,若该标签下无快照则返回当前内容,按常规的逐个键分层逻辑合并。因此在版本发布后创建的区域设置仍会贡献其键值,从未发布该标签的父级仍会贡献其文本,拼写错误的标签会解析为当前文件内容而非清空输出。删除版本会使固定到该版本的调用重新使用当前文件;删除区域设置会同时删除其所有版本。
{ version: "v1.0.0" }工作区版本会随发布冻结翻译内容。创建工作区版本(,见)会发布所有自有区域设置的版本——先发布区域设置,以便后续的固定调用有目标——然后重写发布快照,使无版本参数的调用固定到该标签,包括流程代码、模块代码和表单Schema:
fcode team:versions:createfcode-clifcode.i18njavascript
// 工作副本(不会被修改)
fcode.i18n("greetings.hello", { name });
fcode.i18n("legal.terms");
// 发布后的v1.0.0快照
fcode.i18n("greetings.hello", { name }, { version: "v1.0.0" });
fcode.i18n("legal.terms", null, { version: "v1.0.0" }); // Python中无null参数已传入选项的调用会被视为有意设置,保持不变。(注意与模块导入的不对称性:模块导入在字符串中固定版本————以兼容旧版执行器;请勿修改其中一种格式使其与另一种一致。)修复发布后的拼写错误需要重新发布——快照是不可变的。
fcode.import("m", "v1.0.0")REST API, SDKs & MCP tools
REST API、SDK与MCP工具
Locales are addressed by identifier, and upserts, so a sync never needs
to know whether the workspace already had the locale:
PUTGET /{team}/rest/locales
GET /{team}/rest/locales/{locale}
PUT /{team}/rest/locales/{locale} body: { "content": "<yaml>" }
DELETE /{team}/rest/locales/{locale}Both SDKs expose the same surface as :
FcodeI18njavascript
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");python
from fcode_sdk import FcodeI18n
i18n = FcodeI18n()
i18n.list()
i18n.set("pt-BR", 'greetings:\n hello: "Olá %{name}"\n')
i18n.delete("pt-BR")set()The MCP server exposes , , and
. replaces the locale's content entirely — to
add keys, first and write back the merged YAML.
get_localesget_localesave_localedelete_localesave_localeget_locale区域设置通过标识符寻址,操作为更新插入,因此同步无需知道工作区是否已存在该区域设置:
PUTGET /{team}/rest/locales
GET /{team}/rest/locales/{locale}
PUT /{team}/rest/locales/{locale} body: { "content": "<yaml>" }
DELETE /{team}/rest/locales/{locale}两个SDK都暴露了与相同的接口:
FcodeI18njavascript
import { FcodeI18n } from "@factorialco/fcode-sdk";
const i18n = new FcodeI18n();
await i18n.list(); // 包含继承的内容
await i18n.set("pt-BR", 'greetings:\n hello: "Olá %{name}"\n');
await i18n.delete("pt-BR");python
from fcode_sdk import FcodeI18n
i18n = FcodeI18n()
i18n.list()
i18n.set("pt-BR", 'greetings:\n hello: "Olá %{name}"\n')
i18n.delete("pt-BR")对父工作区已拥有的标识符执行会在此处创建覆盖——与CLI推送的逐个键分层逻辑相同。
set()MCP服务器暴露了、、和方法。会完全替换区域设置的内容——如需添加键值,请先调用获取当前内容,合并后再写回YAML。
get_localesget_localesave_localedelete_localesave_localeget_locale