应用语言偏好设置

系统设置中的应用语言

在许多情况下,多语言用户会将系统语言设置为一种语言(例如英语),但他们希望为特定应用选择其他语言,例如荷兰语、中文或印地语。为了帮助应用为这些用户提供更好的体验,Android 13 为支持多种语言的应用引入了以下功能:

  • 系统设置:用户可以在此集中位置为每个应用选择首选语言。

    您可以配置应用以自动生成支持应用语言偏好设置所需的文件,并使其显示在系统设置中。如需了解详情,请参阅启用自动应用语言支持的说明。

  • 附加 API:这些公共 API(例如 LocaleManager 中的 setApplicationLocales()getApplicationLocales() 方法)允许应用在运行时设置不同于系统语言的语言。

    这些 API 会自动与系统设置同步;因此,使用这些 API 创建自定义应用内语言选择器的应用将确保其用户无论在哪里选择语言偏好设置,都能获得一致的用户体验。公共 API 还有助于您减少样板代码量,它们支持拆分 APK,并支持应用自动备份以存储应用级别的用户语言设置。

    为了与之前的 Android 版本向后兼容,AndroidX 中也提供了等效的 API。但是,对于 Android 12(API 级别 32)及更早版本,向后兼容的 API 适用于 AppCompatActivity 上下文,而非应用上下文。使用 Appcompat 1.6.0 或更高版本访问向后兼容的 API。

实现此功能概览

下表显示了基于不同用例的建议实现。

用例 建议实现
您的应用没有应用内语言选择器
  1. 启用自动应用语言支持以生成 LocaleConfig 文件,并将您的应用语言添加到系统设置中。
  2. (可选)如果您想添加应用内语言选择器:使用 AndroidX 库并选择加入我们的API 实现,以通过 autoStoreLocales 支持向后兼容性。
您的应用已有应用内语言选择器
  1. 启用自动应用语言支持以生成 LocaleConfig 文件,并将您的应用语言添加到系统设置中。
  2. 迁移您的应用自定义逻辑以使用公共 API,以确保用户获得一致的体验。
  3. 处理以下极端情况
    1. 在设备首次运行 Android 13 时调用 AppCompatDelegate.setApplicationLocales()
    2. 在以下情况下调用 AppCompatDelegate.setApplicationLocales(),以向系统提供预先存在的用户请求的语言区域:

用户系统设置

从 Android 13 开始,Android 在系统设置中包含一个集中位置,用于设置应用语言偏好设置。为确保您的应用语言可在运行 Android 13 或更高版本的设备的系统设置中进行配置,请启用自动应用语言支持(建议)或手动配置支持

启用自动应用语言支持

从 Android Studio Giraffe 和 AGP 8.1 开始,您可以配置您的应用以自动支持应用语言偏好设置。根据您的项目资源,AGP 会生成 LocaleConfig 文件,并在最终清单文件中添加对其的引用,因此您不再需要手动操作。AGP 使用应用模块和任何库模块依赖项的 res 文件夹中的资源来确定要包含在 LocaleConfig 文件中的语言区域。这意味着如果您为应用添加新语言的资源,您不必担心更新 LocaleConfig 文件。

请注意,自动应用语言功能支持运行 Android 13 (API 级别 33) 或更高版本的应用。要使用此功能,您必须将 compileSdkVersion 设置为 33 或更高。要为 Android 的早期版本配置应用语言偏好设置,您仍然需要使用 API 和应用内语言选择器

要启用自动应用语言支持,请按照以下步骤操作:

  1. 要开启此功能,请在模块级 build.gradle.kts 文件(如果您使用 Groovy,则是 build.gradle 文件)的 androidResources {} 块中使用 generateLocaleConfig 设置。此功能默认关闭。

    Kotlin

        android {
          androidResources {
            generateLocaleConfig = true
          }
        }
        

    Groovy

        android {
          androidResources {
            generateLocaleConfig true
          }
        }
        
  2. 指定默认语言区域
    1. 在应用模块的 res 文件夹中,创建一个名为 resources.properties 的新文件。
    2. resources.properties 文件中,使用 unqualifiedResLocale 标签设置默认语言区域。有关语言区域名称的格式,请参阅如何形成语言区域名称

AGP 会将此默认语言区域以及您使用 res 文件夹中的 values-* 目录指定的任何替代语言区域添加到自动生成的 LocaleConfig 文件中。

如何形成语言区域名称

要形成语言区域名称,请将语言代码与可选的脚本和区域代码组合起来,并用破折号分隔开:

例如,如果您的默认语言区域是美式英语:

unqualifiedResLocale=en-US

使用 android:localeConfig 将支持的语言添加到系统设置

您可以手动设置您的应用,以确保其语言可在运行 Android 13 或更高版本的设备的系统设置中进行配置。为此,请创建一个 locales_config XML 文件,并使用 android:localeConfig 属性将其添加到您的应用清单中。省略 android:localeConfig 清单条目表示用户不应能够在系统设置中独立于其系统语言设置您的应用语言。

要手动将您的应用支持的语言添加到用户的系统设置:

  1. 创建一个名为 res/xml/locales_config.xml 的文件,并指定您的应用语言,包括您的应用的最终回退语言区域,即 res/values/strings.xml 中指定的语言区域。

    有关格式要求,请参阅如何形成语言区域名称。另请参阅示例 locale_config.xml 文件,其中列出了最常用的语言区域。

    例如,对于支持以下语言的应用,请像这样格式化 locales_config.xml 文件:

    • 英语(美国)作为最终回退语言区域
    • 英语(英国)
    • 法语
    • 日语
    • 中文(简体,澳门)
    • 中文(繁体,澳门)
    <?xml version="1.0" encoding="utf-8"?>
    <locale-config xmlns:android="http://schemas.android.com/apk/res/android">
       <locale android:name="en-US"/>
       <locale android:name="en-GB"/>
       <locale android:name="fr"/>
       <locale android:name="ja"/>
       <locale android:name="zh-Hans-MO"/>
       <locale android:name="zh-Hant-MO"/>
    </locale-config>
    
  2. 在清单中,添加一行指向此新文件:

    <manifest>
        ...
        <application
            ...
            android:localeConfig="@xml/locales_config">
        </application>
    </manifest>
    

您可以使用 LocaleManager.setOverrideLocaleConfig 动态更新应用的 localeConfig,以自定义 Android 设置中按应用语言列表显示的语言集。这使您可以按区域自定义语言列表、运行 A/B 实验,并在您的应用使用服务器端本地化推送时提供更新的语言环境,如以下示例所示:

Kotlin

//For setOverrideLocaleConfig
val localeManager = applicationContext
    .getSystemService(LocaleManager::class.java)
localeManager.overrideLocaleConfig = LocaleConfig(
LocaleList.forLanguageTags("en-US,ja-JP,zh-Hans-SG")
)

//For getOverrideLocaleConfig
// The app calls the API to get the override LocaleConfig
val overrideLocaleConfig = localeManager.overrideLocaleConfig
// If the returned overrideLocaleConfig isn't equal to NULL, then the app calls the API to get the supported Locales
val supportedLocales = overrideLocaleConfig.supportedLocales()

Java

//For setOverrideLocaleConfig
mContext.getSystemService(LocaleManager.class).setOverrideLocaleConfig(new LocaleConfig(LocaleList.forLanguageTags("en-US,ja-JP,zh-Hans-SG")));

//For getOverrideLocaleConfig
// The app calls the API to get the override LocaleConfig
LocaleConfig overrideLocaleConfig = mContext.getSystemService(LocaleManager.class).getOverrideLocaleConfig();
// If the returned overrideLocaleConfig isn't equal to NULL, then the app calls the API to get the supported Locales
LocaleList supportedLocales = overrideLocaleConfig.getSupportedLocales();

此外,IME 现在可以使用 LocaleManager.getApplicationLocales 来了解当前应用的 UI 语言,以更新键盘语言,如所示:

Kotlin

val currentAppLocales: LocaleList = applicationContext.getSystemService(LocaleManager::class.java).getApplicationLocales(appPackageName)

Java

LocaleList currentAppLocales =
    mContext.getSystemService(LocaleManager.class).getApplicationLocales(appPackageName);

在 Gradle 中指定支持的语言

如果您的应用模块级 build.gradle 文件中尚未包含,请使用 resourceConfigurations 属性指定相同的语言:

android {
  ...
  defaultConfig {
    resourceConfigurations += ["en", "en-rGB", "fr", "ja", "b+zh+Hans+MO", "b+zh+Hant+MO"]
  }
}

resourceConfigurations 属性存在时,构建系统只会在 APK 中包含这些指定语言的语言资源,从而避免包含来自可能支持您的应用不支持的其他语言的库的翻译字符串。如需了解更多信息,请参阅指定您的应用支持的语言

用户如何在系统设置中选择应用语言

用户可以通过系统设置选择每个应用的首选语言。他们可以通过两种不同的方式访问这些设置:

  • 通过系统设置访问

    设置 > 系统 > 语言和输入法 > 应用语言 > (选择一个应用)

  • 通过应用设置访问

    设置 > 应用 > (选择一个应用) > 语言

处理应用内语言选择器

对于已包含应用内语言选择器或希望使用该选择器的应用,请使用公共 API 而非自定义应用逻辑来处理设置和获取用户为您的应用首选的语言。如果您将公共 API 用于您的应用内语言选择器,设备的系统设置将自动更新,以匹配用户通过您的应用内体验选择的任何语言。

为了与之前的 Android 版本向后兼容,我们强烈建议在实现应用内语言选择器时使用 AndroidX 支持库。但是,如果需要,您也可以直接实现框架 API

使用 AndroidX 支持库实现

使用 Appcompat 1.6.0 或更高版本中的 setApplicationLocales()getApplicationLocales() 方法。请注意,对于 Android 12 (API 级别 32) 及更早版本,向后兼容的 API 适用于 AppCompatActivity 上下文,而非应用上下文。

例如,要设置用户的首选语言,您可以要求用户在语言选择器中选择一个语言区域,然后将该值设置到系统中:

Kotlin

val appLocale: LocaleListCompat = LocaleListCompat.forLanguageTags("xx-YY")
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale)

Java

LocaleListCompat appLocale = LocaleListCompat.forLanguageTags("xx-YY");
// Call this on the main thread as it may require Activity.restart()
AppCompatDelegate.setApplicationLocales(appLocale);

请注意,除非您的应用自行处理语言区域配置更改,否则调用 setApplicationLocales() 会重新创建您的 Activity

使用 AppCompatDelegate.getApplicationLocales() 检索用户的首选语言区域。用户可能已从系统设置或您的应用内语言选择器中选择了其应用语言区域。

支持 Android 12 及更低版本

为了支持运行 Android 12(API 级别 32)及更低版本的设备,请通过在应用的 AppLocalesMetadataHolderService 服务的清单条目中将 autoStoreLocales 值设置为 true 并将 android:enabled 设置为 false,告知 AndroidX 处理语言区域存储,如以下代码段所示:

<application
  ...
  <service
    android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
    android:enabled="false"
    android:exported="false">
    <meta-data
      android:name="autoStoreLocales"
      android:value="true" />
  </service>
  ...
</application>

请注意,将 autoStoreLocales 值设置为 true 会导致主线程上发生阻塞式读取,如果您正在记录线程违规行为,可能会导致 StrictModediskReaddiskWrite 违规。有关详细信息,请参阅 AppCompatDelegate.setApplicationLocales()

自定义存储处理

省略清单条目或将 autoStoreLocales 设置为 false 表示您正在自行处理存储。在这种情况下,您必须在 Activity 生命周期的 onCreate 之前提供存储的语言区域,并在 Android 12 (API 级别 32) 或更低版本中对 AppCompatDelegate.setApplicationLocales() 的调用进行限制。

如果您的应用具有自定义语言区域存储位置,我们建议在您的自定义语言区域存储解决方案和 autoStoreLocales 之间进行一次性切换,以便用户继续以他们偏好的语言使用您的应用。这尤其适用于您的应用在设备升级到 Android 13 后首次运行的情况。在这种情况下,您可以通过从自定义存储中检索语言区域并将其传递到 AppCompatDelegate.setApplicationLocales() 来提供预先存在的、用户请求的语言区域。

使用 Android 框架 API 实现

虽然我们强烈建议您使用 AndroidX 支持库来实现应用内语言选择器,但您也可以将 Android 框架中的 setApplicationLocales()getApplicationLocales() 方法用于运行 Android 13 的设备。

例如,要设置用户的首选语言,您可以要求用户在语言选择器中选择一个语言区域,然后将该值设置到系统中:

// 1. Inside an activity, in-app language picker gets an input locale "xx-YY"
// 2. App calls the API to set its locale
mContext.getSystemService(LocaleManager.class
    ).setApplicationLocales(new LocaleList(Locale.forLanguageTag("xx-YY")));
// 3. The system updates the locale and restarts the app, including any configuration updates
// 4. The app is now displayed in "xx-YY" language

要获取用户的当前首选语言以在语言选择器中显示,您的应用可以从系统中获取值:

// 1. App calls the API to get the preferred locale
LocaleList currentAppLocales =
    mContext.getSystemService(LocaleManager.class).getApplicationLocales();
// 2. App uses the returned LocaleList to display languages to the user

其他最佳实践

请注意以下最佳实践。

在其他应用中调用 Intent 时考虑语言

以语言为中心的 Intent 可能允许您指定您希望调用的应用所使用的语言。一个例子是语音识别器 API 中的 EXTRA_LANGUAGE 功能。

考虑 Chrome 自定义标签页的 Accept-Language 标头

在调用 Chrome 自定义标签页时,考虑通过 Browser.EXTRA_HEADERS 添加 Accept-Language 标头,以在您的应用语言中打开网页。

如果您在系统设置中移除应用语言偏好设置,请将应用语言区域重置为系统语言区域

如果您从系统设置中移除应用的语言偏好设置(通过从应用的 AndroidManifest.xml 中移除 android:localeConfig),用户将无法轻易地将应用语言重置回系统默认设置。

因此,如果您移除 android:localeConfig,请考虑使用 LocaleListCompat.getEmptyLocaleList()LocaleList.getEmptyLocaleList() 将应用语言区域重置为系统语言区域,如以下代码段所示:

Kotlin

// Use the AndroidX APIs to reset to the system locale for backward and forward compatibility
AppCompatDelegate.setApplicationLocales(
  LocaleListCompat.getEmptyLocaleList()
)

// Or use the Framework APIs for Android 13 and above to reset to the system locale
val context = LocalContext.current
context.getSystemService(LocaleManager::class.java)
  .applicationLocales = LocaleList.getEmptyLocaleList()

Java

// Use the AndroidX APIs to reset to the system locale for backward and forward compatibility
AppCompatDelegate.setApplicationLocales(
  LocaleListCompat.getEmptyLocaleList()
);

// Or use the Framework APIs for Android 13 and above to reset to the system locale
mContext.getSystemService(LocaleManager.class)
  .setApplicationLocales(LocaleList.getEmptyLocaleList());

其他资源

有关更多信息,请参阅我们的代码示例、博客文章和视频。

示例 locale_config.xml 文件

默认情况下,Android 在 Android 开源项目 (AOSP) 中包含了一组最常用语言环境的系统级翻译。本节中包含的示例 locale_config.xml 文件显示了这些语言环境的建议格式。参考此示例文件可帮助您为应用支持的语言集构建自己的 locale_config.xml 文件。

<?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android">
   <locale android:name="af"/> <!-- Afrikaans -->
   <locale android:name="am"/> <!-- Amharic -->
   <locale android:name="ar"/> <!-- Arabic -->
   <locale android:name="as"/> <!-- Assamese -->
   <locale android:name="az"/> <!-- Azerbaijani -->
   <locale android:name="be"/> <!-- Belarusian -->
   <locale android:name="bg"/> <!-- Bulgarian -->
   <locale android:name="bn"/> <!-- Bengali -->
   <locale android:name="bs"/> <!-- Bosnian -->
   <locale android:name="ca"/> <!-- Catalan -->
   <locale android:name="cs"/> <!-- Czech -->
   <locale android:name="da"/> <!-- Danish -->
   <locale android:name="de"/> <!-- German -->
   <locale android:name="el"/> <!-- Greek -->
   <locale android:name="en-AU"/> <!-- English (Australia) -->
   <locale android:name="en-CA"/> <!-- English (Canada) -->
   <locale android:name="en-GB"/> <!-- English (United Kingdom) -->
   <locale android:name="en-IN"/> <!-- English (India) -->
   <locale android:name="en-US"/> <!-- English (United States) -->
   <locale android:name="es"/> <!-- Spanish (Spain) -->
   <locale android:name="es-US"/> <!-- Spanish (United States) -->
   <locale android:name="et"/> <!-- Estonian -->
   <locale android:name="eu"/> <!-- Basque -->
   <locale android:name="fa"/> <!-- Farsi -->
   <locale android:name="fi"/> <!-- Finnish -->
   <locale android:name="fil"/> <!-- Filipino -->
   <locale android:name="fr"/> <!-- French (France) -->
   <locale android:name="fr-CA"/> <!-- French (Canada) -->
   <locale android:name="gl"/> <!-- Galician -->
   <locale android:name="gu"/> <!-- Gujarati -->
   <locale android:name="hi"/> <!-- Hindi -->
   <locale android:name="hr"/> <!-- Croatian -->
   <locale android:name="hu"/> <!-- Hungarian -->
   <locale android:name="hy"/> <!-- Armenian -->
   <locale android:name="in"/> <!-- Indonesian -->
   <locale android:name="is"/> <!-- Icelandic -->
   <locale android:name="it"/> <!-- Italian -->
   <locale android:name="iw"/> <!-- Hebrew -->
   <locale android:name="ja"/> <!-- Japanese -->
   <locale android:name="ka"/> <!-- Georgian -->
   <locale android:name="kk"/> <!-- Kazakh -->
   <locale android:name="km"/> <!-- Khmer -->
   <locale android:name="kn"/> <!-- Kannada -->
   <locale android:name="ko"/> <!-- Korean -->
   <locale android:name="ky"/> <!-- Kyrgyz -->
   <locale android:name="lo"/> <!-- Lao -->
   <locale android:name="lt"/> <!-- Lithuanian -->
   <locale android:name="lv"/> <!-- Latvian -->
   <locale android:name="mk"/> <!-- Macedonian -->
   <locale android:name="ml"/> <!-- Malayalam -->
   <locale android:name="mn"/> <!-- Mongolian -->
   <locale android:name="mr"/> <!-- Marathi -->
   <locale android:name="ms"/> <!-- Malay -->
   <locale android:name="my"/> <!-- Burmese -->
   <locale android:name="nb"/> <!-- Norwegian -->
   <locale android:name="ne"/> <!-- Nepali -->
   <locale android:name="nl"/> <!-- Dutch -->
   <locale android:name="or"/> <!-- Odia -->
   <locale android:name="pa"/> <!-- Punjabi -->
   <locale android:name="pl"/> <!-- Polish -->
   <locale android:name="pt-BR"/> <!-- Portuguese (Brazil) -->
   <locale android:name="pt-PT"/> <!-- Portuguese (Portugal) -->
   <locale android:name="ro"/> <!-- Romanian -->
   <locale android:name="ru"/> <!-- Russian -->
   <locale android:name="si"/> <!-- Sinhala -->
   <locale android:name="sk"/> <!-- Slovak -->
   <locale android:name="sl"/> <!-- Slovenian -->
   <locale android:name="sq"/> <!-- Albanian -->
   <locale android:name="sr"/> <!-- Serbian (Cyrillic) -->
   <locale android:name="sr-Latn"/> <!-- Serbian (Latin) -->
   <locale android:name="sv"/> <!-- Swedish -->
   <locale android:name="sw"/> <!-- Swahili -->
   <locale android:name="ta"/> <!-- Tamil -->
   <locale android:name="te"/> <!-- Telugu -->
   <locale android:name="th"/> <!-- Thai -->
   <locale android:name="tr"/> <!-- Turkish -->
   <locale android:name="uk"/> <!-- Ukrainian -->
   <locale android:name="ur"/> <!-- Urdu -->
   <locale android:name="uz"/> <!-- Uzbek -->
   <locale android:name="vi"/> <!-- Vietnamese -->
   <locale android:name="zh-Hans"/> <!-- Chinese (Simplified) -->
   <locale android:name="zh-Hant"/> <!-- Chinese (Traditional) -->
   <locale android:name="zu"/> <!-- Zulu -->
</locale-config>