运行时国际化模块
通过已安装模块和 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 目录的校验范围内。