支持不同的语言和文化

应用包含可能特定于特定文化的资源。例如,应用可以包含特定于文化的字符串,这些字符串被翻译成当前区域设置的语言。

最好将特定于文化的资源与应用的其余部分分开。Android 根据系统区域设置设置解析语言和特定于文化的资源。您可以通过使用 Android 项目中的资源目录来提供对不同区域设置的支持。

您可以指定针对使用您应用的人员文化的资源。您可以提供适合用户语言和文化的任何资源类型。例如,以下屏幕截图显示了一个应用在设备的默认en_US区域设置和西班牙语es_ES区域设置中显示字符串和可绘制资源。

The app shows a
different text and icon depending on the current locale

图 1. 应用根据当前区域设置使用不同的资源。

当您使用 Android SDK 工具创建项目时,这些工具会在项目的顶层生成一个res/目录。在这个res/目录中是各种资源类型的子目录。还有一些默认文件,例如res/values/strings.xml文件,其中包含您的字符串值。

支持不同的语言不仅仅是使用特定于区域设置的资源。一些用户选择使用从右到左 (RTL) 脚本(例如阿拉伯语或希伯来语)作为其 UI 区域设置的语言。其他用户将其 UI 区域设置设置为使用 LTR 脚本(例如英语)的语言的用户可能会查看或生成使用 RTL 脚本的语言的内容。为了支持这两种类型的用户,您的应用需要执行以下操作

  • 为 RTL 区域设置采用 RTL UI 布局。

  • 检测并声明在格式化消息中显示的文本数据的方向。通常,您可以调用方法(如本指南所述),该方法可以为您确定文本数据的方向。

创建语言环境目录和资源文件

要添加对更多语言环境的支持,请在res/内部创建其他目录。每个目录的名称必须符合以下格式

<resource type>-b+<language code>[+<country code>]

例如,values-b+es/包含语言代码为es的语言环境的字符串资源。类似地,mipmap-b+es+ES/包含语言代码为es且国家/地区代码为ES的语言环境的图标。

Android 会根据设备在运行时的语言环境设置加载相应的资源。有关更多信息,请参阅提供替代资源

确定要支持哪些语言环境后,请创建资源子目录和文件。例如

MyProject/
    res/
       values/
           strings.xml
       values-b+es/
           strings.xml
       mipmap/
           country_flag.png
       mipmap-b+es+ES/
           country_flag.png

使用本地化资源填充资源文件。以下是本地化字符串和图像资源文件的示例

英文字符串(默认语言环境)位于/values/strings.xml

<resources>
    <string name="hello_world">Hello World!</string>
</resources>

西班牙语字符串(es语言环境)位于/values-b+es/strings.xml

<resources>
    <string name="hello_world">¡Hola Mundo!</string>
</resources>

美国国旗图标(默认语言环境)位于/mipmap/country_flag.png

The icon of flag of the
United States

图 2. 用于默认(en_US)语言环境的图标。

西班牙国旗图标(es_ES语言环境)位于/mipmap-b+es+ES/country_flag.png

The icon of flag of
Spain

图 3. 用于es_ES语言环境的图标。

注意:您可以对任何资源类型使用配置限定符(例如语言环境限定符)。例如,您可能希望提供位图可绘制对象的本地化版本。有关更多信息,请参阅本地化您的应用

在您的应用中使用资源

通过使用每个资源的name属性引用源代码和其他 XML 文件中的资源:R.<resource type>.<resource name>。各种方法都以这种方式接受资源,如下例所示

Kotlin

// Get a string resource
val hello = resources.getString(R.string.hello_world)

// Or supply a string resource to a method that requires a string
TextView(this).apply {
    setText(R.string.hello_world)
}

Java

// Get a string resource
String hello = getResources().getString(R.string.hello_world);

// Or supply a string resource to a method that requires a string
TextView textView = new TextView(this);
textView.setText(R.string.hello_world);

在 XML 文件中,您可以使用语法@<resource type>/<resource name>引用资源,只要 XML 属性接受兼容的值,如下例所示

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:src="@mipmap/country_flag" />

注意:为了确保正确优先考虑用户语言设置,请使用resConfigs属性指定您的应用支持的语言。有关更多信息,请参阅指定您的应用支持的语言

格式化消息中的文本

应用中最常见的任务之一是格式化文本。本地化消息通过将文本和数字数据插入到适当的位置来进行格式化。不幸的是,在处理 RTL UI 或 RTL 数据时,简单的格式化可能会显示不正确甚至无法读取的文本输出。

阿拉伯语、希伯来语、波斯语和乌尔都语等语言是从右到左书写的。但是,某些元素(例如数字和嵌入的 LTR 文本)在其他 RTL 文本中是从左到右书写的。包括英语在内的使用 LTR 脚本的语言也是双向的,因为它们可能包含需要从右到左显示的嵌入式 RTL 脚本。

应用通常会生成此类嵌入式相反方向文本的实例,例如通过将任意语言和任意文本方向的文本数据插入到本地化消息中。这种方向的混合通常不包含关于相反方向文本开始和结束位置的明确指示,因此应用生成的文本会导致用户体验不佳。

尽管系统对双向文本的默认处理通常会按预期呈现文本,但当您的应用将其插入本地化消息时,文本可能无法正确呈现。以下是文本可能显示不正确的示例情况

  • 插入到消息开头的文本

    PERSON_NAME 正在呼叫您

  • 以数字开头的文本,例如地址或电话号码

    987 654-3210

  • 以标点符号开头的文本,例如电话号码

    +19876543210

  • 以标点符号结尾的文本

    您确定吗?

  • 包含两个方向的文本

    单词בננה是希伯来语中的香蕉。

示例

假设某个应用有时需要显示消息“您是说 %s 吗?”,并在运行时将地址插入到 %s 的位置。该应用支持不同的 UI 语言环境,因此消息来自特定于语言环境的资源,并在设备设置为 RTL 语言环境时使用 RTL 方向。例如,对于希伯来语 UI,消息显示如下

האם התכוונת ל %s?

但是,建议的地址可能来自不包含该语言环境语言文本的数据库。例如,如果地址是加利福尼亚州某个地方的地址,则它在数据库中使用英文文本显示。如果您在不提供任何有关文本方向的提示的情况下将地址“15 Bay Street, Laurel, CA”插入 RTL 消息中,则结果不会像预期的那样或正确

האם התכוונת ל 15 Bay Street, Laurel, CA?

门牌号码显示在地址的右侧,而不是预期的左侧。这使得门牌号码看起来更像是一个奇怪的邮政编码。如果您在使用 LTR 文本方向的消息中包含 RTL 文本,也会出现同样的问题。

说明和解决方案

此示例中的问题发生是因为文本格式化程序未指定“15”是地址的一部分,因此系统无法确定“15”是其前面 RTL 文本的一部分还是其后面 LTR 文本的一部分。

要解决此问题,请使用unicodeWrap()方法(来自BidiFormatter类)。此方法检测字符串的方向,并将其包装在声明该方向的 Unicode 格式化字符中。

以下代码片段演示了如何使用unicodeWrap()

Kotlin

val mySuggestion = "15 Bay Street, Laurel, CA"
val myBidiFormatter: BidiFormatter = BidiFormatter.getInstance()

// The "did_you_mean" localized string resource includes
// a "%s" placeholder for the suggestion.
String.format(getString(R.string.did_you_mean), myBidiFormatter.unicodeWrap(mySuggestion))

Java

String mySuggestion = "15 Bay Street, Laurel, CA";
BidiFormatter myBidiFormatter = BidiFormatter.getInstance();

// The "did_you_mean" localized string resource includes
// a "%s" placeholder for the suggestion.
String.format(getString(R.string.did_you_mean),
        myBidiFormatter.unicodeWrap(mySuggestion));

由于“15”现在出现在声明为 LTR 的文本内,因此它在正确的位置显示

האם התכוונת ל 15 Bay Street, Laurel, CA?

对您插入本地化消息的每一部分文本使用unicodeWrap()方法,除非以下情况之一适用

  • 文本正在插入机器可读字符串中,例如 URI 或 SQL 查询。
  • 您知道文本部分已正确包装。

注意:如果您的应用面向 Android 4.3(API 级别 18)或更高版本,请使用 Android 框架中提供的BidiFormatter版本。否则,请使用支持库中提供的BidiFormatter版本。

格式化数字

在应用的逻辑中,使用格式化字符串(而不是方法调用)将数字转换为字符串

Kotlin

var myIntAsString = "$myInt"

Java

String myIntAsString = String.format("%d", myInt);

这会根据您的语言环境适当地格式化数字,这可能包括使用不同的数字集。

当您使用String.format()在设置为使用其自身数字集的语言环境(例如波斯语和大多数阿拉伯语语言环境)的设备上创建 SQL 查询时,如果查询的任何参数都是数字,则会出现问题。这是因为数字以语言环境的数字格式化,而这些数字在 SQL 中无效。

要保留 ASCII 格式的数字并使 SQL 查询有效,您需要改用包含语言环境作为第一个参数的String.format()的重载版本。使用语言环境参数Locale.US

支持布局镜像

使用 RTL 脚本的用户更喜欢 RTL 用户界面,其中包括右对齐的菜单、右对齐的文本以及指向左侧的前进箭头。

图 4 显示了设置应用中屏幕的 LTR 版本与其 RTL 版本之间的对比

The notification area is right-aligned near the top-right corner,
           the menu button in the app bar is near the top-left corner, the
           content in the main part of the screen is left-aligned and appears
           LTR, and the back button is near the bottom-left corner and is
           pointing to the left. The notification area is left-aligned near the top-left corner, the
            menu button in the app bar is near the top-right corner, the content
            in the main part of the screen is right-aligned and appears RTL, and
            the back button is near the bottom-right corner and is pointing to
            the right
图 4. 设置屏幕的 LTR 和 RTL 变体。

在向您的应用添加 RTL 支持时,请牢记以下几点

  • 仅当在运行 Android 4.2(API 级别 17)或更高版本的设备上使用时,应用中才支持 RTL 文本镜像。要了解如何在较旧的设备上支持文本镜像,请参阅本指南中的为旧版应用提供支持
  • 要测试您的应用是否支持 RTL 文本方向,请使用开发者选项进行测试(如本指南所述),并邀请使用 RTL 脚本的用户使用您的应用。

注意:要查看与布局镜像相关的其他设计指南(包括已镜像和未镜像元素的列表),请参阅双向性材质设计指南。

要镜像应用中的 UI 布局,使其在 RTL 语言环境中显示为 RTL,请完成以下部分中的步骤。

修改构建和清单文件

修改应用模块的build.gradle文件和应用清单文件,如下所示

build.gradle(模块:应用)

Groovy

android {
    ...
    defaultConfig {
        targetSdkVersion 17 // Or higher
        ...
    }
}

Kotlin

android {
    ...
    defaultConfig {
        targetSdkVersion(17) // Or higher
        ...
    }
}

AndroidManifest.xml

<manifest ... >
    ...
    <application ...
        android:supportsRtl="true">
    </application>
</manifest>

注意:如果您的应用面向 Android 4.1.1(API 级别 16)或更低版本,则android:supportsRtl属性将被忽略,以及应用的布局文件中出现的任何startend属性值。在这种情况下,应用中不会自动进行 RTL 布局镜像。

更新现有资源

将现有布局资源文件中的leftright分别转换为startend。这允许框架根据用户的语言设置对齐应用的 UI 元素。

注意:在更新资源之前,请了解如何为旧版应用提供支持,或面向 Android 4.1.1(API 级别 16)和更低版本的应用。

要使用框架的 RTL 对齐功能,请更改布局文件中表 1 中显示的属性。

表 1. 应用支持多个文本方向时要使用的属性

仅支持 LTR 的属性 支持 LTR 和 RTL 的属性
android:gravity="left" android:gravity="start"
android:gravity="right" android:gravity="end"
android:layout_gravity="left" android:layout_gravity="start"
android:layout_gravity="right" android:layout_gravity="end"
android:paddingLeft android:paddingStart
android:paddingRight android:paddingEnd
android:drawableLeft android:drawableStart
android:drawableRight android:drawableEnd
android:layout_alignLeft android:layout_alignStart
android:layout_alignRight android:layout_alignEnd
android:layout_marginLeft android:layout_marginStart
android:layout_marginRight android:layout_marginEnd
android:layout_alignParentLeft android:layout_alignParentStart
android:layout_alignParentRight android:layout_alignParentEnd
android:layout_toLeftOf android:layout_toStartOf
android:layout_toRightOf android:layout_toEndOf

表 2 显示了系统如何根据目标 SDK 版本、是否定义了leftright属性以及是否定义了startend属性来处理 UI 对齐属性。

表 2. 基于目标 SDK 版本和定义的属性的 UI 元素对齐行为

面向 Android 4.2
(API 级别 17)或更高版本?
已定义 Left 和 Right? 已定义 Start 和 End? 结果
startend将被使用,覆盖leftright
leftright将被使用
startend将被使用
使用 leftright 属性(忽略 startend 属性)。
leftright将被使用
startend 分别解析为 leftright

添加方向和语言相关的资源

此步骤涉及添加布局、可绘制对象和值资源文件的特定版本,这些文件包含针对不同语言和文本方向的自定义值。

在 Android 4.2(API 级别 17)及更高版本中,您可以使用 -ldrtl(布局方向从右到左)和 -ldltr(布局方向从左到右)资源限定符。为了与现有资源保持向后兼容性,旧版本的 Android 使用资源的语言限定符来推断正确的文本方向。

假设您想添加一个特定的布局文件来支持 RTL 脚本,例如希伯来语、阿拉伯语和波斯语。为此,在您的 res/ 目录中添加一个 layout-ldrtl/ 目录,如下例所示

res/
    layout/
        main.xml This layout file is loaded by default.
    layout-ldrtl/
        main.xml This layout file is loaded for languages using an
                 RTL text direction, including Arabic, Persian, and Hebrew.

如果您想添加一个仅为阿拉伯语文本设计的布局的特定版本,则您的目录结构如下所示

res/
    layout/
        main.xml This layout file is loaded by default.
    layout-ar/
        main.xml This layout file is loaded for Arabic text.
    layout-ldrtl/
        main.xml This layout file is loaded only for non-Arabic
                 languages that use an RTL text direction.

注意:语言特定的资源优先于布局方向特定的资源,布局方向特定的资源优先于默认资源。

使用受支持的小部件

从 Android 4.2(API 级别 17)开始,大多数框架 UI 元素自动支持 RTL 文本方向。但是,一些框架元素(例如 ViewPager)不支持 RTL 文本方向。

只要其对应的清单文件包含属性赋值 android:supportsRtl="true",主屏幕小部件就支持 RTL 文本方向。

提供对旧版应用的支持

如果您的应用面向 Android 4.1.1(API 级别 16)或更低版本,请除了 startend 之外,还包括 leftright 属性。

要检查您的布局是否需要使用 RTL 文本方向,请使用以下逻辑

Kotlin

private fun shouldUseLayoutRtl(): Boolean {
    return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
        View.LAYOUT_DIRECTION_RTL == layoutDirection
    } else {
        false
    }
}

Java

private boolean shouldUseLayoutRtl() {
    if (android.os.Build.VERSION.SDK_INT >=
            android.os.Build.VERSION_CODES.JELLY_BEAN_MR1) {
        return View.LAYOUT_DIRECTION_RTL == getLayoutDirection();
    } else {
        return false;
    }
}

注意:为避免兼容性问题,请使用 23.0.1 或更高版本的 Android SDK Build Tools

使用开发者选项进行测试

在运行 Android 4.4(API 级别 19)或更高版本的设备上,您可以在 设备上的开发者选项 中启用强制 RTL 布局方向。此设置允许您以 RTL 模式查看使用 LTR 脚本(例如英文文本)的文本。

更新应用逻辑

本节介绍在调整应用以处理多个文本方向时需要更新的应用逻辑的特定方面。

属性更改

要处理任何与 RTL 相关的属性(例如布局方向、布局参数、填充、文本方向、文本对齐或可绘制对象位置)的更改,请使用 onRtlPropertiesChanged() 回调。此回调允许您获取当前的布局方向并相应地更新活动的 View 对象。

视图

如果您正在创建不直接属于活动视图层次结构的 UI 小部件(例如对话框或类似吐司的 UI 元素),请根据上下文设置正确的布局方向。以下代码片段演示了如何完成此过程

Kotlin

val config: Configuration = context.resources.configuration
view.layoutDirection = config.layoutDirection

Java

final Configuration config =
    getContext().getResources().getConfiguration();
view.setLayoutDirection(config.getLayoutDirection());

View 类的几种方法需要额外考虑

onMeasure()
视图测量可能因文本方向而异。
onLayout()
如果您创建了自己的布局实现,则需要在您的 onLayout() 版本中调用 super() 并调整您的自定义逻辑以支持 RTL 脚本。
onDraw()
如果您正在实现自定义视图或向绘图添加高级功能,则需要更新您的代码以支持 RTL 脚本。使用以下代码确定您的窗口小部件是否处于 RTL 模式

Kotlin

// On devices running Android 4.1.1 (API level 16) and lower,
// you can call the isLayoutRtl() system method directly.
fun isLayoutRtl(): Boolean = layoutDirection == LAYOUT_DIRECTION_RTL

Java

// On devices running Android 4.1.1 (API level 16) and lower,
// you can call the isLayoutRtl() system method directly.
public boolean isLayoutRtl() {
    return (getLayoutDirection() == LAYOUT_DIRECTION_RTL);
}

可绘制对象

如果您有一个需要针对 RTL 布局镜像的可绘制对象,请根据设备上运行的 Android 版本完成以下步骤之一

  • 在运行 Android 4.3(API 级别 18)及更低版本的设备上,添加并定义 -ldrtl 资源文件。
  • 在 Android 4.4(API 级别 19)及更高版本上,在定义可绘制对象时使用 android:autoMirrored="true",这允许系统为您处理 RTL 布局镜像。

    注意:android:autoMirrored 属性仅适用于其双向镜像只是整个可绘制对象的图形镜像的简单可绘制对象。如果您的可绘制对象包含多个元素,或者如果反射您的可绘制对象会改变其解释,您可以自己执行镜像。在任何情况下,请咨询双向专家以确定镜像的可绘制对象对用户是否有意义。

重力

如果您的应用布局代码使用 Gravity.LEFTGravity.RIGHT,请将这些值分别更改为 Gravity.STARTGravity.END

如果您有依赖于 Gravity.LEFTGravity.RIGHT 属性的 Kotlin 或 Java 代码,您可以通过设置 absoluteGravity 以匹配 layoutDirection 来使其适应此更改。

例如,如果您使用以下代码

Kotlin

when (gravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
    Gravity.LEFT -> {
        // Handle objects that are left-aligned.
    }
    Gravity.RIGHT -> {
        // Handle objects that are right-aligned.
    }
}

Java

switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    case Gravity.LEFT:
        // Handle objects that are left-aligned.
        break;
    case Gravity.RIGHT:
        // Handle objects that are right-aligned.
        break;
}

将其更改为以下内容

Kotlin

val absoluteGravity: Int = Gravity.getAbsoluteGravity(gravity, layoutDirection)
when (absoluteGravity and Gravity.HORIZONTAL_GRAVITY_MASK) {
    Gravity.LEFT -> {
        // Handle objects that are left-aligned.
    }
    Gravity.RIGHT -> {
        // Handle objects that are right-aligned.
    }
}

Java

final int layoutDirection = getLayoutDirection();
final int absoluteGravity =
        Gravity.getAbsoluteGravity(gravity, layoutDirection);
switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
    case Gravity.LEFT:
        // Handle objects that are left-aligned.
        break;
    case Gravity.RIGHT:
        // Handle objects that are right-aligned.
        break;
}

这意味着即使您对重力值使用 startend,您也可以保留处理左对齐和右对齐值的现有代码。

注意:应用重力设置时,请使用包含 layoutDirection 参数的 Gravity.apply() 的重载版本。

边距和填充

要支持应用中的 RTL 脚本,请遵循与边距和填充值相关的以下最佳实践

支持每个应用的语言首选项

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

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

    您的应用必须在其清单中声明 android:localeConfig 属性,以告知系统它支持多种语言。要了解更多信息,请参阅有关 创建资源文件并在应用的清单文件中声明它 的说明。

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

    使用自定义应用内语言选择器的应用可以使用这些 API 为用户提供一致的用户体验,无论他们在何处选择其语言首选项。公共 API 还可帮助您减少样板代码量,并支持拆分 APK。它们还支持 应用的自动备份 以存储应用级别的用户语言设置。

    为了与以前的 Android 版本向后兼容,AndroidX 中也提供了等效的 API。我们建议使用 Appcompat 1.6.0-beta01 或更高版本。

    要了解更多信息,请参阅有关 实现新 API 的说明。

另请参阅

其他资源

要了解有关支持旧版设备的更多信息,请查看以下资源

博文