应用本地化
应用本地化:保留第四版正文机制,以所有者—状态—结果合同、事件轨迹和章专属故障完成可重放验收。
学习目标
- 能沿“应用本地化”的用户事件解释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(两字母,如 zh、en、ja、fr),地区代码遵循 ISO 3166-1(如 CN、TW、US),前面加 r 前缀。目录名必须严格小写。
<Term def="系统在所有匹配的资源限定符中选择最具体的一个——比如同时有 values-zh-rCN 和 values-zh,用户用中文(中国大陆)时会优先选前者,用户用中文(台湾)时因没有 zh-rTW 而退到 zh">资源匹配规则</Term> 遵循 "最具体匹配" 原则:系统根据用户设备当前的 <Term def="语言+地区的组合标识(如 zh-CN、en-US),决定了日期格式、数字格式、字符排序等文化相关行为">Locale</Term> 设置,在所有可用的资源目录中选择最匹配的那个。优先级从高到低依次是:语言+地区完全匹配 → 仅语言匹配 → 默认值。
Locale 、屏幕密度等)去所有候选限定符目录里挑 最具体匹配 的那个:西班牙语设备命中 values-es/strings.xml、超高密度屏命中 drawable-xxhdpi/;任何一项匹配不到就回退默认 values/。代码里 @string/app_name 始终不变——换语言只是多加一个目录。在 Android Studio 中添加语言
Android Studio 提供了 Translations Editor(翻译编辑器)——这是最高效的添加多语言方式。操作路径:
- 右键
res/values/strings.xml→ Open Translations Editor - 点击左上角的地球图标 → 选择要添加的语言(比如 Chinese (zh) 或 French (fr))
- 编辑器中会出现新的一列——直接在表格里翻译每条字符串
- 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. 准备默认的 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)
}
}public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 从 strings.xml 取文字
if (getSupportActionBar() != null) {
getSupportActionBar().setTitle(getString(R.string.title_main));
}
// 数字格式化
double price = 1234567.89;
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance();
String formattedPrice = currencyFormat.format(price);
tvPrice.setText(getString(R.string.label_price, formattedPrice));
// 日期格式化
Date today = new Date();
DateFormat dateFormat = DateFormat.getDateInstance(DateFormat.LONG);
String formattedDate = dateFormat.format(today);
tvDate.setText(getString(R.string.label_date, formattedDate));
// 带参数的字符串
int itemCount = 5;
tvCount.setText(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 = textint count = 3;
String text = getResources().getQuantityString(
R.plurals.photo_count, count, count
);
tvPhoto.setText(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辅助功能 →
章专属可重放状态实验
先预测“切换英语、中文、阿拉伯语、大字体与区域格式”发生后,Android Resources 与 locale 配置应怎样改变字符串、复数、日期格式、布局方向和候选资源;再操作三个实验。第四版示例与当前 Android 政策分别记录,实验不把新 API 名称倒填为原书内容。
实验一:所有者—状态—结果合同
选择任一正式目录节点和正常/边界场景,检查它是否真的进入本章状态合同。目录标题只有同时出现在解释、可视状态和交付证据中才算覆盖。
Owner · state · observable result
应用本地化:状态合同
让资源匹配器按 locale、方向、尺寸与密度选择可回退资源
验证场景
第四版正式目录节点
localization · 正常任务
17. Localization:固定 SDK、设备配置和初始状态,触发“切换英语、中文、阿拉伯语、大字体与区域格式”
冻结入口:17. Localization
记录Android Resources 与 locale 配置的初始字符串、复数、日期格式、布局方向和候选资源
观察:配置值、资源命中、伪本地化截图、RTL 与格式断言中的“17. Localization”轨迹
预期:由Android Resources 与 locale 配置提交字符串、复数、日期格式、布局方向和候选资源,并持续满足“用户可见文本来自资源和 locale 格式化,不拼接固定英语语序”
实验二:事件与生命周期轨迹
沿五次转换逐步执行“切换英语、中文、阿拉伯语、大字体与区域格式”。每一步只允许Android Resources 与 locale 配置按职责提交状态,并持续核对“用户可见文本来自资源和 locale 格式化,不拼接固定英语语序”。
Deterministic event replay
应用本地化:事件轨迹
不变量:用户可见文本来自资源和 locale 格式化,不拼接固定英语语序
交付证据:配置值、资源命中、伪本地化截图、RTL 与格式断言
实验三:章专属反例与同输入恢复
注入“把日期和数量手工拼进英文句子,阿拉伯语下语序和数字错误”,保存第一个偏离点;撤销后以完全相同的 SDK、设备状态和用户事件重放。只有配置值、资源命中、伪本地化截图、RTL 与格式断言一起恢复才算修复。
Fault · cancel · restore
应用本地化:反例与恢复
故障:把日期和数量手工拼进英文句子,阿拉伯语下语序和数字错误
第 1 次使用相同 SDK、设备配置、初始状态与用户事件
保持正常输入不变,仅注入“把日期和数量手工拼进英文句子,阿拉伯语下语序和数字错误”
用户可见文本来自资源和 locale 格式化,不拼接固定英语语序
配置值、资源命中、伪本地化截图、RTL 与格式断言