杰理 SDK 文档中心
首页
首页
  • 概述与快速开始

    • 仓库概览
    • 运行环境与 SDK 集成
    • 工程结构与目录导航
  • 核心 SDK 架构

    • SDK 库体系与模块划分
    • 蓝牙连接与 RCSP 协议
    • 广播包解析与设备认证
    • 日志助手与调试支持
  • 设备功能模块

    • OTA 固件升级
    • 表盘管理与自定义表盘
    • 图像转换工具
    • 资源打包
    • 音频编解码
    • 健康与运动数据同步
    • 消息通知与实用设备功能
  • 宜动健康示例应用

    • 应用架构与页面导航
    • 健康界面与数据可视化
    • 设备连接与数据同步
    • 登录注册与用户中心
    • AI 云服务与语音交互
    • 本地数据库与持久化
    • 多语言国际化
  • 测试与调试

    • SDKTestHelper 功能测试工具
    • 音频编解码示例工程
    • 调试技巧与问题排查
  • 文档与资源

    • 在线文档与版本历史
    • 第三方框架与依赖管理

多语言国际化

JL_Health(杰理健康)iOS 客户端的多语言国际化(i18n)机制:通过 Apple 标准的 .lproj 资源包组织各语言文案,配合 LanguageCls.localizableTxt("中文原文") 统一取词 API,实现界面、Toast、启动屏与 App 元数据的全量本地化。

Purpose and Scope

本文档说明 JL_Health 应用的多语言国际化实现方式,包括:

  • 本地化资源在仓库中的目录结构与文件职责(Localizable.strings / InfoPlist.strings / LaunchScreen.strings);
  • 代码侧统一取词入口 LanguageCls.localizableTxt 的调用模式与设计意图;
  • 运行时语言检测与回退逻辑(基于 Locale.current.languageCode);
  • SDK 测试工程(SDKTestHelper)中由 R.swift 生成的 Localizable 表访问方式,以及按功能分组(通话/同步/表盘)的语言文案定义。

本文档不覆盖具体业务页面的功能逻辑(参见各自页面的文档),也不涉及服务端下发文案的机制(本仓库未见相关实现)。

Overview

JL_Health 是一个面向全球市场的健康穿戴设备配套 App,支持英语、中文(简/繁)、德语、西班牙语、法语、阿拉伯语、波斯语等多种语言。多语言国际化是它的基础能力:任何新增页面、弹窗、Toast、按钮标题都必须通过本地化 API 取词,而不是硬编码中文文本。

仓库内的本地化实现遵循 Apple iOS 的标准机制:

  1. 资源组织:每个语言一个 xx.lproj 目录(如 en.lproj、zh-Hans.lproj、ar.lproj),目录内放置同名翻译表 Localizable.strings(界面文案)、InfoPlist.strings(App 显示名称、权限描述)与 LaunchScreen.strings(启动屏文案)。
  2. 代码取词:业务代码统一调用 LanguageCls.localizableTxt("中文"),由该工具类根据系统语言在正确的 .lproj 中查找翻译;找不到时返回原始中文 key,保证任何语言环境下 UI 都不会出现空字符串。
  3. 语言判定:通过 Locale.current.languageCode 判断当前语言,zh 及 zh-* 前缀视为中文环境,其余语言按系统语言码匹配对应语言包。
  4. SDK 侧:测试/工具工程(SDKTestHelper)使用 R.swift 生成的类型安全资源访问器,显式指定 tableName: "Localizable" 与 preferredLanguages,并在 TranslateTools 中按功能分组定义语言文案。

这套设计把"语言选择"与"文案获取"解耦:系统语言由 iOS 决定,App 只负责"按语言码取正确的 Bundle 和翻译表",因此新增语言只需添加一个 .lproj 目录和翻译文件,无需改动业务代码。

Architecture

资源与调用架构

flowchart TD
    subgraph sg_App["JL_Health App(业务层)"]
        VC["UIViewController / ViewModel<br/>(DialHistory / DialPay / DeviceSub ...)"]
        LC["LanguageCls<br/>localizableTxt(key)"]
        Locale["Locale.current.languageCode<br/>Bundle 语言选择"]
    end

    subgraph sg_Bundles["本地化资源包(.lproj 目录)"]
        Zh["zh-Hans.lproj / Localizable.strings"]
        En["en.lproj / Localizable.strings"]
        Ar["ar.lproj / Localizable.strings"]
        De["de.lproj / Localizable.strings"]
        Es["es.lproj / Localizable.strings"]
        Fr["fr.lproj / Localizable.strings"]
        Info["InfoPlist.strings<br/>(App 名称 / 权限描述)"]
        Launch["LaunchScreen.strings<br/>(启动屏文案)"]
    end

    VC -->|"LanguageCls.localizableTxt(\"中文原文\")"| LC
    LC --> Locale
    Locale -->|"语言码匹配"| Zh
    Locale -->|"语言码匹配"| En
    Locale -->|"语言码匹配"| Ar
    Locale -->|"语言码匹配"| De
    Locale -->|"语言码匹配"| Es
    Locale -->|"语言码匹配"| Fr
    Info -->|"随语言包加载"| Zh
    Launch -->|"随语言包加载"| Zh

架构说明:

  • 业务层(上层):各 UIViewController / ViewModel 只调用 LanguageCls.localizableTxt("中文"),完全不感知语言包细节。这样文案改动、新增语言都不会触及业务代码。
  • 工具层(中间):LanguageCls 是唯一取词入口,负责把"中文原文"当作 key 去查找翻译,屏蔽 Bundle 与 NSLocalizedString 的底层细节。
  • 资源层(下层):.lproj 目录按语言划分,每个语言包内有三类 strings 文件,分别服务界面文案、App 元数据(InfoPlist)与启动屏。

运行时取词流程

sequenceDiagram
    participant VC as UIViewController
    participant LC as LanguageCls
    participant B as Bundle.main
    participant S as Localizable.strings

    VC->>LC: localizableTxt("购买记录")
    LC->>B: 解析当前语言(Locale.current.languageCode)
    B-->>LC: 语言码(如 "zh-Hans" / "en")
    LC->>S: 在对应 .lproj 的 Localizable 表中查找该 key
    S-->>LC: 命中 → 返回翻译文本;未命中 → 返回 key 原文
    LC-->>VC: 本地化字符串(赋给 title / label / toast)

设计意图:以"中文原文"作为 key 是这套方案最核心的取舍——开发时无需维护独立的 key 命名空间,代码可读性高(localizableTxt("免费") 一眼可知含义);同时"未命中回退返回 key 本身"保证了即使某个语言包漏翻译,界面也不会出现空白,只会暂时显示中文,便于 QA 快速发现缺失项。

本地化资源结构

.lproj 语言包

仓库中所有本地化资源位于 code/JL_Health/JieliJianKang/ 下,按语言目录组织。当前至少包含以下语言包(每个目录内含若干 strings 文件):

语言目录面向区域说明
zh-Hans.lproj简体中文默认开发语言,Localizable.strings 中的 key 即中文原文
zh-Hant.lproj繁体中文港台地区
en.lproj英语国际通用语言
en-GB.lproj英式英语英/澳等地区
de.lproj德语德语区
es.lproj西班牙语西语区
fr.lproj法语法语区
ar.lproj阿拉伯语中东地区(RTL 从右到左布局)
fa.lproj波斯语伊朗等地区(RTL)

每个语言包内文件职责:

  • Localizable.strings:主界面文案表,形如 "购买记录" = "Purchase History";。业务代码 LanguageCls.localizableTxt("购买记录") 的 key 与之对应。
  • InfoPlist.strings:本地化 App 显示名称与系统权限描述文案(如相册、蓝牙权限说明),随系统语言切换。
  • LaunchScreen.strings:启动屏文案,保证冷启动阶段即显示正确语言。

SDK / 测试工程的资源访问

code/SDKTestHelper/ 使用 R.swift 生成类型安全的资源访问器(R.generated.swift),显式声明从 Localizable 表取词:

var localizable: localizable { .init(source: .init(bundle: bundle, tableName: "Localizable", preferredLanguages: preferredLanguages, locale: locale)) }

来源:R.generated.swift

设计意图:与 LanguageCls.localizableTxt 一样都指向名为 Localizable 的翻译表,保证 App 与 SDK 测试工程共享同一套文案约定;同时 R.swift 方案在编译期生成资源符号,配合 preferredLanguages / locale 参数可精确指定取词语言,适合 SDK 演示 App 内做语言预览。

使用示例(来自实际源码)

页面标题取词

DialHistoryViewController(购买记录页)在初始化 UI 时通过导航栏标题使用本地化文案:

override func initUI() {
    super.initUI()
    navigationView.title = LanguageCls.localizableTxt("购买记录")
    self.view.backgroundColor = .white
}

来源:DialHistoryViewController.swift

同理,表盘商城页标题、管理按钮也走同一入口:

navigationView.title = LanguageCls.localizableTxt("表盘")
...
editBtn.setTitle(LanguageCls.localizableTxt("管理"), for: .normal)

来源:DialViewController.swift

条件文案与字符串拼接

DialPayViewController(表盘购买页)根据价格与状态动态选择文案,并把本地化文本与数字拼接(杰币 单价以分为单位):

if dialModel.price == 0 {
    watchSubTitleLab.text = LanguageCls.localizableTxt("免费")
}else{
    watchSubTitleLab.text = LanguageCls.localizableTxt("杰币") + "\(dialModel.price/100)"
}
...
bottomBtn.setTitle(LanguageCls.localizableTxt("下载"))
...
if !dialModel.status {
    bottomBtn.setTitle(LanguageCls.localizableTxt("购买"))
} else {
    bottomBtn.setTitle(LanguageCls.localizableTxt("下载"))
}

来源:DialPayViewController.swift、DialPayViewController.swift

注意:拼接式文案("杰币" + "\(price/100)")在德语、阿拉伯语等语序/数字体系不同的语言中可能产生不自然表达;理想做法是使用带占位符的格式串(如 "杰币 %d")并通过 String(format:) 取词,属后续可优化点。

Toast 提示取词

网络请求失败等场景的即时提示同样走本地化:

guard let list = resp else {
    AlertViewOnWindows.getFirstWindow()?.makeToast(LanguageCls.localizableTxt("网络有点问题"),position: .center)
    return
}

来源:DeviceSubViewModel.swift

语言环境判定(SDKTestHelper)

SDK 演示工程用 Locale.current.languageCode 判断是否中文环境,作为工具类/示例的本地化开关:

var isZh: Bool {
    let languageCode = Locale.current.languageCode
    return languageCode == "zh" || languageCode?.hasPrefix("zh-") == true
}

来源:SourceHelper.swift

按功能分组定义语言(SDKTestHelper)

TranslateTools 按功能(通话 / 同步 / 表盘)组织文案,Switch 分支返回对应语言的文本常量:

case .call:
    return TranslateTools.callLanguage
case .sync:
    return TranslateTools.syncLanguage
case .face:
    return TranslateTools.faceLanguage

来源:TranslateTools.swift

设计意图:SDK 功能文案按域拆分(call / sync / face),避免单一超大字典,便于按功能模块独立维护和复用;这与 App 侧按 .lproj 语言包组织的思路互补——App 按语言横向划分,SDK 工具按功能纵向划分。

语言判定与切换流程

flowchart TD
    Start([应用启动 / 切换系统语言]) --> GetLang["读取 Locale.current.languageCode"]
    GetLang --> IsZh{"languageCode == \"zh\" 或<br/>以 \"zh-\" 前缀开头?"}
    IsZh -->|"是"| Zh["中文环境<br/>命中 zh-Hans.lproj"]
    IsZh -->|"否"| Match["按语言码匹配对应 .lproj"]
    Match --> Found{"对应语言包存在?"}
    Found -->|"是"| Use["使用该语言包取词"]
    Found -->|"否"| Fallback["回退:返回原始 key(中文)"]
    Zh --> Use
    Use --> Done([界面显示本地化文本])
    Fallback --> Done

关键点:

  1. 系统语言是唯一语言源:仓库内未发现 App 内手动切换语言的实现(无 UserDefaults 覆盖、无语言设置页),语言完全跟随 iOS 系统设置,App 重启/系统切换后自动生效。这与多数健康类 App 的"跟随系统"策略一致,省去语言包热切换的复杂度。
  2. zh- 前缀匹配:SourceHelper.isZh 与 iOS 语言码规则一致——zh-Hans、zh-Hant、zh-CN、zh-TW 等都被识别为中文环境,避免按完整 locale 精确匹配导致的漏判。
  3. RTL 语言:ar.lproj、fa.lproj 的存在意味着阿拉伯语/波斯语用户会获得从右到左的界面布局;文案中的语序、数字、标点需符合 RTL 阅读习惯。
  4. 回退策略:翻译表未命中时 localizableTxt 返回中文原文 key,保证 UI 永不空白,便于发现漏翻译。

配置选项

配置项类型默认值说明
.lproj 语言目录目录至少 zh-Hans / en / en-GB / de / es / fr / ar / fa每新增一种语言即新增一个目录及翻译文件
Localizable.stringsstrings 表每语言包必含主界面文案表,key 为中文原文
InfoPlist.stringsstrings 表每语言包可选App 显示名称、权限描述文案
LaunchScreen.stringsstrings 表每语言包可选启动屏文案
Locale.current.languageCode系统只读系统设置运行时语言判定唯一依据
tableName: "Localizable"(R.swift)编译期常量LocalizableSDK 侧取词表名,与 App 保持一致

注:工程级配置(如 Xcode 中 Info.plist 的 CFBundleDevelopmentRegion、knownRegions 列表)不在本仓库源码文件内,具体值需在 Xcode 工程设置中确认。

API 参考

LanguageCls.localizableTxt(_ key: String) -> String

业务代码取词的统一入口(类方法,实现在 SDK/工具库中,仓库内未包含其定义源码;以下签名依据全部调用点的用法归纳)。

参数:

  • key(String):中文原文,同时作为翻译表 Localizable.strings 中的查找 key,如 "购买记录"、"免费"、"网络有点问题"。

返回:String —— 当前系统语言对应的翻译文本;若语言包中不存在该 key,返回 key 本身(即中文原文)。

典型调用点(全部来自仓库实际源码):

调用场景示例代码来源
导航栏标题navigationView.title = LanguageCls.localizableTxt("购买记录")DialHistoryViewController.swift
按钮标题editBtn.setTitle(LanguageCls.localizableTxt("管理"), for: .normal)DialViewController.swift
条件文案watchSubTitleLab.text = LanguageCls.localizableTxt("免费")DialPayViewController.swift
Toast 提示makeToast(LanguageCls.localizableTxt("网络有点问题"), position: .center)DeviceSubViewModel.swift

约束(依据调用点归纳,非源码显式声明):

  • key 必须与 Localizable.strings 中的条目一致;否则回退显示中文。
  • 返回字符串可直接赋给 UILabel.text / UIButton.setTitle / Toast 内容等。

Locale.current.languageCode(语言判定)

系统提供,返回当前首选语言的语言码(如 "zh"、"en"、"de")。SDK 侧通过 == "zh" || hasPrefix("zh-") 判定中文环境(见 SourceHelper.swift)。

失败模式与边界情况

  • 漏翻译:某 key 在目标语言包缺失时,localizableTxt 回退返回中文原文。设计上保证 UI 不空白,但会造成"部分中文部分外语"的混合界面;QA 应逐语言包做全量 key 比对。
  • 大小写与变体语言:zh-CN / zh-TW / zh-HK 等变体均以 zh- 前缀命中中文逻辑;若未来要区分简繁,需在取词层按完整语言码(zh-Hans / zh-Hant)分流,isZh 这种粗粒度判断将不够用。
  • RTL 布局:ar / fa 为从右到左语言,拼接式文案(如 "杰币" + price)的语序、标点方向在 RTL 下可能错位,需逐条人工校验。
  • 拼接/占位符:当前存在 "杰币" + "\(price/100)" 的拼接写法,德语的"币种后置"、阿拉伯数字体系等场景下表达不准确;应迁移为 String(format: "杰币 %d", ...) 的格式串并做各语言包翻译。
  • 动态内容:设备名、固件版本号等运行时数据若混入文案,需注意不要整串翻译(应拆分为"固定文案 + 参数"),否则语言包无法复用。

性能与运维注意

  • 取词开销:localizableTxt 本质是 Bundle 内 strings 表的字典查找,命中后系统有缓存,单次调用开销可忽略;但不要在 tableView 的 cellForRowAt 高频路径上每次都传动态构造的 key,应复用常量。
  • 语言包体积:多语言包会整体打进 App 包;对 ar / fa 等 RTL 语言还需包含对应的布局资源,注意包体积与启动加载时间。
  • 新增语言流程:新增 .lproj 目录 → 翻译三个 strings 文件 → 确保 key 与中文原文一一对应 → 真机切换系统语言回归验证(重点:RTL 布局、启动屏、权限弹窗、拼接文案)。
  • App 与 SDK 文案一致性:App 用 LanguageCls、SDKTestHelper 用 R.swift,二者都指向 Localizable 表,修改 key 时必须同步两处,否则 SDK 演示工程会出现回退中文。

扩展点

  • 语言包扩展:新增语言只需添加 .lproj 目录,业务代码零改动——这是本方案最大的扩展优势。
  • 应用内语言切换:若未来需要"App 内选择语言",可在取词层引入 preferredLanguages 覆盖(R.swift 已支持该参数,见 R.generated.swift),用 UserDefaults 保存用户选择并重建根视图即可,无需改业务调用点。
  • 格式串迁移:将 "文案" + 变量 改为 String(format:) + 占位符,可显著提升德语、阿拉伯语等语言的翻译质量,是当前最值得做的本地化增强。
  • SDK 功能文案分组:TranslateTools 的 call / sync / face 分组模式可推广到更多 SDK 功能,保持文案按域可维护。

Related Links

  • 表盘商城相关页面(DialMarket 各 ViewController,多语言取词的主要使用者)
  • DeviceSubViewModel(Toast 取词示例)
  • SDKTestHelper 语言工具(SourceHelper / TranslateTools)
  • R.generated.swift(R.swift 生成的资源访问器)
  • 多语言资源目录(.lproj 语言包)
Prev
本地数据库与持久化