应用本地化

学会将App界面翻译成多语言、用资源限定符适配不同语言环境、格式化数字和日期、以及支持从右到左的布局。

学习目标

  • 能沿“应用本地化”的用户事件解释Android组件、状态所有者、线程与销毁边界。
  • 能围绕“学会将App界面翻译成多语言、用资源限定符适配不同语言环境、格式化数字和日期、以及支持从右到左的布局。”改出一个可运行结果,并用前后状态而非组件数量验收。
  • 能在旋转、进程重建、拒权、离线或无效输入中选择适用反例,定位首个状态分叉。
  • 能用双视口、大字体、焦点顺序、状态语义和对比度截图独立重放结论,并标明第四版机制与现代targetSdk政策的边界。

为什么需要应用本地化

想象你开发了一个记账 App,界面全是中文。一个巴西用户下载了它——他看不懂 "添加支出" 四个字,也不知道 "月收入" 是什么意思。他只给你打了一星差评,然后卸载。

不做本地化,你的 App 就自动放弃了所有非母语用户。Android 设备遍布全球 190+ 个国家——Google Play 上排名靠前的 App 无一例外都支持多语言。好消息是 Android 的资源框架从第一天开始就为多语言做了设计——你不需要改一行代码,只需要把字符串翻译一份放到对应目录。

工厂流水线来类比:你的 Kotlin 代码是流水线机器本身——它只管逻辑(按钮点下去做什么)。而 strings.xml 是机器上可以随时替换的铭牌——中文铭牌装在中文产线,英文铭牌装在出口产线。机器不变,铭牌换一块就行。

资源限定符是怎么工作的

目录命名规则

Android 通过资源目录名称的后缀来区分不同配置。语言限定符的格式是 values-语言代码,如果需要区分地区则是 values-语言代码-r地区代码

res/
  values/              ← 默认(fallback)资源
    strings.xml
  values-zh/           ← 中文(任何地区)
    strings.xml
  values-zh-rCN/       ← 中文(中国大陆)
    strings.xml
  values-zh-rTW/       ← 中文(台湾)
    strings.xml
  values-en/           ← 英文(任何地区)
    strings.xml
  values-en-rUS/       ← 英文(美国)
    strings.xml
  values-ja/           ← 日语
    strings.xml
  values-fr/           ← 法语
    strings.xml

语言代码遵循 ISO 639-1(两字母,如 zhenjafr),地区代码遵循 ISO 3166-1(如 CNTWUS),前面加 r 前缀。目录名必须严格小写。

<Term def="系统在所有匹配的资源限定符中选择最具体的一个——比如同时有 values-zh-rCN 和 values-zh,用户用中文(中国大陆)时会优先选前者,用户用中文(台湾)时因没有 zh-rTW 而退到 zh">资源匹配规则</Term> 遵循 "最具体匹配" 原则:系统根据用户设备当前的 <Term def="语言+地区的组合标识(如 zh-CN、en-US),决定了日期格式、数字格式、字符排序等文化相关行为">Locale</Term> 设置,在所有可用的资源目录中选择最匹配的那个。优先级从高到低依次是:语言+地区完全匹配 → 仅语言匹配 → 默认值。

资源限定符:运行时按设备配置自动选目录,匹配不到就回退默认代码里只写 @string/app_name —— 选哪个目录由系统在运行期按配置决定res/values/strings.xml默认 · 无匹配时回退到这里values-zh-rCN/strings.xml中文(中国大陆)values-es/strings.xml西班牙语 · 与设备 Locale 命中✓ 选中drawable-hdpi/strings.xml高密度图片资源drawable-xxhdpi/strings.xml超高密度 · 与设备密度命中✓ 选中设备当前配置Localees(西班牙语)密度xxhdpi系统据此挑限定符目录匹配最佳限定符 → 命中都不匹配 → 回退默认限定符匹配优先级(最具体匹配优先)语言+地区完全匹配(values-es-rES) > 仅语言匹配(values-es) > 默认(values)• 设备 Locale=es 时优先命中 values-es/;连默认都缺该条目才会报缺资源• 改语言只新增一个限定符目录,代码与 layout 的 @string 引用一行都不用动
系统在运行期拿设备配置( Locale 、屏幕密度等)去所有候选限定符目录里挑 最具体匹配 的那个:西班牙语设备命中 values-es/strings.xml、超高密度屏命中 drawable-xxhdpi/;任何一项匹配不到就回退默认 values/。代码里 @string/app_name 始终不变——换语言只是多加一个目录。

在 Android Studio 中添加语言

Android Studio 提供了 Translations Editor(翻译编辑器)——这是最高效的添加多语言方式。操作路径:

  1. 右键 res/values/strings.xmlOpen Translations Editor
  2. 点击左上角的地球图标 → 选择要添加的语言(比如 Chinese (zh) 或 French (fr))
  3. 编辑器中会出现新的一列——直接在表格里翻译每条字符串
  4. AS 自动在 res/ 下创建 values-zh/strings.xml,只包含翻译过的条目

Translations Editor 还会自动标记哪些字符串还没有翻译(显示为红色),以及哪些翻译与默认值完全相同(可能是忘了翻译)。

strings.xml 里的字符串应该怎么写

所有用户可见的文字都放进 strings.xml,永远不要在代码或布局 XML 里硬编码:

<!-- res/values/strings.xml(默认,通常是英文) -->
<resources>
    <string name="app_name">ExpenseTracker</string>
    <string name="add_expense">Add Expense</string>
    <string name="monthly_summary">Monthly Summary</string>
    <string name="delete_confirm">Are you sure you want to delete this entry?</string>
    <string name="total_amount">Total: ¥%1$s</string>
</resources>
<!-- res/values-zh/strings.xml(中文翻译) -->
<resources>
    <string name="app_name">记账助手</string>
    <string name="add_expense">添加支出</string>
    <string name="monthly_summary">月度汇总</string>
    <string name="delete_confirm">确定要删除这条记录吗?</string>
    <string name="total_amount">合计:¥%1$s</string>
</resources>

注意 %1$s 这种带格式参数的字符串用法——%1 表示第一个参数,$s 表示字符串类型。不同语言的语序可能完全不同,用位置参数让翻译者可以自由调整顺序。比如中文 "合计:¥123" 和英文 "Total: ¥123" 语序一致,但日语可能是 "合計:123円"。

动手:给一个 App 加上中英文支持

猜一猜:如果你只加了 values-zh/strings.xml 但没有加 values-en/strings.xml,系统语言设为英文时 App 会显示什么?

分步1 / 3

1. 准备默认的 strings.xml

假设你有一个简单的 App,只有一个主页面显示问候语和按钮。先在 res/values/strings.xml 中写好默认(英文)字符串:

<resources>
    <string name="app_name">My App</string>
    <string name="greeting">Hello, welcome!</string>
    <string name="btn_send">Send</string>
    <string name="btn_cancel">Cancel</string>
</resources>

布局文件 activity_main.xml 通过 @string/greeting 引用字符串——代码完全不碰文字内容:

 

代码逐段拆解

下面拆解一个完整的支持多语言 + 本地化格式的 Activity。

先定义好所有字符串资源:

<!-- res/values/strings.xml -->
<resources>
    <string name="label_price">Price: %1$s</string>
    <string name="label_date">Date: %1$s</string>
    <string name="title_main">My Expenses</string>
    <string name="btn_add">Add</string>
    <string name="item_count">You have %1$d items</string>
</resources>
<!-- res/values-zh/strings.xml -->
<resources>
    <string name="label_price">价格:%1$s</string>
    <string name="label_date">日期:%1$s</string>
    <string name="title_main">我的支出</string>
    <string name="btn_add">添加</string>
    <string name="item_count">你有 %1$d 笔记录</string>
</resources>

然后是 Activity 的 Kotlin 代码——数字和日期都用 Locale 感知的方式格式化:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        // 从 strings.xml 取文字——永远不硬编码
        supportActionBar?.title = getString(R.string.title_main)
 
        // 数字用 NumberFormat 按当前 Locale 格式化
        val price = 1234567.89
        val currencyFormat = NumberFormat.getCurrencyInstance()
        val formattedPrice = currencyFormat.format(price)
        // 中文环境:¥1,234,567.89  英文环境:$1,234,567.89
        tvPrice.text = getString(R.string.label_price, formattedPrice)
 
        // 日期用 DateFormat 按当前 Locale 格式化
        val today = Date()
        val dateFormat = DateFormat.getDateInstance(DateFormat.LONG)
        val formattedDate = dateFormat.format(today)
        // 中文环境:2024年3月15日  英文环境:March 15, 2024
        tvDate.text = getString(R.string.label_date, formattedDate)
 
        // 带参数的字符串
        val itemCount = 5
        tvCount.text = getString(R.string.item_count, itemCount)
    }
}

NumberFormat.getCurrencyInstance() 不带参数时自动使用用户设备的当前 Locale——中文设备显示 ¥,美国设备显示 $,日本设备显示 。同理 getNumberInstance() 处理千位分隔符的差异。

DateFormat.getDateInstance(DateFormat.LONG) 根据 Locale 决定日期格式——中文 "2024年3月15日"、英文 "March 15, 2024"、德语 "15. März 2024"。不需要你手动拼接字符串。

RTL 布局支持: 阿拉伯语、希伯来语等语言是从右到左书写的。用 start/end 替代 left/right,系统自动根据语言方向翻转布局:

<!-- 所有支持 RTL 的 App 都应该这样写 -->
 
<!-- 而不是这样 -->
<!-- android:layout_marginLeft="16dp"  ← LTR 语言没问题,RTL 语言就反了 -->

复数字符串(Quantity Strings):不同语言的复数规则完全不同。中文 "1张照片" 和 "5张照片" 说法一样;英文 "1 photo" 和 "5 photos" 不同;俄语甚至有四种形式。用 <plurals> 而非 if-else:

<!-- res/values/strings.xml(英文) -->
<plurals name="photo_count">
    <item quantity="one">%d photo</item>
    <item quantity="other">%d photos</item>
</plurals>
 
<!-- res/values-zh/strings.xml(中文) -->
<plurals name="photo_count">
    <item quantity="other">%d 张照片</item>
</plurals>
 
<!-- res/values-ru/strings.xml(俄语,有四种形式) -->
<plurals name="photo_count">
    <item quantity="one">%d фотография</item>
    <item quantity="few">%d фотографии</item>
    <item quantity="many">%d фотографий</item>
    <item quantity="other">%d фотографии</item>
</plurals>

代码中通过 resources.getQuantityString() 使用复数字符串——系统自动根据数量选择正确的形式:

val count = 3
val text = resources.getQuantityString(
    R.plurals.photo_count, count, count
)
// 英文:3 photos  中文:3 张照片  俄语:3 фотографии
tvPhoto.text = text

容易踩的坑

小结

  • 资源限定符 values-语言代码 让系统自动选择最匹配的字符串资源
  • Android Studio 的 Translations Editor 可视化管理和翻译所有字符串
  • NumberFormat/DateFormat 按当前 Locale 自动格式化数字和日期——不用手写格式串
  • start/end 替代 left/right,一行代码不改就能兼容 RTL 语言
  • 复数字符串用 <plurals> 而非 if-else 拼接——不同语言有不同的复数规则

练习

问题 1:“第17章 Localization”覆盖哪些正式节点和项目主线?

问题 2:怎样建立本页最小可执行实验?

问题 3:为什么只在正常点击路径运行不能证明完成?

问题 4:怎样设计能推翻当前实现的反例?

问题 5:从第4版语境迁移到现代目标SDK时如何控制变量?

问题 6:本页达到独立交接标准需要什么?

名词解释

本章出现的专业名词,用大白话再讲一遍。

Locale

语言+地区的组合标识(如 zh-CN、en-US)。决定了日期格式、数字格式、货币符号、字符排序规则等文化相关的行为。通过 Locale.getDefault() 获取当前系统设置。

资源匹配规则

系统在所有匹配的资源限定符中选择最具体的一个——比如同时有 values-zh-rCN 和 values-zh,用户用中文(中国大陆)时会优先选前者,用户用中文(台湾)时因没有 zh-rTW 而退到 zh。原则是"越具体越优先"。

Configuration 变化

当设备配置(语言、屏幕方向、屏幕尺寸等)发生变化时,Android 系统默认会销毁并重新创建 Activity,以便加载与新配置匹配的资源。可以通过重写 onConfigurationChanged 或使用 ViewModel 来优化处理。

资源限定符

Android 资源目录名中的后缀(如 -zh 表示中文、-land 表示横屏、-xhdpi 表示超高密度屏幕),告诉系统在特定设备配置下使用对应目录里的资源。多个限定符可以用 - 连接,如 values-zh-rCN-land。

RTL(从右到左)

Right-to-Left 的缩写。阿拉伯语、希伯来语、波斯语等语言的书写和阅读方向。Android 通过 start/end 属性和 android:supportsRtl 支持自动镜像布局。

“应用本地化”不使用未获授权的纸书正文;InformIT出版信息授权电子版完整目录只用于确认第四版32章、269个正式目录节点和时代语境,第四版官方勘误用于识别工具链变更。下列中文解释、图示、交互、代码与练习均为独立教学重写,平台行为再以Android Developers的一手文档复核。

为什么“应用本地化”必须回到可观察状态

“应用本地化”的学习结果不是记住类名,而是能预测“学会将App界面翻译成多语言、用资源限定符适配不同语言环境、格式化数字和日期、以及支持从右到左的布局。”在一次输入、一次重建和一次失败中的不同状态,并指出哪条Android合同产生差异。

第四版机制逐项深读

17. Localization

在“应用本地化”中,“17. Localization”让资源匹配器根据locale与配置选择候选;字符串参数、复数、日期、RTL和回退链必须由目标locale实测,不能拼接英语语序。

Localizing Resources

在“应用本地化”中,“Localizing Resources”的复用不能吞掉状态语义;pressed、focused、disabled与checked都要有可辨反馈。

Configuration Qualifiers

在“应用本地化”中,“Configuration Qualifiers”让资源匹配器根据locale与配置选择候选;字符串参数、复数、日期、RTL和回退链必须由目标locale实测,不能拼接英语语序。

Testing Alternative Resources

在“应用本地化”中,“Testing Alternative Resources”通过资源限定符和主题属性把内容与设备配置解耦;最终值由资源匹配和主题继承共同决定。

For the More Curious: More on Determining Device Size

在“应用本地化”中,“For the More Curious: More on Determining Device Size”用于推翻“学会将App界面翻译成多语言、用资源限定符适配不同语言环境、格式化数字和日期、以及支持从右到左的布局。”的顺利路径:先写预期,再引入一个边界输入、重建或平台差异,并用状态、日志与用户结果解释首个分叉。

Challenge: Localizing Dates

在“应用本地化”中,“Challenge: Localizing Dates”让资源匹配器根据locale与配置选择候选;字符串参数、复数、日期、RTL和回退链必须由目标locale实测,不能拼接英语语序。

“应用本地化”验收回顾

“应用本地化”只有在双视口、大字体、焦点顺序、状态语义和对比度截图能够从相同基线再次得到相同断言时才通过;第四版机制与现代平台政策分别记录,不用新API名称掩盖旧行为。

← 上一页:使用intent拍照 · 下一页:Android辅助功能 →

讨论

评论区加载中…