关于 experience-ui-bundle-localize
用于为 Salesforce UI Bundle 添加本地化能力的技能,面向维护 React 或 Angular 界面的开发者。它将界面字符串提取为 Custom Labels,并通过 Platform SDK 和 GraphQL 在运行时获取标签供 i18n 库呈现。
主要功能
- 01扫描并识别面向用户的字符串
- 02将字符串替换为 Custom Labels
- 03通过清单注册标签键
- 04借助 Platform SDK 与 GraphQL 获取标签
- 05检测框架、Bundle 类型及组织 API 版本
适用场景
- 01为现有 React 或 Angular UI Bundle 添加多语言文本
- 02检查无障碍标签等界面字符串是否已本地化
- 03为 Salesforce 组织配置运行时标签加载
- 04确认项目及组织是否满足本地化前置条件
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ forcedotcom/ sf-skills --skill experience-ui-bundle-localize安装后在 Claude Code 中输入/experience-ui-bundle-localize确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/angular/check-i18n-wired.shi18n-setup.mdinterpolation.mdlocalize.mdreferences/common/gotchas.mdlabel-xml.mdplatform-sdk-i18n.mdverifying.mdreferences/react/check-i18n-wired.shi18n-setup.mdinterpolation.mdlocalize.mdscripts/check-manifest-registered.shcheck-org-api-version.shdetect-bundle-type.shdetect-framework.shscripts/tests/test-detect-bundle-type.sh详细介绍
本地化 UI Bundle
本地化 UI Bundle:将面向用户的字符串提取到 Salesforce 自定义标签中,通过 Platform SDK 后端接入运行时 i18n 库,并验证各语言区域中的标签。
本文是与框架无关的工作流与防护指南核心。与框架相关的细节——使用哪种 i18n 库、翻译调用约定、扫描哪些文件、接线方式以及文档深度——请参阅各框架的参考文档。
一段话理解
UI Bundle 无法像 LWC 那样使用编译时标签导入(@salesforce/label/* 会由平台编译器解析,而独立的 Bundle 不会经过该编译器)。相反,应用会通过 Salesforce GraphQL UI API 在运行时获取标签,并交由标准 i18n 库进行渲染。Platform SDK 提供运行时所需的基础组件:用于读取用户语言的检测器、通过 GraphQL 获取标签的后端,以及上下文获取功能。你只需编写两个精简文件——一个简短的初始化文件,将 SDK 组件接入 i18n 库;另一个清单,列出应用使用的标签——然后将标签本身编写为 Salesforce 自定义标签元数据。具体使用的库和调用约定因框架而异;请参阅相应框架的参考文档。
步骤 0:确定任务路径
| 任务是…… | 前往 |
|---|---|
| Bundle 尚不存在 | experience-ui-bundle-frontend-generate 技能 |
| 使用其标签部署应用 | experience-ui-bundle-deploy 技能 |
配置站点语言或 sfdc_cms__languageSettings | experience-ui-bundle-site-generate 技能 |
| 本地化现有 bundle | 确定框架(见下文),然后按工作流操作 |
确定框架。 通常,调用上下文已经确定了框架——由调用此技能的协调技能传递,或由用户在请求中说明。请使用该框架。
此技能支持的框架,恰好是 <SKILL_DIR>/references/ 下各自包含 localize.md 的参考文件夹(例如,react 对应 <SKILL_DIR>/references/react/localize.md)。这是唯一可信来源——要添加框架,只需添加参考文件夹,无需更改本文档。
-
如果框架已知——打开
<SKILL_DIR>/references/<framework>/localize.md,并将其与本文档一并参照。该文件会说明所用库、调用约定、要扫描的文件以及接线代码。 -
如果框架未知(独立运行且无人说明框架)——在询问任何人之前,先对应用或 uiBundle 根目录运行确定性检测器:
bash "<SKILL_DIR>/scripts/detect-framework.sh" "<app-or-uiBundle-root>"它会结合根目录或其上级目录中的
angular.json、任何非node_modulespackage.json中的@angular/core/react,以及源文件特征,然后打印一个标记并设置相应的退出码:react或angular(退出码 0)→ 使用该框架。不要询问用户——检测结果是确定的。打开<SKILL_DIR>/references/<framework>/localize.md。ambiguous(退出码 2)→ 两种框架均存在。列出<SKILL_DIR>/references/并询问用户要本地化哪一种。如果用户指定的框架没有对应的参考文件夹,则此处不支持该框架——停止。unknown(退出码 3)→ 未检测到受支持的框架。终止工作流。 不要猜测,也不要继续:报告 bundle 中未发现 React 或 Angular 特征,因此无法继续本地化,并在此停止。
在下列步骤中,<framework> 均指此处选定的文件夹。确定性检查脚本按耦合关系分为以下两类:
- 框架无关,位于
<SKILL_DIR>/scripts/——detect-framework.sh(上述步骤 0 检测器)、check-org-api-version.sh和detect-bundle-type.sh(纯 org/元数据检查),以及check-manifest-registered.sh(通用骨架;通过--framework <framework>选择调用点语法)。 - 框架专属,位于
<SKILL_DIR>/references/<framework>/下——check-i18n-wired.sh(其将清单接入后端的检测方式与 i18n 库相关,因此每个框架都提供自己的脚本)。
前置条件:编辑前进行验证
| # | 要求 | 验证方式 | 缺失时的处理 |
|---|---|---|---|
| 1 | 这是一个 uiBundles/*/src/ 项目(React 或 Angular) | 项目结构符合要求 | 不是 UI Bundle → 转到正确的技能 |
| 2 | Platform SDK、UI Bundle 和 build-plugin 同级依赖均已安装且版本一致(≥11.49.3) | 检查 UI Bundle 目录中的 package.json | 告知用户需要统一版本并升级;无法继续 |
| 3 | 能确定应用的挂载位置 | 阅读入口文件(参见框架参考) | 没有明确的挂载位置 → 请用户指出 |
| 4 | 目标组织确实支持 API v68.0+(UI Bundle 的运行时标签 GraphQL 随 Release 264 发布) | 执行下方的运行时组织版本检查 | 组织的最高 API 版本低于 v68.0(Release 262 或更早版本)→ 无法继续;改用 Release 264+ 组织,或升级该组织 |
| 5 | Bundle 已通过身份验证,为 B2E;或者请求/上下文明确指出这是 B2C 站点,而非 B2B | 执行下方的 Bundle 类型检测,并使用请求/上下文判断站点产品类型 | 明确为 B2B → 拒绝;站点类型不明确 → 询问用户并暂停,直到得到确认;B2C 还需满足前置条件 6 |
| 6 | 仅针对 B2C:管理员已启用 GraphQLApiOrgPrefForGuestUsers | 请管理员确认组织偏好设置已启用 | 不要启用该设置;说明未启用时,访客 GraphQL 请求会返回 HTTP 403,并停止(依赖项:W-23854208) |
运行时组织版本检查(前置条件 4)。 在运行时为 UI Bundle 解析标签的 platform.labels GraphQL 路径随 Salesforce Release 264 发布(API v68.0 或更高版本)。sfdx-project.json 中的 sourceApiVersion 记录的是你声明的版本,而不是组织支持的版本,因此,使用较新 CLI 指向较旧组织时,静态文件检查可能通过,但运行时会失败。在配置任何内容之前,先查询组织实际支持的最高 API 版本:
bash <SKILL_DIR>/scripts/check-org-api-version.sh <org-alias-or-username>
退出码为 0 → 组织支持 v68.0+,继续。退出码为 1 → 组织版本过旧或无法访问;不要配置 i18n 或标签,向用户报告版本不匹配并停止。(脚本中的 sf api request rest 会将身份验证保留在 CLI 传输层,因此访问令牌不会进入上下文。)
Bundle 类型检测(前置条件 5)。 Bundle 类型决定适用的本地化分支。此检测与框架无关(纯 Salesforce 元数据)。传入 Bundle 目录的完整路径;脚本会据此推导元数据根目录,因此当前目录无关紧要:
bash <SKILL_DIR>/scripts/detect-bundle-type.sh <path-to-uiBundles/<name>/ dir>
按照退出码约定处理:0 → 已通过身份验证的应用(B2E 或核心内部应用),使用 B2E 分支;10 → 绑定到公开站点的应用容器候选项,表示元数据证明了站点绑定和访客访问,但不能证明是 B2C 还是 B2B;11 → 绑定到非公开/不受支持的站点,停止;12 → 同时存在 CustomApplication 和一个站点绑定,询问用户要将哪个运行时上下文作为本地化目标;13 → 存在多个匹配的 Experience 站点绑定,显示报告中的站点名称,并询问用户要将哪个站点/运行时上下文作为目标;2 → 未绑定/未知,报告脚本输出并停止,不要猜测。
对于退出码 10,根据明确的请求/上下文进行分流:如果说明是 B2C,确认前置条件 6 并使用 B2C 分支;如果说明是 B2B,则拒绝;如果产品类型不明确,询问用户站点是 B2C 还是 B2B,并暂停,直到得到确认。对于退出码 12 和 13,要求用户选择运行时上下文(退出码 13 还需选择站点),然后应用相应分支的回退方案和前置条件。绝不要根据 DigitalExperienceConfig、appSpace、appContainer 或 AUTHENTICATED_WITH_PUBLIC_ACCESS_ENABLED 推断为 B2C;身份验证值表示已启用访客访问。
对于 B2C,只有组织管理员可以启用 GraphQLApiOrgPrefForGuestUsers;绝不要代为配置或更改此设置。未启用时,访客标签请求会返回 HTTP 403。通过 W-23854208 跟踪其可用性。
如果不满足前置条件,请停止:向用户报告具体的阻碍,并记录一项计划,待问题解决后再继续。不要为 B2B 或未知 bundle 添加 i18n 配置或 TODO 标记。
工作流:五个步骤
每个步骤都有完成标准和一个继续前确认暂停点。框架的具体信息(文件扩展名、翻译调用、安装、初始化代码)来自
<SKILL_DIR>/references/<framework>/localize.md。
步骤 1:检测
**目标:**扫描框架的组件文件,查找面向用户的硬编码字符串。(框架参考文档会列出要扫描的文件扩展名。)
扫描内容:
- 标记语言中向用户显示的字符串:标题中的
Welcome→ 候选项 - 面向用户的字符串属性:
placeholder="Enter name"→ 候选项 - 面向用户的无障碍文本:
aria-label、aria-describedby、alt→ 候选项(屏幕阅读器用户会听到这些内容,因此也必须本地化)
跳过内容:
- 导入语句
- 对象键/属性名
data-*属性(机器可读)- 测试 ID(
data-testid、id属性) - 已经封装在翻译调用中的文本
- 控制台日志、抛给开发者的错误消息(非面向用户)
- 类名、文件路径、技术常量
操作:
- 扫描
src/目录中的框架组件文件 - 提取候选项,并列出每项的文件路径和行号
- 向开发者展示列表
完成标准: 开发者确认列表(或对列表进行编辑以移除误报)。
暂停:“我在 M 个组件中找到 N 个面向用户的字符串。列表如下:[显示文件路径:行号 + 字符串]。看起来对吗?[确认/编辑列表/跳过部分项]”
步骤 2:提取
**目标:**为每个已确认的字符串添加 Custom Label,并将字面文本替换为翻译调用。
每个字符串的操作:
- 提出一个键名,格式为:
<Context>_<Role>(例如,"Welcome"→Welcome_Text,"Save"→Save_Button,"Failed to save"→Save_Failed_Message)。遵循命名规则:单词使用 PascalCase,各部分之间用下划线分隔,并确保名称足够明确且唯一。 - 将标签添加到
force-app/main/default/labels/CustomLabels.labels-meta.xml:
(完整 XML 结构:<labels> <fullName>Welcome_Text</fullName> <language>en_US</language> <protected>false</protected> <shortDescription>Welcome banner heading</shortDescription> <value>Welcome</value> </labels>references/common/label-xml.md) - 在组件中将字符串替换为相应框架的翻译调用,并添加所需的导入/注入。具体调用方式见框架参考文档。
完成标准:
每个已确认的字符串都在其原位置拥有一个 CustomLabels 条目和一个翻译调用。
暂停:“对于每个字符串,我都会添加一个 Custom Label,并将字面文本替换为翻译调用。拟议的键名如下:[显示 字符串 → 命名空间:键名 的映射]。应用这些编辑吗?[是/逐项检查]”
步骤 3:注册
目标: 将每个键添加到标签清单中,以便 i18n 运行时知道要获取哪些内容。
操作:
- 将每个键添加到
src/i18n/label-manifest.ts中的清单数组:
如果该文件尚不存在,步骤 4 会创建它;下方的完成检查会报告文件缺失,因此不要手动检查文件是否存在。export const labelManifest = [ "c:Welcome_Text", "c:Save_Button", "c:Save_Failed_Message", ];
完成标准:
在 UI 包目录中运行 check-manifest-registered.sh(它会相对于当前目录扫描 src/),并报告它返回的任何错误。该脚本负责确定性检查:它会将每个翻译调用点与清单交叉核对,并在存在调用点但缺少 label-manifest.ts 时判定为失败。已调用但未注册的键在运行时会直接以其字面名称显示,且不会报错;本检查用于防范这种静默失败陷阱。
cd <path-to-uiBundles/<name>/ dir> # scripts scan src/ relative to here
bash <SKILL_DIR>/scripts/check-manifest-registered.sh --framework <framework>
根据退出码采取相应操作:0,每个键都已注册(或者没有需要检查的调用点),继续。1,清单缺失或所列键不在清单中;创建文件或添加键(步骤 4 会创建文件),然后重新运行。64,用法错误,源目录不存在(当前工作目录错误或参数无效);这不是“缺少键”的结果,不要创建文件或注册键,修正路径后重新运行。
暂停:“已向 label-manifest.ts 添加 N 个条目。check-manifest-registered.sh 已通过:[确认]。”
第 4 步:接线
目标: 确保 i18n 接线已就绪;如果应用尚未设置 i18n,则搭建相关配置。安装命令、接线代码以及 B2E 与 B2C 的回退配置,均见框架参考文档(references/<framework>/localize.md 和 i18n-setup.md)。
检查:
在 UI bundle 目录中运行 check-i18n-wired.sh(它会相对于当前目录扫描 src/),并报告返回结果。该脚本负责完整的确定性检查:查找框架的 i18n 接线(React:在启动时调用的 initI18n() 初始化文件;Angular:注册自定义 TranslateLoader 的 provideTranslateService/TranslateModule.forRoot,并在启动时通过 TranslateService.use 加载),如果接线存在,还会报告标签清单是否已导入且确实能到达标签后端/加载器。不要自行阅读文件并重新推导这些信息。
cd <path-to-uiBundles/<name>/ dir> # scripts scan src/ relative to here
bash <SKILL_DIR>/references/<framework>/check-i18n-wired.sh
根据退出码进行分支处理(输出的消息会列出报告中要用的具体文件/符号,但决策依据是退出码):
- 退出码
0→ 接线完整,清单能到达标签后端/加载器;只需添加新键。 - 退出码
1→ 尚无 i18n 接线;按照框架参考文档搭建完整配置。 - 退出码
2→ 接线存在但不完整(React:未在启动时调用初始化;Angular:未注册加载器,或已注册但未在启动时调用TranslateService.use);不要重新搭建或覆盖现有配置。只添加消息指出的缺失接线,然后重新运行检查。 - 退出码
3→ 已在启动时接线,但脚本无法确认清单能否到达后端/加载器。最后这项检查采用文本启发式:清单可能通过脚本无法识别的变量、展开语法、工厂或辅助函数接线,因此应将退出码 3 视为“编辑前先核实”,而不是“确定存在问题”。打开消息中列出的文件并确认后再调整;切勿重新搭建或重复添加已有且有效的接线。 - 退出码
64→ 用法错误:源目录不存在(当前工作目录错误或参数有误)。这不代表“没有接线”;不要搭建配置。修正路径后重新运行。
B2C 覆盖规则——即使退出码为 0 也适用:接线检查只能证明 i18n 存在,不能证明它适用于 B2C。使用 B2E 接线的种子项目——dir = ctx.dir,且加载器没有 labelFallback——在运行 check-i18n-wired.sh 时会通过并返回退出码 0,但对 B2C 网站而言是错误的。如果网站是 B2C,不要止步于“添加新键”:打开 src/i18n/index.ts(或框架的初始化文件),并使用 resolvedLang(= SFDC_ENV.language || ctx.lang;检测器不会读取 SFDC_ENV.language)进行以下操作:(a) 添加 labelFallback: "USER_DEFAULT";(b) 根据它设置方向——使用 i18next.dir(resolvedLang),绝不能使用 ctx.dir;(c) 在其中初始化——React 在 i18next.init 中设置 lng: resolvedLang;Angular 调用 translate.use(resolvedLang)。请参阅 references/<framework>/i18n-setup.md。切勿在 B2C 网站上保留 B2E 接线。
完成标准:
i18n 接线已就绪,并且在启动时调用一次;清单能到达标签后端/加载器。对于 B2C,必须应用全部三项 resolvedLang 覆盖设置——USER_DEFAULT 回退、显示语言(React lng、Angular translate.use)以及 i18next.dir(resolvedLang) 方向——不能使用种子项目的 B2E 默认值。
严格遵循框架参考文档中的具体搭建说明,绝不覆盖现有接线。
暂停:“i18n 设置[已存在/已创建]。已在启动时加载:[确认]。”
步骤 5:验证
目标: 指导开发者验证标签是否能以第二种语言呈现。
操作: 按照 references/common/verifying.md 中针对分支的流程操作。
对于 B2E,启用第二种语言,编写或获取其翻译元数据,针对目标 org 构建,仅部署目标 bundle 和标签元数据,然后更改已认证用户的 Language 并重新加载。
对于 B2C,按照框架参考文档中的详细说明,验证已配置的网站语言、URL 路由、SFDC_ENV.language、完整重新加载的语言切换器、本地化的本地预览、访客 GraphQL 访问权限以及缓存清理。
部署 bundle、标签和翻译不会发布 Experience 网站。将 sf community publish 视为单独的上线变更:显示确切的网站和目标 org,然后在运行命令前立即等待用户明确确认。
如果未能呈现: 检查 references/common/gotchas.md 中列出的问题:
- 清单键未注册(步骤 3 漏掉了一个标签)
- API 版本不匹配(针对不同的 org 构建)
- 标签缓存过期(React:localStorage 中的
i18next_res_*;Angular:内存缓存,重新加载会重新获取) - B2C 访客 GraphQL 403(管理员未启用
GraphQLApiOrgPrefForGuestUsers) - B2C 路由、网站语言和
SFDC_ENV.language不一致
完成标准: 标签可在 ≥2 种区域设置下呈现,或已确定阻止问题。
暂停:“要进行验证:在 Translation Workbench 中启用第二种语言,编写翻译(我可以搭建 XML 框架),然后构建/部署并重新加载。要我为 [language] 搭建翻译文件吗?[y / 我会手动处理]”
边缘情况:妥善处理
- 代码已本地化:检测是否已有翻译调用用法 / 已填充的清单;建议在现有设置基础上添加,而不是重新搭建全部内容。
- 未找到字符串:清楚地报告并停止;不要凭空添加工作。
- 应用尚未设置 i18n:步骤 4 先搭建两个文件,然后步骤 3 才能注册内容。
- 设置不完整(存在清单但缺少初始化,或反之),协调现有内容;绝不覆盖已有接线配置。
- B2B 网站:明确拒绝支持。支持 B2C 并不意味着支持 B2B。
防护措施:绝不破坏以下内容
- 绝不对可部署元数据进行机器翻译。 搭建一个格式正确的
<Translations>文档,每个标签都包含一个闭合的<customLabels>块,并对英文源文本进行 XML 转义。保留占位符,并在完成前解析这两个元数据文件。翻译人员应手动或通过 Translation Workbench 替换搭建的值;绝不为此调用机器翻译 API。 - 绝不注册没有标签的键。 清单条目数必须等于标签数(步骤 3 的标准)。未注册的键会以其自身的字面名称呈现,且不会显示控制台警告。这是最常见的本地化错误。
- 绝不覆盖已有的 i18n 接线配置。 如果步骤 4 发现已有
initI18n(),应协调现有配置(如果缺少清单导入,则补上),而不是替换整个文件。 - 每个文件都必须适用于客户。 任何地方都不得包含
webapps、仅供核心使用的路径或内部基础设施引用。应以外部客户使用 SFDX 项目的情境来编写。 - 绝不隐式发布 B2C 网站。 元数据部署和网站发布是分开的。只有在显示网站和 org 并收到针对该次发布的明确确认后,才能运行
sf community publish。
命令与目录布局
<project-root>/ ← SFDX project root
└── force-app/main/default/
├── labels/CustomLabels.labels-meta.xml ← English base labels
├── translations/<locale>.translation-meta.xml ← one per translated language
└── uiBundles/<your-bundle>/
├── package.json
└── src/
├── i18n/
│ ├── index.ts ← init wiring (you write this once)
│ └── label-manifest.ts ← list of labels to fetch (you maintain this)
└── components/ ← components call the translation function
| 命令 | 运行位置 | 用途 |
|---|---|---|
安装 i18n 依赖(参见 references/<framework>/localize.md) | UI bundle 目录 | 安装框架的 i18n 库(第 4 步) |
npm run build | UI bundle 目录 | 构建应用(会内置 API 版本;先设置 target-org) |
参见 references/<framework>/verifying.md | 项目根目录 | 检查并将确切的目标 bundle 和变更后的标签元数据部署到指定组织 |
sf project retrieve start --metadata Translations:<locale> | 项目根目录 | 拉取在 Translation Workbench 中编写的翻译 |
预检清单:整个运行的完成标准
- 每个已确认的字符串都有一个
CustomLabels条目和一个翻译调用 -
label-manifest.ts条目数 == 标签数(没有未注册的键) - 已接入 i18n,并在启动时调用一次;清单已接入标签后端/加载器
- 仅限 B2C:已确认访客 GraphQL 偏好设置,已配置
USER_DEFAULT,且站点语言路由与SFDC_ENV.language匹配 - 仅限 B2E:没有覆盖回退设置;保留框架参考中的默认值
- 标签可在 ≥2 种区域设置下呈现(或已指出阻塞问题)
-
*-meta.xml中没有写入手工编写的机器翻译(仅提供脚手架和指南) - 两个元数据文件均可解析为 XML;翻译脚手架只有一个
<Translations>根元素,且每个标签都有一个闭合的块