安卓开发中繁体中文资源文件夹如何命名?

1,192 阅读7分钟

主要结论:仅考虑 Android 7.0 及以上的情况

  • 简体中文放 values-b+zh+Hans
  • 繁体中文放 values-b+zh+Hant
  • 各地区定制语言放指定地区的文件夹,比如台湾 values-b+zh+Hant+TW

明确问题

  1. 东南亚国家将中文作为官方语言的国家虽然不少,但总体上仅有简体中文繁体中文两种语言。
  2. 安卓系统为繁体中文(xx)时,获取 APP 多语言资源时不会取 values-zh 中的资源,为什么?
  3. 安卓系统为繁体中文(台湾)时,获取 APP 多语言资源时能匹配到 zh-rHK 中的资源,为什么?

安卓系统的字符串资源匹配策略

语言和语言区域解析概览 的描述有点含糊不清, 我们可以确定的信息是: Android 7.0 及以上语言匹配时会把相同语言的其他地区加入备选项。

比如:用户设置 fr_CH, APP 中有 fr_FRen_US,系统会依次尝试:

  1. fr_CH ❌
  2. fr ❌
  3. fr_FR ✅

诶,按照这里的逻辑,那么如果用户设置 zh-TW, APP 中有 zh-CNen_US,系统不是应该按如下顺序匹配到简体中文吗?

  1. zh-TW ❌
  2. zh x ❌
  3. zh-CN ✅

代码测试

系统语言设置为繁体中文(台湾),logo 默认前景色为 ic_launcher_foreground.xml 颜色默认白色,变体为绿色,修改语言变体资源文件夹名称,查看 logo 颜色如何变化。

  1. ic_launcher_foreground.xml 复制到 drawable-zh 中,并修改颜色为绿色

    发现系统未匹配 drawable-zh 文件夹中的资源 系统未匹配 drawable-zh 文件夹中的资源

  2. drawable-zh 重命名为 drawable-zh-rHK,运行查看结果

    发现系统成功匹配 drawable-zh-rHK 文件夹中的资源 系统成功匹配 drawable-zh-rHK 文件夹中的资源

  3. drawable-zh-rHK 重命名为 drawable-b+zh,运行查看结果

    发现系统未匹配 drawable-b+zh 文件夹中的资源 系统未匹配 drawable-b+zh 文件夹中的资源

  4. drawable-b+zh 重命名为 drawable-b+zh+Hant,运行查看结果

    发现系统成功匹配 drawable-b+zh+Hant 文件夹中的资源 系统成功匹配 drawable-b+zh+Hant 文件夹中的资源

测试说明:zhb+zh 不会作为 繁体中文 的备选项;zh-rHKb+zh+Hant 可以作为 繁体中文 的备选项

Locale 相关信息打印

displayNamelanguagescriptcountry
中文 (简体中文,中国)zhHansCN
中文 (繁體中文,台灣)zhHantTW
中文 (简体中文,新加坡)zhHansSG
English (United States)enUS

源码验证

入口为 Context#getString

Java 层
Context#getString
...
Resources#getText
...
AssetManager#nativeGetResourceValue
JNI 层
AssetManager#nativeGetResourceValue
...
AssetManager2#FindEntryInternal
...
ResourceTypes#ResTable_config::match
LocaleData#localeDataComputesScript
LocaleDataTables#LIKELY_SCRIPTS

重要逻辑在 ResourceTypes#ResTable_config::match 中,这里语言匹配策略的伪代码为:

fun isThisConfigMatch() {
    if( language 不匹配 ) return false
    if( 当前配置没有 Script ){
        if( 当前配置 country != 系统配置 country ) return false
    }else {
        if( 当前配置 script != 系统配置 script ) return false
    }

    return true
}

也就是说:

如果系统设置的语言没有 script ,比如 en-US,那么资源的 country 必须为 US 才会被匹配上。

如果系统设置的语言有 script,比如 zh-Hant-TW,那么资源的 language 必须为 zh 并且 script必须为 Hant 才会被匹配上。

这样我们就知道为什么繁体中文不会默认 fallback 到 简体中文 了。

但是还存在两个问题:

  1. 为什么安卓能准确映射 zh-rHKHant
  2. 为什么 zhzh-Hans 的备选项呢?

安卓资源文件夹(比如 zh-rHK) 的匹配逻辑

LocaleData#localeDataComputesScript 中,会根据 language (zh) 及 region (HK) 在 LocaleDataTables#LIKELY_SCRIPTS 这个默认的 Map 中查找并返回一个 script

{0x7A684155u, 30u}, // zh-AU -> Hant
{0x7A68424Eu, 30u}, // zh-BN -> Hant
{0x7A684742u, 30u}, // zh-GB -> Hant
{0x7A684746u, 30u}, // zh-GF -> Hant
{0x7A68484Bu, 30u}, // zh-HK -> Hant  👈
{0x7A684944u, 30u}, // zh-ID -> Hant
{0x7A684D4Fu, 30u}, // zh-MO -> Hant
{0x7A685041u, 30u}, // zh-PA -> Hant
{0x7A685046u, 30u}, // zh-PF -> Hant
{0x7A685048u, 30u}, // zh-PH -> Hant
{0x7A685352u, 30u}, // zh-SR -> Hant
{0x7A685448u, 30u}, // zh-TH -> Hant
{0x7A685457u, 30u}, // zh-TW -> Hant
{0x7A685553u, 30u}, // zh-US -> Hant
{0x7A68564Eu, 30u}, // zh-VN -> Hant

这样,当系统语言为 繁体中文(台湾) 并且没有 b+zh+Hant+TW 这个最匹配的语言资源时,并且也没有b+zh+Hant时,在匹配 zh-rHK 时,由于它返回的 script 与 系统语言 script 一致,这种情况下 country 字段在判断中被忽略,它就被命中了。

也可以理解为 zh-rHK 就是 b+zh+Hant+HK ,只是这个逻辑是写在安卓底层的。

而资源文件夹 values-zh 这种只有 language 的文件夹如何匹配,安卓底层也考虑到了,默认指定的是简体中文 Hans

{0x7A680000u, 29u}, // zh -> Hans

总结

  1. 安卓中 繁体中文简体中文 可以认为是两种不同的语言(其实据我所知 iOS 也是)。
  2. Android 7.0 之后会根据 script 判断是否将仅有 language 的资源文件夹作为备选项,比如 values-zh 永远不会被系统语言 繁体中文(台湾) 匹配上。
  3. zh-rHK 类似的文件夹在匹配逻辑中会计算出 script 后再进行匹配,这个计算逻辑由系统底层实现。

现状探讨

1. 项目中繁体仅有一套,放在 zh-rHK 中,没有针对地区做定制化,是否有必要改为 values-b+zh+Hant 的形式。

安卓官方推荐,并且这是最准确的匹配方式,不依赖安卓系统预置的计算 script 的逻辑,条件允许就做吧。

我们项目的阻塞流程:

  1. 因为有类似 字符串录入平台字符串导入工具 这样的基础设施在,改了之后没人去更新这些工具。
  2. 新建文件夹命名麻烦,规则不清晰,没有 GUI 方便,比如 values-b+en+001,根本无从下手,而且不好维护。
  3. 项目支持的语言种类还不是很多。

2. resConfig 如何配置?

安卓官方文档指定您的应用支持哪些语言推荐添加配置去除不必要的资源。

android {
    defaultConfig {
        ...
        resourceConfigurations += setOf("en", "us")
    }
}

这里需要按照自己的资源命名来写,比如:

  • values 英文
  • values-zh 简体中文
  • values-zh-rHK 繁体中文

那么我们加上 resourceConfigurations += setOf("zh", "zh-rHK")

再比如:

  • values 英文
  • values-b+zh+Hans 简体中文
  • values-b+zh+Hant 繁体中文

那么我们加上 resourceConfigurations += setOf("b+zh+Hans", "b+zh+Hant")

测试得到的 resourceConfigurations 规则:

  1. 大小写不敏感,b+Zh+hAnT 都能识别,同样的,资源文件夹名称大小写也不敏感,drawable-b+zh+hant 在 APK 中会自动重命名为 drawable-b+zh+Hant
  2. 如果是 b+zh+Hans 这样的命名, "b+" 不能省略,否则编译失败。
  3. 一条数据匹配一个语言,比如 setOf("b+zh") 仅会将 values-b+zh+Hans 添加到 APK,这里的匹配逻辑没有详细研究,猜测也应该是根据传入的信息做了 script 的计算。