多语言与区域化: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_THvi_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。

注意: Android 7.0 之后,多语言支持改成了「多 Locale 列表」。用户可以选择多个语言,系统按优先级匹配。如果你的 App 只支持单语言切换,记得用 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 添加新字体

我做过一个印度市场的项目,需要支持泰卢固语。步骤其实就三步:

  1. 拿到字体文件:比如 NotoSansTelugu-Regular.ttf
  2. 放到 frameworks/base/data/fonts/ 目录下
  3. 修改 fonts.xml,添加一行:
<family lang="te">
    <font weight="400" style="normal">NotoSansTelugu-Regular.ttf</font>
</family>

然后重新编译系统镜像。嗯,听起来简单,但我第一次做的时候忘了改 Android.mk,字体文件根本没被打包进去。折腾了半天才发现。

核心原则: 字体 fallback 链要完整。中文、日文、韩文建议用同一个 CJK 字体文件(比如 NotoSansCJK),避免不同语言混排时风格不一致。

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 配置 字体替换 输入法集成 默认语言设置 资源覆盖 (values-xx) 运行时动态切换 fonts.xml 配置 Fallback 链管理 字体裁剪与性能 预装到系统分区 设置默认输入法 多语言键盘切换 三者协同:Locale 决定语言 → 字体提供显示能力 → 输入法支持用户输入 缺一不可,否则就会出现「显示方框」或「无法输入」的问题

五、避坑指南

最后,分享几个我踩过的坑:

  • Locale 大小写问题zh_CNzh-cn 在 Android 里是两回事。建议统一用小写 + 下划线格式。
  • 字体 fallback 顺序:如果两个字体都声明了同一个语言,系统会取第一个。我曾经把泰语字体放在中文后面,结果泰语用户看到的全是中文汉字。
  • 输入法权限:预装输入法一定要声明 BIND_INPUT_METHOD 权限,否则系统不认。我见过一个团队因为这个原因,输入法装上了但无法启用。
  • 多语言测试:别只在模拟器上测。真机上不同厂商的 locale 实现有差异。我建议至少准备 3 台不同品牌的设备做兼容性测试。

好了,多语言这块儿就聊到这儿。说白了,就是「配置要全、字体要够、输入法要稳」。把这三点做好了,你的 ROM 就能出海了。


公众号:蓝海资料掘金营,微信deep3321