
1. 项目概述为什么我们需要关注Android语言列表简码如果你是一个Android开发者或者正在维护一个需要支持多语言的App那你一定在res/values目录下见过strings.xml文件也一定见过像values-zh-rCN、values-en-rUS这样的文件夹。这里的zh、en就是语言代码CN、US就是地区代码。但你是否曾经疑惑过这些简码是从哪里来的为什么简体中文是zh-rCN而不是zh-Hans为什么有时候设置了values-es但系统却可能匹配到values-es-r419拉丁美洲西班牙语这些问题都指向了一个看似基础但至关重要的知识点Android语言列表简码。简单来说Android语言列表简码是一套由ISO标准定义并被Android系统内部用于标识和匹配语言与地区的代码体系。它远不止是文件夹命名那么简单它直接关系到你的App能否在用户的设备上正确显示其偏好的语言影响着用户体验的“第一印象”。一个错误的简码设置可能导致你的App在法语区用户手机上显示英语或者在简体中文环境下显示乱码。因此无论是进行国际化i18n还是本地化l10n深入理解这套简码规则都是绕不开的第一步。2. 核心概念解析语言、地区与Locale在深入简码之前我们必须先厘清三个核心概念语言代码、地区代码和Locale对象。这是理解整个匹配机制的基础。2.1 语言代码与地区代码语言代码通常是一个由两个小写字母组成的代码遵循ISO 639-1标准。它标识的是人类语言本身。zh: 中文en: 英语es: 西班牙语ja: 日语fr: 法语地区代码通常是一个由两个大写字母组成的代码遵循ISO 3166-1 alpha-2标准。它标识的是国家或地区。CN: 中国US: 美国GB: 英国FR: 法国JP: 日本当语言和地区组合在一起时就形成了我们常见的语言区域标识符例如zh-CN中国大陆简体中文、en-US美国英语。在Android资源目录的命名中使用下划线_和字母r来连接如values-zh-rCN。这里的r是一个字面字符没有特殊含义只是历史遗留的格式。2.2 Android中的Locale对象在Java/Kotlin代码中Android使用java.util.Locale类来封装语言和地区信息。它是所有语言相关操作的基石。// 创建一个表示简体中文中国的Locale val localeZhCN Locale(zh, CN) // 或者使用常量但常量有限 val localeUS Locale.US // 等同于 Locale(en, US) // 获取系统当前Locale val currentLocale Locale.getDefault() // 使用Locale设置上下文Configuration val config resources.configuration config.setLocale(localeZhCN) // 注意在Android 7.0 (API 24) 之后更推荐使用Context.createConfigurationContext val context createConfigurationContext(config)Locale对象不仅包含了语言和地区代码还可能包含脚本Script和变体Variant等信息例如zh-Hans-CNHans表示简体中文脚本。系统在寻找资源时就是根据当前Configuration中的Locale信息按照一套复杂的匹配规则去查找最合适的资源目录。注意在代码中设置Locale时务必注意作用域。直接修改Resources.updateConfiguration在API 25及以上版本已被废弃且可能影响整个应用甚至不当使用时其他应用。正确的做法是针对特定的Context或Activity使用createConfigurationContext来创建新的配置上下文。2.3 资源目录命名规则详解理解了Locale再看资源目录命名就清晰了。Android资源系统支持非常灵活的限定符组合语言和地区是其中最关键的部分。基本格式是资源类型-语言代码[-r地区代码]values/: 默认资源当没有更具体的匹配时使用。values-zh/: 适用于所有中文语言环境的通用字符串。values-zh-rCN/: 专门适用于中国大陆地区的简体中文字符串。values-zh-rTW/: 专门适用于中国台湾地区的繁体中文字符串。values-zh-rHK/: 专门适用于中国香港地区的繁体中文字符串。values-en/: 适用于所有英语语言环境。values-en-rUS/: 美式英语。values-en-rGB/: 英式英语。更复杂的情况是Android还支持脚本作为限定符这对于区分简繁体中文尤其重要。虽然zh-CN通常隐含简体zh-TW隐含繁体但使用脚本代码Hans简体和Hant繁体是更精确的做法。values-zh-Hans/: 简体中文不限地区。values-zh-Hans-rCN/: 中国大陆简体中文。values-zh-Hant/: 繁体中文不限地区。values-zh-Hant-rTW/: 台湾繁体中文。系统在匹配时会遵循“从最具体到最通用”的原则。例如对于Locale为zh-Hans-CN的设备查找字符串资源的顺序可能是values-zh-Hans-rCN/values-zh-Hans/values-zh-rCN/values-zh/values/(默认)如果第1步找到就用它找不到则继续第2步依此类推。3. 系统语言列表与匹配策略用户可以在系统设置中调整语言偏好顺序这个列表就是App资源匹配的“寻宝地图”。Android系统使用一套精密的算法来为应用确定最合适的资源。3.1 如何获取和解析系统语言列表在Android 7.0 (API 24) 及以上版本系统支持多语言偏好列表。我们可以通过以下代码获取import android.os.Build import android.content.res.Configuration val config: Configuration resources.configuration val locales: Arrayout Locale if (Build.VERSION.SDK_INT Build.VERSION_CODES.N) { config.locales.toTypedArray() } else { // API 24以下只有首选Locale arrayOf(config.locale) } locales.forEachIndexed { index, locale - Log.d(Language, 偏好 $index: ${locale.language}-${locale.country} 显示名: ${locale.displayName}) }这个列表的顺序至关重要。假设用户设置是中文简体中国English (United States)。那么系统会优先尝试用zh-CN去匹配资源如果匹配不到比如你的App没有提供中文资源才会回退到en-US最后再到默认的values/。3.2 Android的资源匹配算法Android的资源匹配是一个复杂但设计良好的过程其核心目标是找到与设备配置“最匹配”的资源而不是简单的“相等”。匹配过程考虑以下限定符的优先级从高到低MCC移动国家代码和MNC移动网络代码语言和地区即我们讨论的简码布局方向ldrtl, ldltr最小宽度可用宽度屏幕尺寸屏幕方向UI模式夜间模式屏幕像素密度触摸屏类型键盘可用性主要输入法导航键可用性平台版本对于语言地区的匹配其内部逻辑可以概括为精确匹配查找与设备Locale完全一致的目录如zh-CN对values-zh-rCN。语言匹配如果找不到则忽略地区代码只匹配语言如zh-CN对values-zh。地区回退某些语言有默认的地区关联。例如对于en英语如果找不到values-en系统可能会尝试回退到values-en-rUS因为美式英语被当作一种“默认”的英语变体。但这是一个系统行为并非所有语言都如此不能依赖。父语言匹配对于某些语言变体系统可能进行父语言匹配。例如values-id印度尼西亚语可能会匹配到values-in旧的印尼语代码因为in是id的旧代码。Android维护了一个内部映射来处理这种历史遗留问题。脚本匹配如果Locale包含脚本如zh-Hans系统会优先匹配脚本再匹配语言。实操心得永远不要假设系统的回退行为。最稳妥的做法是为你的主要目标市场提供精确的语言-地区对资源并提供一个高质量的默认values/资源作为最后保障。我曾经在一个项目中因为只提供了values-en-rGB英式英语而没有values-en或values-en-rUS导致一些设置为en-AU澳大利亚英语的设备回退到了默认的德语资源造成了严重的用户体验问题。教训就是覆盖核心语言时考虑使用通用的语言目录如values-en/来捕获所有该语言的变体。3.3 常见匹配陷阱与误区zh与zh-CN/zh-TW的混淆如果你只提供了values-zh/那么所有中文用户无论简体繁体大陆还是港澳台都会看到同一套字符串。这可能没问题但如果涉及货币、日期格式、计量单位或特定文化词汇就会出问题。例如“软件”和“軟體”。最佳实践是如果资源允许至少区分zh-Hans和zh-Hant。es与es-419es-419是“拉丁美洲西班牙语”的地区代码。很多应用为庞大的拉美市场提供values-es-r419资源。但如果用户设备Locale是es-MX墨西哥西班牙语系统会优先匹配values-es-rMX找不到则匹配values-es-r419最后才是values-es。这意味着为es-419准备的通用拉美西语资源是一个很好的折中方案。已废弃的代码如印尼语代码从in改为id希伯来语从iw改为he意第绪语从ji改为yi。Android系统内部会处理这些映射但为了清晰和未来兼容性你应该始终使用新的、标准的ISO代码。4. 实战配置、管理与测试多语言资源理论清楚了我们来动手操作。如何在Android Studio中高效地管理和测试多语言资源4.1 在Android Studio中创建与管理资源目录图形化创建在Project视图下右键点击res目录 -New-Android Resource Directory。选择资源类型在弹出窗口中Resource type选择values。添加限定符在左侧Available Qualifiers列表中选择Locale点击添加到Chosen Qualifiers。选择语言和地区这时右侧会出现Language和Specific Region Only下拉框。选择语言如zh: Chinese如果需要再选择地区如CN: China。完成创建点击OKAndroid Studio会自动生成类似values-zh-rCN的目录。更高效的方式是直接修改目录名你可以在res下直接新建一个名为values-zh-rCN的文件夹然后在里面新建strings.xml文件。Android Studio会正确识别它。4.2 编写多语言strings.xml文件每个语言目录下的strings.xml结构相同但内容翻译。关键是name属性必须完全一致。values/strings.xml(默认英文)resources string nameapp_nameMy Awesome App/string string namewelcome_messageHello, %s!/string string namebutton_confirmConfirm/string string nameerror_networkNetwork connection lost./string /resourcesvalues-zh-rCN/strings.xml(简体中文)resources string nameapp_name我的神奇应用/string string namewelcome_message你好%s/string string namebutton_confirm确认/string string nameerror_network网络连接已断开。/string /resourcesvalues-es/strings.xml(西班牙语)resources string nameapp_nameMi App Increíble/string string namewelcome_message¡Hola, %s!/string string namebutton_confirmConfirmar/string string nameerror_networkSe perdió la conexión de red./string /resources重要提示对于包含占位符的字符串要特别注意语序。例如英语是File %s not found.而中文可能是未找到文件%s。。翻译时只需翻译固定部分占位符%s、%d等必须原样保留且顺序可能需要调整。Android支持位置参数如%1$s、%2$d这可以解决语序问题。string namefile_not_foundFile %1$s not found in folder %2$s./string !-- 翻译时可以重新排列 -- string namefile_not_found在文件夹%2$s中未找到文件%1$s。/string4.3 在代码与布局中引用字符串引用方式很简单但有些细节需要注意。在布局XML中TextView android:layout_widthwrap_content android:layout_heightwrap_content android:textstring/welcome_message /在Java/Kotlin代码中// 直接获取 val message getString(R.string.welcome_message) // 带参数的获取 val userName Alex val personalizedMessage getString(R.string.welcome_message, userName) // 在非Activity/Context环境中需要传入Context val message context.getString(R.string.welcome_message)一个常见的坑使用Resources.getString(int id)时它使用的是当前Context的资源配置。如果你通过createConfigurationContext创建了一个新的配置上下文必须使用那个新上下文的getString方法否则拿到的还是系统默认语言的字符串。4.4 多语言测试策略测试是确保多语言功能正常的关键。你不能只在自己手机的中文环境下测试。使用Android Studio的翻译编辑器Tools-Resource Manager在Resource Manager标签页的左侧选择String这里可以直观地看到所有语言的翻译情况缺失的翻译会高亮显示。在设备/模拟器上快速切换语言开发者选项在设备的开发者选项中开启“强制使用从右到左的布局方向”可以测试RTL语言如阿拉伯语。更棒的是在Android 13及以上开发者选项里提供了“应用语言”的覆盖设置可以直接为单个App指定语言而无需改变系统语言。ADB命令这是最强大的测试方法。通过ADB可以动态修改设备或模拟器的Locale。# 查看当前Locale adb shell getprop persist.sys.locale adb shell settings get system system_locales # 设置Locale (例如设为英文-美国) adb shell setprop persist.sys.locale en-US # 或者使用settings命令 (API 21) adb shell settings put system system_locales en-US # 重启系统UI使更改生效或者直接重启设备 adb shell am broadcast -a android.intent.action.LOCALE_CHANGED注意setprop命令可能因设备厂商定制而不同settings命令更通用但需要API 21以上。设置后通常需要重启应用或系统UI才能生效。创建多语言测试构建变体在app/build.gradle中你可以配置不同的产品风味product flavors每个风味默认使用不同的资源。android { flavorDimensions language productFlavors { english { dimension language resConfigs en // 只打包英语资源 } chinese { dimension language resConfigs zh // 只打包中文资源 } full { dimension language // 打包所有资源 } } }这样你可以构建一个仅包含英语资源的APK和一个仅包含中文资源的APK用于测试在纯语言环境下的应用表现和包体积。自动化测试使用Espresso或UI Automator编写UI测试在测试开始前通过ADB或ActivityTestRule设置特定的Locale然后断言界面元素是否显示了正确语言的文本。5. 进阶话题与疑难杂症掌握了基础配置和测试后我们来看看那些容易让人栽跟头的进阶问题。5.1 应用内动态切换语言用户希望不改变系统语言只在你的App内切换语言。这是一个非常普遍的需求。实现的核心是更新应用的Configuration并重启相关界面。基本步骤保存用户选择的语言代码如zh-CN、en-US到SharedPreferences。创建一个工具类用于为任何Context应用保存的语言设置。在BaseActivity或每个Activity的attachBaseContext方法中应用这个设置。切换语言时更新设置并重启Activity。关键代码示例object LocaleManager { private const val SELECTED_LANGUAGE Locale.Helper.Selected.Language fun setLocale(context: Context, languageCode: String): Context { persist(context, languageCode) return updateResourcesLegacy(context, languageCode) } private fun persist(context: Context, language: String) { val preferences PreferenceManager.getDefaultSharedPreferences(context) preferences.edit().putString(SELECTED_LANGUAGE, language).apply() } // 获取保存的语言如果未保存则返回系统默认 fun getLanguage(context: Context): String { val preferences PreferenceManager.getDefaultSharedPreferences(context) return preferences.getString(SELECTED_LANGUAGE, Locale.getDefault().language) ?: en } Suppress(DEPRECATION) private fun updateResourcesLegacy(context: Context, language: String): Context { val locale Locale(language) Locale.setDefault(locale) val resources context.resources val configuration resources.configuration if (Build.VERSION.SDK_INT Build.VERSION_CODES.N) { configuration.setLocale(locale) configuration.setLocales(LocaleList(locale)) return context.createConfigurationContext(configuration) } else { configuration.locale locale resources.updateConfiguration(configuration, resources.displayMetrics) } return context } } // 在BaseActivity中应用 open class BaseActivity : AppCompatActivity() { override fun attachBaseContext(newBase: Context) { val language LocaleManager.getLanguage(newBase) val context LocaleManager.setLocale(newBase, language) super.attachBaseContext(context) } } // 切换语言时例如在设置页面 fun switchToChinese() { LocaleManager.setLocale(this, zh-CN) // 必须重启Activity才能使更改生效 val intent Intent(this, MainActivity::class.java) intent.flags Intent.FLAG_ACTIVITY_CLEAR_TASK or Intent.FLAG_ACTIVITY_NEW_TASK startActivity(intent) finish() }重要警告动态切换语言是一个“深水区”。以上方法在大多数情况下有效但存在已知问题WebViewWebView内部的页面可能不会立即跟随应用语言改变因为它有自己的缓存和上下文。通常需要重新加载WebView或处理其WebViewClient。第三方库一些第三方库特别是那些自行缓存Context或资源的可能无法正确响应语言变化导致界面语言不一致。后台服务/通知由AlarmManager、WorkManager或后台服务触发的通知其文本可能仍然使用系统默认语言因为触发时可能没有正确的Context。应用小部件App Widget小部件的更新由系统控制难以保证使用应用内语言。 因此在决定支持应用内切换语言前务必进行充分测试并评估其带来的复杂性和潜在bug。5.2 处理RTL从右到左语言阿拉伯语、希伯来语等是从右到左书写的语言。Android提供了很好的RTL支持但需要开发者主动适配。在Manifest中声明支持在application标签中添加android:supportsRtltrue。布局镜像对于布局文件使用start和end代替left和right。!-- 错误 -- Button android:layout_marginLeft16dp / !-- 正确 -- Button android:layout_marginStart16dp /图片资源可能需要为RTL语言提供镜像版本的图片。可以创建drawable-ldrtl目录存放RTL专用的图片或者使用android:autoMirroredtrue属性适用于具有明确方向的图标如箭头。测试如前所述在开发者选项中强制开启RTL布局进行测试。5.3 语言资源与APK包体积优化支持的语言越多strings.xml和其他资源文件就越多APK体积也会增大。优化策略包括使用resConfigs在app/build.gradle中指定你真正需要打包的语言过滤掉不需要的。android { defaultConfig { ... // 只打包英语、简体中文、繁体中文的资源 resConfigs en, zh-rCN, zh-rTW, zh-rHK } }按需下载语言包对于资源极其丰富的应用如大型游戏可以考虑将非核心语言的资源放在服务器上根据用户选择动态下载。但这会显著增加开发复杂度。使用Android App Bundle发布时使用.aab格式Google Play会根据用户设备的Locale自动交付对应的语言资源无需用户下载所有语言从而减少用户下载的APK体积。这是目前最推荐的方式。5.4 常见问题排查清单问题现象可能原因排查步骤与解决方案App在某种语言下显示空白或崩溃1. 该语言资源目录存在但某个关键字符串缺失。2. 布局文件中硬编码了文本未使用string/引用。3. 该语言下的某个资源文件如图片引用了一个不存在的资源ID。1. 使用Android Studio的“Lint”检查缺失的翻译。2. 全局搜索布局文件中的硬编码文本android:text...。3. 检查崩溃日志看是否是在解析资源时出错。确保所有语言目录下的资源name一致。语言切换后部分文本没变1. 文本是在代码中通过getString()获取后设置的但获取时使用的Context不是应用了新语言配置的Context。2. 文本被缓存了例如在ViewModel或静态变量中。3. 第三方库内部缓存了字符串。1. 确保在动态切换语言后所有通过代码设置的文本都使用更新后的Context重新获取。2. 检查代码避免缓存与Locale相关的字符串。在语言变更事件中清除缓存。3. 查阅第三方库文档看是否有语言变更的回调或刷新机制。系统语言是A但App启动后显示语言B1. 应用内动态语言设置覆盖了系统设置。2. Gradle配置中resConfigs限制了语言导致系统语言不在列表中回退到了列表中的第一种语言。1. 检查应用是否保存了自定义语言偏好并确认其逻辑。2. 检查build.gradle中的resConfigs确保包含了所有目标系统语言。zh-CN和zh-TW用户看到的内容一样资源目录只提供了values-zh/没有提供更具体的values-zh-rCN/或values-zh-Hans/、values-zh-Hant/。为需要区分的市场创建独立的资源目录。如果内容确实无需区分可以保持通用但需明确知晓此决定。语言切换后数字、日期格式没变格式化数字或日期时没有使用与当前Locale对应的格式化器。使用NumberFormat.getInstance(Locale)和DateFormat.getDateInstance(style, Locale)等API并传入正确的Locale对象而不是使用系统默认的静态方法。理解并熟练运用Android语言列表简码是构建真正全球化应用的地基。它始于简单的文件夹命名但延伸至复杂的Locale匹配逻辑、动态切换的陷阱以及包体积的权衡。最好的学习方式就是动手实践为一个现有项目添加一门新的语言支持尝试动态切换并用ADB命令切换系统Locale进行测试。过程中踩到的每一个坑都会让你对这套机制的理解更深一层。