多语言与区域化:Locale 配置、字体替换、输入法集成
说实话,做嵌入式Android系统开发,多语言这块儿经常被忽视。很多人觉得「不就是加几个翻译文件嘛」。嗯,真这么简单就好了。我当年第一次做海外市场的定制ROM时,就栽在了字体替换上——阿拉伯语显示全是方框,那叫一个尴尬。
今天咱们就聊聊,怎么在AOSP里把多语言和区域化这件事做扎实。核心就三块:Locale怎么配、字体怎么换、输入法怎么集成。
一、Locale 配置:不只是改个语言列表
先说说Locale。说白了,它就是告诉系统「我现在在哪个地区,用什么语言」。但实际配置起来,坑不少。
1.1 默认语言设置
在AOSP里,默认语言写在 build/target/product/languages_full.mk 里。你可能会看到这样的配置:
PRODUCT_LOCALES := en_US zh_CN zh_TW ja_JP ko_KR
这里有个细节——顺序决定优先级。排第一个的就是系统默认语言。我习惯把目标市场语言放最前面,比如做东南亚市场,就把 th_TH 或 vi_VN 提到首位。
PRODUCT_DEFAULT_LOCALE 单独指定。比如 PRODUCT_DEFAULT_LOCALE := zh_CN。这样其他语言虽然还在,但开机默认就是中文。
1.2 区域化资源覆盖
Locale 不只是语言,还有数字格式、日期格式、货币符号这些。Android 用 values-{locale} 目录来做资源覆盖。比如:
res/
values/
strings.xml # 默认英文
values-zh/
strings.xml # 简体中文
values-zh-rTW/
strings.xml # 繁体中文(台湾)
我曾经遇到过一个坑:某个App只翻译了 values-zh,没做 values-zh-rCN。结果在部分国产手机上,系统 locale 是 zh-CN,资源死活加载不到。嗯,建议统一用 values-zh 做中文兜底,再按需细分。
1.3 运行时切换Locale
有些场景需要动态切换语言,比如设置里的语言选择器。代码其实不复杂:
// 获取当前配置
Configuration config = getResources().getConfiguration();
config.setLocale(new Locale("zh", "CN"));
// 更新资源
getResources().updateConfiguration(config, getResources().getDisplayMetrics());
但注意——Activity 不会自动重建。你需要自己处理界面刷新。我一般会在 BaseActivity 里重写 attachBaseContext(),统一替换 Context 的 Locale。
LocaleList 来兼容。
二、字体替换:别让用户看到方框
字体替换,说白了就是「系统里没有这个字符的字体,怎么办?」。尤其是阿拉伯语、泰语、印地语这些复杂文字,缺字体就是满屏方块。
2.1 系统字体配置文件
AOSP 的字体配置在 frameworks/base/data/fonts/fonts.xml 里。这个文件定义了系统所有字体的加载顺序和 fallback 链。举个例子:
<family>
<font weight="400" style="normal">Roboto-Regular.ttf</font>
<font weight="700" style="normal">Roboto-Bold.ttf</font>
<alias name="sans-serif">Roboto</alias>
</family>
<family lang="zh-Hans">
<font weight="400" style="normal">NotoSansCJK-Regular.ttc</font>
</family>
看到 lang="zh-Hans" 了吗?这就是告诉系统:当 locale 是简体中文时,优先用 NotoSansCJK。如果这个字体里没有某个字符,系统会继续往下找 fallback 字体。
2.2 添加新字体
我做过一个印度市场的项目,需要支持泰卢固语。步骤其实就三步:
- 拿到字体文件:比如
NotoSansTelugu-Regular.ttf - 放到
frameworks/base/data/fonts/目录下 - 修改
fonts.xml,添加一行:
<family lang="te">
<font weight="400" style="normal">NotoSansTelugu-Regular.ttf</font>
</family>
然后重新编译系统镜像。嗯,听起来简单,但我第一次做的时候忘了改 Android.mk,字体文件根本没被打包进去。折腾了半天才发现。
2.3 字体压缩与性能
字体文件通常很大。一个完整的 NotoSansCJK 有 20 多 MB。对于嵌入式设备来说,这太奢侈了。我建议:
- 只保留需要的字符集:用
pyftsubset工具裁剪字体 - 使用 ttc 集合格式:多个字体打包成一个文件,节省 inode
- 按语言分拆:中文用中文子集,日文用日文子集,不要一股脑全塞进去
三、输入法集成:从 AOSP 到定制 ROM
输入法这块,AOSP 默认带了一个 LatinIME(英文键盘)。但你要做中文市场,肯定得换。我见过不少团队直接预装搜狗、百度输入法,但说实话,预装第三方输入法有坑。
3.1 预装输入法到系统分区
把输入法 APK 放到 packages/apps/ 目录下,然后在 device.mk 里添加:
PRODUCT_PACKAGES += \
MyInputMethod
但注意——输入法需要声明为系统级应用,否则无法设置为默认输入法。在 AndroidManifest.xml 里加上:
<uses-permission android:name="android.permission.BIND_INPUT_METHOD" />
我曾经犯过一个错:预装了一个输入法,但没在 settings secure 里设置默认值。结果用户开机后,系统提示「请选择输入法」,体验极差。
3.2 设置默认输入法
在 frameworks/base/packages/SettingsProvider/res/values/defaults.xml 里,可以设置默认输入法:
<string name="config_default_input_method">com.example.inputmethod/.MyInputMethodService</string>
或者通过 overlay 机制覆盖。我习惯在 device 目录下建一个 overlay:
device/mycompany/mydevice/overlay/frameworks/base/packages/SettingsProvider/res/values/defaults.xml
这样不改 AOSP 源码,干净利落。
3.3 输入法切换与多语言键盘
如果你的 ROM 支持多语言,输入法也得跟着切换。比如用户从中文切到阿拉伯语,键盘布局应该自动变。这需要输入法实现 onStartInputView() 时检查当前 locale。
我见过一个方案:在输入法里内置多个语言的键盘布局文件,根据 locale 动态加载。代码大致这样:
@Override
public View onCreateInputView() {
String locale = getCurrentInputEditorInfo().getLocale();
if (locale.startsWith("ar")) {
return new ArabicKeyboardView(this);
} else if (locale.startsWith("zh")) {
return new ChineseKeyboardView(this);
} else {
return new LatinKeyboardView(this);
}
}
嗯,这个方案简单粗暴,但有个问题——切换语言时键盘会闪一下。更好的做法是用同一个 View,只切换键盘布局数据。
四、知识体系总览
说了这么多,咱们用一张图把核心逻辑串起来:
五、避坑指南
最后,分享几个我踩过的坑:
- Locale 大小写问题:
zh_CN和zh-cn在 Android 里是两回事。建议统一用小写 + 下划线格式。 - 字体 fallback 顺序:如果两个字体都声明了同一个语言,系统会取第一个。我曾经把泰语字体放在中文后面,结果泰语用户看到的全是中文汉字。
- 输入法权限:预装输入法一定要声明
BIND_INPUT_METHOD权限,否则系统不认。我见过一个团队因为这个原因,输入法装上了但无法启用。 - 多语言测试:别只在模拟器上测。真机上不同厂商的 locale 实现有差异。我建议至少准备 3 台不同品牌的设备做兼容性测试。
好了,多语言这块儿就聊到这儿。说白了,就是「配置要全、字体要够、输入法要稳」。把这三点做好了,你的 ROM 就能出海了。
公众号:蓝海资料掘金营,微信deep3321