把“展品详情”改成另一种语言,看起来只需要替换一个字符串。但如果路由名也跟着页面标题变化,返回历史就会被翻译工作牵动;如果按钮文案先在启动时解析成普通字符串,再长期缓存,配置变化又会留下旧文字。GalleryGuide 进行资源化之前,先明确三种名字:业务对象的标识、导航使用的路由名、观众看到的文案。它们可以指向同一件事,却承担不同责任。

资源化页面外壳技术封面

先迁移界面外壳,保留内容协议

第1篇确立 pottery-01 等展品标识,第2篇让列表与详情共享同一导航容器。本篇继续面向 API 12 及以上,只处理随应用发布的界面文字:主页标题、详情标题、返回按钮和错误说明。展品名称、展厅与讲解仍然来自 Exhibit 数据模型。这个范围能让我们独立验证资源匹配,同时为以后接入多语言馆藏内容保留清晰边界。

界面资源与业务内容的更新节奏不同。按钮用词通常随应用版本一起审查,适合进入资源目录;展品介绍可能由内容团队维护,还涉及版本、语言与授权来源。如果把不断增长的馆藏说明都塞进字符串资源,每次内容修订都会牵涉打包发布;如果把所有界面文案也交给网络接口,离线错误页又可能无字可显示。边界不是由字符串长短决定,而是由生命周期和责任决定。

因此 ExhibitDetail 作为程序内部路由名保持不变,item.id 仍然识别业务实体,guide_detail_title 才是可翻译的资源名。资源名也不直接采用某个中文句子,而采用稳定的语义标识。修改显示文案时,无须同步改路由调用点;重命名资源键则是一项代码变更,需要检查全部引用,不能当作普通翻译编辑处理。

本篇不增加语言切换开关,直接使用系统资源匹配机制。这样可以把“资源是否配置正确”与“应用自定义语言设置是否生效”分开验证。自定义设置需要额外考虑覆盖配置、页面刷新与恢复策略,应在产品确实需要时实现,不宜为了展示一个语言按钮而把两类问题混成一个教程。

给两种语言建立相同的键集合

在 entry/src/main/resources/base/element/string.json 的现有 string 数组内追加下列条目。代码块给出本篇条目的完整集合,但不要用它覆盖模板已有的应用名称、模块描述等资源;那些名称可能仍被 app.json5 或 module.json5 引用。JSON 文件不接受注释,合并时注意逗号与重复键。

{
  "string": [
    { "name": "guide_home_title", "value": "展馆导览" },
    { "name": "guide_detail_title", "value": "展品详情" },
    { "name": "guide_back_to_list", "value": "返回展品列表" },
    { "name": "guide_unavailable_title", "value": "展品不存在" },
    { "name": "guide_unavailable_body", "value": "请返回展品列表,重新选择一个有效条目。" },
    { "name": "guide_unknown_page", "value": "无法打开页面" }
  ]
}

再创建或合并 entry/src/main/resources/en_US/element/string.json,保持资源名完全相同,只改变值。基础目录提供默认内容,限定词目录承担匹配配置下的替代值。本例采用美式英语目录是为了给出明确的验收条件,不意味着只创建一个英语目录就完成了所有地区的本地化。

{
  "string": [
    { "name": "guide_home_title", "value": "Gallery Guide" },
    { "name": "guide_detail_title", "value": "Exhibit Details" },
    { "name": "guide_back_to_list", "value": "Back to Exhibits" },
    { "name": "guide_unavailable_title", "value": "Exhibit Unavailable" },
    { "name": "guide_unavailable_body", "value": "Return to the exhibit list and choose an available item." },
    { "name": "guide_unknown_page", "value": "Unable to Open Page" }
  ]
}

同名资源在不同目录中出现,是替代关系;同一个文件内重复定义同一个名称,则是需要修复的配置问题。目录层级也不能凭习惯猜测:element 与 string.json 各有明确职责。把文件放成 resources/en_US/string.json,或者把目录写成没有对应限定词含义的自由名称,都不会得到预期匹配。

如果工程原来已经存在 zh_CN 的同名资源,需要一起核对。匹配到更具体目录时,看到的值不一定来自刚编辑的 base 文件。排查“修改中文为什么没生效”,第一步是检查同名键的所有候选目录,而不是先清空业务状态。对资源变更做代码审查时,也应同时比较键集合和翻译值,避免某个语言漏掉错误说明。

基础目录不能成为遗漏翻译的隐藏通道。回退机制保证应用有机会显示可用文字,但“能够显示”与“这一语言已经验收”是两件事。发布前可以把本篇六个键列为检查集合,逐一核对各语言目录是否存在及语义是否一致;不要仅凭运行时没有报错就认为翻译齐全。默认值既是可靠性措施,也是测试中需要主动观察的行为。

资源名称还应保持局部可理解。本例统一使用 guide_ 前缀,让导览模块的界面外壳与模板资源区分;这不是给业务对象重新编号,而是限制命名冲突的范围。若未来多个模块都需要“返回”按钮,是否共享一个键取决于语义是否长期一致,不能只因当前汉字相同就提前合并。相同文字在不同上下文中可能需要不同译法。

页面直接引用资源,不把标题当参数

在第2篇的 pages/Index.ets 中,保留数据导入、Index 的导航栈及布局属性。将 Navigation 的标题改为 .title($r('app.string.guide_home_title'))。随后用下面的实现替换整个 ExhibitDetail 组件;它保留滚动布局,并把无效标识的展示切换为资源化错误分支。

@Component
struct ExhibitDetail {
  @State item: Exhibit = missingExhibit();
  private stack: NavPathStack = new NavPathStack();

  @Builder
  detailTitle() {
    Text($r('app.string.guide_detail_title')).fontSize(20)
      .fontWeight(FontWeight.Medium)
  }

  build() {
    NavDestination() {
      Scroll() {
        Column({ space: 18 }) {
          if (this.item.id.length > 0) {
            Text(this.item.title).fontSize(28).fontWeight(FontWeight.Bold)
            Text(this.item.hall).fontSize(16).fontColor('#52616B')
            Text(this.item.summary).fontSize(18).lineHeight(28)
          } else {
            Text($r('app.string.guide_unavailable_title'))
              .fontSize(28).fontWeight(FontWeight.Bold)
            Text($r('app.string.guide_unavailable_body'))
              .fontSize(18).lineHeight(28)
          }
          Button($r('app.string.guide_back_to_list'))
            .onClick(() => this.stack.pop())
        }.width('100%').padding(24).alignItems(HorizontalAlign.Start)
      }.width('100%').height('100%')
    }
    .title(this.detailTitle)
    .onReady((context: NavDestinationContext) => {
      this.stack = context.pathStack;
      const raw = context.pathInfo.param;
      this.item = typeof raw === 'string'
        ? (findExhibit(raw) ?? missingExhibit()) : missingExhibit();
    })
  }
}

目的页映射的错误分支也要一起替换。只处理正常页面,会让不常触发的异常画面保留另一套文字风格。下面给出 Index 内的错误标题构建器与完整 destination 方法;列表点击继续使用固定的 ExhibitDetail 与展品标识,不进行翻译,也不拼接资源名称。

@Builder
unavailableTitle() {
  Text($r('app.string.guide_unknown_page')).fontSize(20)
    .fontWeight(FontWeight.Medium)
}

@Builder
destination(name: string) {
  if (name === 'ExhibitDetail') {
    ExhibitDetail()
  } else {
    NavDestination() {
      Text($r('app.string.guide_unavailable_body')).padding(24)
    }.title(this.unavailableTitle)
  }
}

$r 返回的是资源引用,不是普通字符串。Text、Button 与 Navigation 标题能够接收对应资源。API 12 的 NavDestination 标题通过自定义构建器内的 Text 使用资源;直接向其 title 传 Resource 要求 API 14,不能混用这两种容器的签名。这里不必先同步读取字符串再长期保存。资源解析和业务导航也不应该相互依赖:切换语言可以改变按钮外观,却不能把路由参数从 pottery-01 改成翻译后的展品标题。导航协议保持稳定,展示层才有自由演进的空间。

资源化错误页面的竖屏示例

本例仍用 missingExhibit 提供结构完整的默认对象,但错误分支不再显示其中的中文说明。保留这个函数是为了与前两篇数据协议兼容,后续也可以把查询结果改为明确的联合状态。不要在同一次资源迁移里顺手改变全部错误类型、导航入口和数据仓库;控制改动面,回归失败时才能知道是哪条边界被碰到了。

还有一种需要避免的写法:根据展品标识动态拼出 $r 的完整资源路径。资源引用应对应编译阶段可识别的资源定义,业务输入不应成为任意资源查找指令。需要按实体选择固定插图或本地文本时,应编写显式映射,并为未知标识给出默认值。显式映射虽然多几行,却让删除资源、重命名键和增加展品时的影响能够在代码审查中被看见。

语言变化要按输入、匹配、布局三层定位

先在中文环境打开正常详情,核对顶部标题和返回按钮,再使用不存在的标识打开详情,核对错误标题与说明。然后在美式英语环境重复同样操作。正常展品内容仍是本地中文样例,这是本篇数据范围的结果;验收外壳时不能据此判断资源匹配失败,也不能把外壳翻译完成写成全部馆藏已本地化。

如果英语界面仍出现中文按钮,检查是否命中了对应资源目录、键名是否一致、当前组件是否仍使用旧字符串。若只有一处异常,优先查那一处调用和同名资源;若所有外壳文字都未变化,再检查环境与资源目录。这个定位顺序能够缩小范围,避免一开始就引入自定义配置覆盖,让本来简单的目录问题变得复杂。

业务标识、资源键与显示文案的处理流程

布局验证延续第2篇:在窄窗口和较大字体设置下,错误说明仍应完整可读,返回按钮仍应可到达。不同语言的长度和断词方式会改变行数,不能把汉字数量当成通用宽度估算。对一个动作使用固定的极窄按钮,再要求翻译人员把句子缩短到某个字符数,是把布局限制转嫁给内容,通常会损失语义。

还要区分系统配置变更时的页面刷新与应用重新启动。资源机制负责按配置选择内容,但业务会话能否跨重建保留,需要另外实现恢复策略。本篇不把一次语言切换后的视觉更新当成所有状态都自动持久化的证据。验证记录应说明操作前后的展品标识和页面层级,若页面重建回到列表,先判断会话恢复要求,再决定是否属于资源缺陷。

给后续馆藏内容留下独立扩展点

未来接入多语言展品仓库时,可以让查询条件包含展品标识和内容语言,而路由继续只表达稳定实体及必要的展示条件。仓库负责缺少译文时的回退、内容版本与缓存失效;页面外壳仍从本地资源取得。这样断网时至少能解释当前状态,错误说明不依赖那条已经失败的内容请求。

资源审查也应建立两种不同记录。界面资源检查名称是否完整、引用是否可解析、语言是否匹配;馆藏内容检查文本是否属于正确展品、译文与版本是否一致、来源是否允许使用。两者都叫“文案”,但不能用同一份存在性检查代替。尤其是实体名称被翻译后,不应反向覆盖用于列表键和路由的标识。

最后,错误页面也要核对动作是否与文案一致。这里按钮执行的是当前目的页出栈,文字表达返回展品列表,成立的前提是本阶段列表直接打开详情。后续若允许详情继续打开更多详情,这句话就需要与返回策略一起复审。资源化不会自动保证语义正确,它只是把文字管理集中起来;行为变化仍然需要触发对应文案和验收用例的更新。

迁移完成后的代码比原来多了资源文件,却减少了显示文字与业务协议的耦合。第4篇将增加一个可见期间运行的讲解提示计时器,资源化的详情外壳会继续保留。那次变化处理的是页面何时工作、何时停止,与本篇处理的“显示什么文字”相互独立;把这些职责逐步分开,系列工程才能持续增加能力而不变成一团条件分支。

官方参考

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐