三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

Android开发必备:语言与国家代码清单详解与实战应用

Android开发必备:语言与国家代码清单详解与实战应用

1. 项目概述:为什么你需要一份Android语言/国家代码清单?

在Android开发或者产品本地化的过程中,你有没有遇到过这样的场景:产品经理拿着一份Excel表格,里面列着“简体中文(中国)”、“繁体中文(台湾)”、“英语(美国)”、“英语(英国)”……然后要求你在App里为这些地区配置不同的资源、价格或者功能。你打开Android Studio,准备在res目录下新建values-zh-rCN这样的文件夹时,突然卡壳了——台湾地区的代码是zh-rTW还是zh-TW?香港呢?沙特阿拉伯的阿拉伯语和埃及的阿拉伯语,代码又有什么区别?

这份看似简单的“Android语言/国家代码清单”,恰恰是解决这些混乱的基石。它不是什么高深的算法,但却是构建全球化应用的“螺丝刀”和“扳手”。没有它,你的本地化工作就像在黑暗中摸索,很容易因为代码格式错误导致资源加载失败,或者更糟糕,出现文化或区域上的不当展示。我见过不少团队,在临近上线时才发现某个地区的语言包根本没生效,排查半天,原因竟是一个字母的大小写错误或者分隔符用错了。这份清单,就是帮你避免这些低级但致命错误的“避坑指南”。

简单来说,这份清单定义了Android系统如何识别和匹配设备语言与区域的标准。它基于ISO标准,但在Android环境下有自己特定的格式和规则。无论是为strings.xml创建本地化文件,还是在代码中动态判断用户区域以提供定制化服务,都离不开对这些代码的正确理解和使用。接下来,我们就把它彻底拆解清楚。

2. 核心概念解析:Locale、语言代码与国家/地区代码

在深入清单之前,我们必须先理清两个核心概念:语言代码(Language Code)和国家/地区代码(Region Code),以及它们如何组成Android所使用的Locale对象。

2.1 语言代码(ISO 639)

语言代码用于标识人类语言。Android主要采用ISO 639-1的两字母代码,对于一些没有两字母代码的语言,则使用ISO 639-2的三字母代码。

  • 常见两字母代码zh(中文),en(英语),es(西班牙语),fr(法语),ja(日语),ko(韩语)。
  • 三字母代码示例fil(菲律宾语),zho(中文 – 通常作为备用的三字母代码,但Android中更常用zh)。

注意:代码是小写敏感的。虽然标准规定是小写,但在实际使用中务必保持一致性。

2.2 国家/地区代码(ISO 3166-1)

国家/地区代码用于标识地理区域。Android采用ISO 3166-1 alpha-2的两字母代码。

  • 常见代码CN(中国),US(美国),GB(英国),TW(台湾地区),HK(香港地区),MO(澳门地区),JP(日本),KR(韩国)。

重要提示:在Android的语境下,国家/地区代码用于区分同一语言在不同地区的变体(如简体中文zh-CN和繁体中文zh-TW),或者同一国家内的不同语言偏好。使用时需严格遵守相关的开发规范。

2.3 Android Locale的构成格式

在Android中,语言代码和地区代码组合成一个Locale对象,其字符串表示遵循特定的格式:

  1. 基本格式语言代码语言代码-地区代码
    • 例如:zh,en,zh-CN,en-US,en-GB
  2. 传统格式(Legacy)语言代码_r地区代码
    • 这是早期Android资源目录命名使用的格式,例如values-zh-rCNvalues-en-rUS。其中的r是固定字面量。在代码中创建Locale对象或进行区域设置时,请使用短横线-分隔符。资源目录命名则两种格式系统都能识别,但短横线格式是现在更推荐的方式。
  3. 包含脚本代码(罕见)语言代码-脚本代码-地区代码
    • 脚本代码遵循ISO 15924,用于区分书写系统。例如,塞尔维亚语可以用西里尔字母(sr-Cyrl-RS)或拉丁字母(sr-Latn-RS)书写。在大多数常见开发场景中较少用到。

理解这些格式差异至关重要,尤其是在处理资源目录和API调用时。混淆格式是导致本地化失败的常见原因之一。

3. Android资源系统中的代码应用实战

理论清楚了,我们来看最实际的应用:如何利用这些代码来组织你的应用资源,实现多语言适配。

3.1 资源目录命名规范

Android的资源系统通过为res下的子目录(如values,drawable)添加后缀来自动匹配设备Locale。规则如下:

  • 默认资源res/values/strings.xml(无后缀)
  • 指定语言的资源res/values-zh/strings.xml(仅语言代码)
  • 指定语言和地区的资源res/values-zh-CN/strings.xmlres/values-zh-rCN/strings.xml(语言-地区代码)
  • 指定语言、脚本和地区的资源res/values-zh-Hans-CN/strings.xml(简体中文,中国)

当用户设备设置为“中文(中国)”时,系统会按以下优先级查找strings.xml

  1. values-zh-CN(精确匹配)
  2. values-zh(语言匹配)
  3. values(默认回退)

如果用户设备是“中文(台湾)”,而你没有values-zh-TW,系统会回退到values-zh,最后是values

实操心得:务必设置一个完整、通用的默认values/strings.xml。这是最后的保障,可以避免因为缺少某个特定区域资源而导致应用崩溃(出现空白文本)。我通常会把英文(en)或最通用的语言版本作为默认资源。

3.2 创建与管理多语言资源

在Android Studio中操作非常简便:

  1. 在项目视图中,右键点击res目录。
  2. 选择New->Android Resource File
  3. 在弹出窗口中,Resource type选择ValuesFile name输入strings
  4. Available qualifiers列表中选择Locale,点击>>添加到Chosen qualifiers
  5. 此时会出现语言和地区下拉列表,选择你需要的语言(如 Chinese (zh))和地区(如 China (CN))。
  6. Android Studio会自动生成目录名,例如values-zh-rCN。点击OK,就会创建对应的strings.xml文件。

你可以用同样的方法为arrays.xmlcolors.xml甚至布局文件创建本地化版本。对于图片资源,如果需要本地化(例如包含文字的图片),可以创建drawable-zh-CN这样的目录。

常见问题:有时候你创建了values-zh-CN,但AS显示的目录名却是values-zh-rCN,这是IDE的显示偏好,两者是等价的,系统都能识别。不必纠结,以你在创建时选择的为准。

4. 在Java/Kotlin代码中动态使用Locale

资源系统是静态匹配,有时我们需要在代码中动态地获取或设置区域信息,以实现更灵活的逻辑。

4.1 获取设备当前Locale

// Kotlin val currentLocale = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // API 24及以上,支持多语言列表 resources.configuration.locales[0] } else { // 旧API @Suppress("DEPRECATION") resources.configuration.locale } val language = currentLocale.language // 例如 "zh" val country = currentLocale.country // 例如 "CN" Log.d("LocaleInfo", "当前语言: $language, 国家/地区: $country")
// Java Locale currentLocale; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { currentLocale = getResources().getConfiguration().getLocales().get(0); } else { currentLocale = getResources().getConfiguration().locale; } String language = currentLocale.getLanguage(); String country = currentLocale.getCountry(); Log.d("LocaleInfo", "当前语言: " + language + ", 国家/地区: " + country);

4.2 根据Locale执行特定逻辑

你可以根据获取到的代码来决定应用行为:

when (currentLocale.language) { "zh" -> { // 中文用户逻辑 if (currentLocale.country == "CN") { // 中国大陆特定逻辑,例如接入微信支付 setupWeChatPay() } else if (currentLocale.country == "TW") { // 台湾地区特定逻辑 setupTraditionalChineseUI() } } "en" -> { // 英文用户逻辑 showEnglishContent() } else -> { // 其他语言,回退到默认逻辑 fallbackToDefault() } }

4.3 以编程方式创建和比较Locale

// 创建特定的Locale对象 val localeChina = Locale("zh", "CN") val localeTaiwan = Locale("zh", "TW") val localeUS = Locale("en", "US") // 比较Locale val isChinese = localeChina.language == "zh" val isSimplifiedChinese = localeChina == Locale.SIMPLIFIED_CHINESE // 使用常量更可靠 val isTraditionalChinese = localeTaiwan == Locale.TRADITIONAL_CHINESE // 格式化信息(如日期、货币) val dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM, localeChina) val formattedDate = dateFormat.format(Date())

Android提供了一些常用的Locale常量,如Locale.CHINA(zh_CN)、Locale.US(en_US)、Locale.TAIWAN(zh_TW)。在代码中使用这些常量比手动拼接字符串更安全、更可读。

注意事项:在代码中拼接语言和地区字符串时,务必使用Locale构造函数或Locale.forLanguageTag(“zh-CN”)方法,而不是自己用“zh-CN”这样的字符串去比较或创建。因为Locale对象的内部表示可能包含更多信息,直接进行字符串比较可能不可靠。

5. 高级主题与最佳实践

掌握了基础应用后,我们来看一些更深层次的内容和实践中总结出的“干货”。

5.1 处理区域设置变更

当用户在系统设置中切换语言时,默认情况下Android会重启当前前台的Activity。为了提供更流畅的体验,你可以选择在应用内提供语言切换选项,并动态更新资源。

方法一:更新Configuration并重建Activity(API 17+推荐)

fun switchAppLocale(locale: Locale) { val resources = context.resources val configuration = resources.configuration configuration.setLocale(locale) // API 17+ 使用 setLocale // 对于API 25+,可以配合 applyOverrideConfiguration,但更简单的方式是创建新的Context val newContext = context.createConfigurationContext(configuration) // 使用newContext的resources来获取更新后的字符串 // 通常需要重启Activity来全局生效 activity.recreate() // 重启当前Activity }

方法二:使用AppCompatDelegate(AndroidX)这是更现代、更易于管理的方式,尤其配合ViewModel保存语言状态。

// 在Application或BaseActivity中保存语言选择(例如到SharedPreferences) // 在Activity的onCreate中(调用super.onCreate之前)设置 val savedLocale = getSavedLocaleFromPrefs() val config = resources.configuration config.setLocale(savedLocale) // 对于使用AppCompat的应用 AppCompatDelegate.setApplicationLocales( LocaleListCompat.forLanguageTags(savedLocale.toLanguageTag()) )

实操心得:应用内切换语言是个复杂功能,涉及到所有Activity的重建和状态恢复。一个常见的“坑”是WebView或第三方SDK,它们可能没有正确响应语言变更。建议在应用设计初期就决定是否支持应用内切换,并做好全面的测试。对于大多数应用,引导用户去系统设置切换可能是更稳定和省心的选择。

5.2 语言与国家/地区代码的常见“陷阱”清单

以下是我在多年开发中踩过或见过的坑,请你务必绕行:

  1. 大小写问题:语言代码始终小写,国家代码始终大写zh-cn是错误的,应该是zh-CN。虽然有些API容错性强,但坚持规范能避免意想不到的问题。
  2. 分隔符混淆:在资源目录名中,zh-CNzh_rCN历史共存,但在代码Locale对象和toLanguageTag()方法中,统一使用短横线-。建议在新项目中统一使用短横线格式。
  3. “国家”不等于“地区”Locale中的country字段实际代表的是地区。对于像en(英语)这种在许多地区使用的语言,必须结合地区代码才能准确匹配资源(en-US美式英语,en-GB英式英语)。
  4. 繁体中文的复杂性
    • zh-TW:中文(台湾)。使用繁体字,一些词汇和用语与大陆不同。
    • zh-HK:中文(香港)。使用繁体字,部分用语受粤语影响。
    • zh-MO:中文(澳门)。类似香港。
    • 不要假设zh-TW能完美覆盖所有繁体中文用户。如果资源允许,为香港和澳门单独配置是更专业的做法。
  5. “方言”或“变体”的处理:有些语言有重要变体,但ISO代码可能相同。例如:
    • 西班牙语:es-ES(卡斯蒂利亚西班牙语)和es-US(美国西班牙语)在用词上有差异。
    • 葡萄牙语:pt-PT(欧洲葡萄牙语)和pt-BR(巴西葡萄牙语)差异显著。
    • 法语:fr-FR(法国法语)和fr-CA(加拿大法语)也有不同。最佳实践:针对这些有显著差异的地区,创建独立的资源文件,即使语言代码相同。
  6. 回退链的误解:系统回退是zh-CN->zh->默认。但没有zh-CN->zh-TW这样的回退。不要指望系统会自动在繁体中文和简体中文之间切换。如果你希望简体中文用户也能看到繁体中文内容(作为一种备选),需要在你的应用逻辑中手动实现。
  7. API Level的差异:旧API(如configuration.locale)在API 24(Android 7.0)后被标记为废弃,改用configuration.locales以支持多语言列表。处理时务必做好版本判断。

5.3 获取完整的支持列表与校验

你可能会问,我怎么知道Android具体支持哪些代码?虽然Android遵循ISO标准,但并非所有代码都在资源选择器中有图形化选项。

  • 通过代码获取系统支持的语言

    val locales = Locale.getAvailableLocales() locales.forEach { locale -> Log.d("SupportedLocale", "${locale.language}-${locale.country} - ${locale.displayName}") }

    这会打印出JVM(Android Runtime)所识别的所有Locale。列表非常长,包含很多历史或罕见的组合。

  • 实践建议:你不需要支持列表中的所有项。通常,你只需要关注你的目标市场。根据产品需求,从上述列表或ISO官方网站筛选出你需要支持的10-20个语言地区组合即可。创建一个枚举类或常量文件来管理这些支持的Locale,是一个好习惯。

    object SupportedLocales { val SIMPLIFIED_CHINESE = Locale(“zh”, “CN”) val TRADITIONAL_CHINESE_TW = Locale(“zh”, “TW”) val TRADITIONAL_CHINESE_HK = Locale(“zh”, “HK”) val ENGLISH_US = Locale(“en”, “US”) val ENGLISH_UK = Locale(“en”, “GB”) val JAPANESE = Locale(“ja”, “JP”) // ... 其他 val ALL = listOf(SIMPLIFIED_CHINESE, TRADITIONAL_CHINESE_TW, ENGLISH_US, JAPANESE) fun isValid(target: Locale): Boolean { return ALL.any { it.language == target.language && it.country == target.country } } }

6. 清单速查与实战案例

最后,为了方便大家“抄作业”,我整理了一份最常见语言/地区代码的速查表,并附上一个实战案例。

6.1 常用Android语言/国家代码速查表

显示名称 (Display Name)语言代码 (Language)国家/地区代码 (Region)Android Locale Tag (推荐)传统资源目录示例
简体中文 (中国)zhCNzh-CNvalues-zh-rCN
繁体中文 (台湾)zhTWzh-TWvalues-zh-rTW
繁体中文 (香港)zhHKzh-HKvalues-zh-rHK
繁体中文 (澳门)zhMOzh-MOvalues-zh-rMO
英语 (美国)enUSen-USvalues-en-rUS
英语 (英国)enGBen-GBvalues-en-rGB
英语 (澳大利亚)enAUen-AUvalues-en-rAU
日语 (日本)jaJPja-JPvalues-ja-rJP
韩语 (韩国)koKRko-KRvalues-ko-rKR
法语 (法国)frFRfr-FRvalues-fr-rFR
法语 (加拿大)frCAfr-CAvalues-fr-rCA
德语 (德国)deDEde-DEvalues-de-rDE
西班牙语 (西班牙)esESes-ESvalues-es-rES
西班牙语 (美国)esUSes-USvalues-es-rUS
葡萄牙语 (巴西)ptBRpt-BRvalues-pt-rBR
葡萄牙语 (葡萄牙)ptPTpt-PTvalues-pt-rPT
俄语 (俄罗斯)ruRUru-RUvalues-ru-rRU
阿拉伯语 (沙特阿拉伯)arSAar-SAvalues-ar-rSA
印地语 (印度)hiINhi-INvalues-hi-rIN

说明

  1. “Android Locale Tag”列是你在代码中创建Locale对象或使用toLanguageTag()时应使用的格式。
  2. “传统资源目录示例”列是Android资源系统可以识别的目录名格式。现在新建目录时,更推荐直接使用zh-CN这样的短横线格式(如values-zh-CN),Android Studio和构建系统都能正确处理。

6.2 实战案例:为电商App配置多区域价格和文案

假设你开发一个电商App,需要针对美国、英国、中国大陆、台湾地区展示不同的价格(货币符号和格式)和促销文案。

步骤1:创建本地化字符串资源

  • res/values/strings.xml(默认,例如用美式英语)
    <string name="product_price">Price: $%.2f</string> <string name="promo_text">Summer Sale! Up to 50%% off!</string>
  • res/values-en-GB/strings.xml
    <string name="product_price">Price: £%.2f</string> <string name="promo_text">Summer Sale! Up to 50%% off!</string>
  • res/values-zh-CN/strings.xml
    <string name="product_price">价格:¥%.2f</string> <string name="promo_text">夏季大促!低至5折!</string>
  • res/values-zh-TW/strings.xml
    <string name="product_price">價格:NT$%.2f</string> <string name="promo_text">夏季特賣!最高50%% off!</string>

步骤2:在代码中动态格式化和显示

fun displayProductInfo(price: Double) { // 获取当前Locale val currentLocale = getCurrentLocale() // 获取本地化字符串 val priceFormat = resources.getString(R.string.product_price) val promoText = resources.getString(R.string.promo_text) // 创建区域特定的数字格式(用于小数位、千分位分隔符) val numberFormat = NumberFormat.getCurrencyInstance(currentLocale) val localizedPriceString = numberFormat.format(price) // 更专业的方式 // 或者使用字符串格式(如果字符串里已包含符号) val formattedPrice = String.format(currentLocale, priceFormat, price) // 更新UI priceTextView.text = localizedPriceString // 或 formattedPrice promoTextView.text = promoText // 更进一步:根据地区决定是否显示促销(例如某些地区无此活动) if (currentLocale.country == "CN" && isSummerSaleInChina()) { promoTextView.visibility = View.VISIBLE } else { promoTextView.visibility = View.GONE } }

步骤3:处理货币转换逻辑价格本身通常来自后端API,API应返回基础价格(如美元)和货币代码(如USD)。客户端需要根据用户区域和实时汇率(可能依赖本地逻辑或再次查询服务端)进行换算和显示。NumberFormat.getCurrencyInstance(locale)能根据Locale自动匹配正确的货币符号和格式,但它不负责汇率计算。

这个案例展示了如何将简单的代码清单,融入到真实的业务逻辑中,实现真正的国际化用户体验。记住,本地化不仅仅是翻译文字,还包括数字、日期、货币格式,乃至图片、布局和功能逻辑的适配。而这一切的起点,就是正确理解和使用那份“Android语言/国家代码清单”。希望这份超详细的拆解,能让你在应对全球化开发的挑战时,手里多一份扎实的地图。

← 返回列表