FForm Platform
enzh-CN

运行时国际化模块

通过已安装模块和 JSON 管理内容增加命名空间词条,无需重新编译宿主。

运行时 i18n 模块开发文档

目标与边界

`FormPlatform.I18n` 把第三方维护的界面词条从编译后的应用资源中分离出来。

服务器模块、校验规则、管理表单和固定浏览器加载器只编译、安装一次;此后增加

或修改词条只是数据操作,不需要源代码、应用构建或模块重新编译。

该目录只适合公开的界面文本,不是密钥库、内容管理系统、任意 JavaScript

注册表或 HTML 模板库。为了让登录页等认证前界面也能使用词条,目录由匿名

只读接口返回。

模块包与运行时存储

部署包包含:

项目职责
`FormPlatform.I18n.dll`校验、持久化、ACL 管理 API 和表单初始化
`module.json`模块身份、兼容范围、菜单和自有表单声明
`client/index.js`固定加载器;使用 `cache: 'no-store'` 获取目录并调用 `registerMessages`
`client/messages.json`不可变的首次启动种子;仅在运行时目录不存在时复制
`appsettings.i18n.json`运行时目录路径配置

默认可写目录是 `App_Data/FormPlatform.I18n/client/messages.json`。它特意不放在

`Modules` 下:应用身份只需对数据目录有写权限;重新部署模块不会覆盖客户词条;

可执行模块目录仍可保持只读。

`FormPlatformI18n:CatalogPath` 可以是绝对路径,也可以是相对 Content Root 的

路径。多节点部署时,应把所有节点配置到同一个持久共享位置,并为最终 IIS

应用池或 Windows Service 身份授予读取、写入、建立和删除文件的权限。写操作

使用跨进程锁文件与原子替换。如果各节点使用独立的本地 `App_Data`,它们的

词条目录也会独立,必须由运维流程另行同步。部署脚本会保留目标目录中已有的

`appsettings.i18n.json`;模块升级时应由运维人员手工检查并合并新增默认项。

词条目录契约

JSON 结构是“语言 -> 扁平键名 -> 字符串”:

{
  "en": {
    "acme.crm.customer.name": "Customer name",
    "acme.crm.customer.email": "Email address"
  },
  "zh-CN": {
    "acme.crm.customer.name": "客户姓名",
    "acme.crm.customer.email": "电子邮箱"
  }
}

不要用嵌套键对象。客户端会直接查找完整的点分键名。表单可以这样引用:

{
  "props": {
    "label": "@acme.crm.customer.name"
  }
}

表单专属的 `displayName`、`description` 不应放进运行时目录。请在 Designer 的

**表单设置 / Action Code** 中编辑;平台会把它们保存到表单定义自己的

`translations[locale]`,并在删除表单时一起删除。定义内的译文是字面量,不加

`@`;当前语言缺项时按字段回退到顶层基础文字。

只有多个表单/模块共享的稳定命名词条,或确实需要脱离表单生命周期独立维护的

内容,才适合放在运行时目录并以 `@key` 引用。顶层基础 `displayName`、

`description` 仍可用这种显式引用;未加 `@` 的基础值绝不会查询目录。现有目录

词条不会随表单删除,因为平台不能证明它没有其他使用者;迁入定义后需要目录

管理员明确清理旧 key。控件自己的 `translations` 对象也继续随表单 schema 保存。

校验采用失败关闭:

  • 语言名使用 BCP-47 风格语法,并按不区分大小写保证唯一。
  • 键名区分大小写,最长 256 字符,至少包含三段点分命名空间。
  • 第一段必须是第三方自己拥有的命名空间。`formplatform`、`common`、`auth`、

`validation`、`menu`、`designer`、`survey`、`respondent`、`system`、`error`、

`http`、`license`、`offline` 和 `i18ncatalog` 为平台保留根,不区分大小写。

  • 值可以是任意合法 JSON 字符串,包括 Unicode、使用 JSON 转义表示的控制字符、

尖括号和类似标记的文本;单值最长 10,000 字符。

  • 整个目录最多 2 MiB UTF-8、32 种语言、50,000 个词条。
  • 不允许 JSON 注释和尾随逗号;重复语言属性和重复键会在反序列化折叠前被发现。

当前语言缺少值时,客户端回退到 `en`,与 FormPlatform 原有动态词条行为一致。

管理与授权

模块启动时只建立一次 `System_I18n_Catalog`:

f2000000-0000-0000-0000-000000000001

该表单的 `department_id = NULL`,按平台部门规则属于共享系统表单,但不是匿名

表单。所有管理接口还会通过 `IFormAccessAuthorizer` 检查该表单的 **Read**

操作,把表单 ACL 与部门范围一起计算。代码没有写死 Administrator。Administrator

仍是平台的隐式恢复主体;其他维护人员必须获得该表单的显式 Read 授权。

如果固定 ID 已存在,初始化器通常不会覆盖表单。用户通过 Designer 修改的 schema、

Action Code、metadata、ACL 和空部门归属都能跨重启、升级保留。1.0.1 只有一项

窄范围兼容修复:当现有显示名或描述仍精确等于 1.0.0 未加前缀的默认 key 时,

将其改为显式 `@key` 引用;任何自定义值都保持不变。如果相同技术名已属于另一个

ID,模块只记录警告并停止建立,不替换任何表单。

管理表单提供:

  • 完整 JSON 编辑器和校验报告;
  • 每次保存/导入都进行 ETag 乐观并发检查;
  • 上传 UTF-8 JSON 文件或粘贴 JSON 导入;
  • 必须显式选择的冲突处理方式;
  • JSON 导出;
  • 保存成功后立即在当前浏览器重新注册词条。

重复与冲突规则

默认导入方式是 `reject`。只要已存目录中存在相同语言和完全相同的键名,就算

冲突;即使两个值相同也不例外。服务器返回 HTTP 409,并在界面显示冲突,整个

导入不修改文件。服务器统计全部冲突,并最多返回前 500 项详情。

`keep-existing` 只增加新键,冲突处保留原值。`overwrite` 增加新键并在冲突处

改用导入值。服务器不会隐式选择这两种方式。完整编辑器代表显式替换整个目录,

所以允许操作员有意修改或删除已有键。

ETag 是存储 JSON 原文的 SHA-256。缺少 ETag 或 ETag 已过期都会返回 HTTP 409;

操作员必须重新载入、检查新内容再操作,服务器不会静默执行“最后写入者获胜”。

HTTP API

方法和路径身份要求用途
`GET /api/i18n/catalog`匿名只返回校验通过的语言目录;`no-store`
`GET /api/i18n/messages`系统用户 + 表单 Read ACL返回原始 JSON、ETag、有效状态、问题和修改时间
`PUT /api/i18n/messages`系统用户 + 表单 Read ACL校验并显式替换完整目录
`POST /api/i18n/messages/import`系统用户 + 表单 Read ACL按 `reject`、`keep-existing` 或 `overwrite` 合并
`GET /api/i18n/messages/export`系统用户 + 表单 Read ACL下载当前存储的 UTF-8 JSON 原文

运行时文件无效时,匿名接口绝不返回原文,而是返回 503,浏览器不会注册新的

目录。已授权编辑器仍会收到原文和校验问题,以便用有效内容修复。匿名调用者

看不到无效内容的细节。

翻译存储层不会猜测消费端如何呈现字符串。普通控件把本地化值作为文本显示;

表单或控件如果显式启用 `allowHtml`,会把最终译文视为受信任 HTML。应只向可信

内容维护者授予目录管理权限;确需接收不可信标记时,应在 HTML 消费边界净化。

第三方使用流程

1. 选择并记录一个不会与别人重复的厂商根,例如 `acme`。

2. 准备 UTF-8 JSON,包含所需语言和扁平点分键。

3. 打开“运行时翻译”,使用默认 `reject` 策略导入。

4. 检查每个冲突。归属不明确时改名;只有确实要替换时才选择覆盖策略。

5. 在表单 JSON/Designer 字段中用 `@键名` 引用,或在受信任代码中调用正常

i18n API。

6. 刷新其他已经打开的浏览器。每次页面加载都以 `cache: 'no-store'` 获取;

管理表单保存后,当前浏览器会立即刷新注册。

受控部署自动化也可以直接编辑运行时文件,但应以原子方式写入完整有效文件,

然后刷新浏览器。优先使用管理 API,因为它会检测原始重复键、检查 ETag 并统一

格式化。

部署、备份与回滚

运行模块 `deploy.ps1`,然后重启一次 FormPlatform 以加载 DLL 并建立表单/目录。

以后只改词条无需重启。应把配置后的运行时 JSON 与其他 `App_Data` 一起备份;

模块包内种子不等于客户数据备份。

大批量导入前先导出当前目录。需要回滚时,可以显式使用 `overwrite` 导入备份,

再在完整编辑器中移除不需要的键;也可以直接用导出的完整 JSON 替换。

卸载默认保留运行时数据。`-RemoveMenuEntries` 和 `-RemoveSystemForms` 会为下次

启动排队执行正常的 Host 清理;`-RemoveData` 会永久删除模块默认 App_Data

目录,只应在确认备份后使用。卸载器不会删除自定义的绝对目录,运维人员应自行

决定保留或删除。

冲突检测边界

模块能够阻止同一个运行时目录内部的静默重复。单独编译的客户端扩展拥有各自的

消息注册表,服务器模块目前看不到所有这些注册表,所以厂商仍必须使用全局唯一

根。如果两个独立编译扩展故意注册同一个外部键,这属于扩展打包冲突,不在本

JSON 目录的校验范围内。