支持不同的语言和文化

应用包含可针对特定文化设置的资源。例如,应用可以包含翻译为当前语言区域语言的特定文化设置的字符串。

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

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

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

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

使用 Android SDK Tools 创建项目时,这些工具会在项目的顶级目录中生成一个 res/ 目录。在此 res/ 目录中,有用于各种资源类型的子目录。还有一些默认文件,例如存放字符串值的 res/values/strings.xml 文件。

支持不同的语言不仅仅是使用特定语言区域的资源。有些用户为他们的 UI 语言区域选择使用从右到左 (RTL) 脚本的语言,例如阿拉伯语或希伯来语。其他将 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>

/values-b+es/strings.xml 中的西班牙语字符串(es 语言区域)

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

/mipmap/country_flag.png 中的美国国旗图标(默认语言区域)

The icon of flag of the
United States

图 2. 默认 (en_US) 语言区域使用的图标。

/mipmap-b+es+ES/country_flag.png 中的西班牙国旗图标(es_ES 语言区域)

The icon of flag of
Spain

图 3. es_ES 语言区域使用的图标。

注意:您可以在任何资源类型上使用配置限定符,例如语言区域限定符。例如,您可能想提供本地化版本的 bitmap drawable。如需了解详情,请参阅本地化您的应用

在应用中使用资源

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

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

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

注意:为确保用户语言设置正确优先,请使用 resConfigs 属性指定您的应用支持的语言。如需了解详情,请参阅指定您的应用支持的语言

格式化消息中的文本

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

阿拉伯语、希伯来语、波斯语和乌尔都语等语言使用 RTL 书写。但是,一些元素,例如数字和内嵌的 LTR 文本,在 RTL 文本中会使用 LTR 书写。使用 LTR 脚本的语言(包括英语)也是双向的,因为它们可能包含需要以 RTL 显示的内嵌 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 文本的一部分。

为了解决这个问题,请使用 BidiFormatter 类中的 unicodeWrap() 方法。此方法会检测字符串的方向,并用声明该方向的 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 Framework 中提供的 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 支持时,请注意以下几点:

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

注意:要查看与布局镜像相关的其他设计准则,包括适合和不适合镜像的元素列表,请参阅双向性 Material Design 准则。

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

修改构建文件和清单文件

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

build.gradle(模块:app)

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

添加特定方向和语言的资源

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

在 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 文本方向。

主屏幕微件支持 RTL 文本方向,前提是其相应的清单文件包含属性赋值 android:supportsRtl="true"

为旧版应用提供支持

如果您的应用以 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 相关的属性(例如布局方向、布局参数、内边距、文本方向、文本对齐或 drawable 位置)的更改,请使用 onRtlPropertiesChanged() 回调。此回调可让您获取当前的布局方向,并相应地更新 activity 的 View 对象。

View

如果您正在创建不是 activity 视图层次结构一部分的 UI 微件,例如对话框或 Toast 式 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()
View 的测量结果可能因文本方向而异。
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);
}

Drawable

如果您有需要为 RTL 布局镜像的 drawable,请根据设备上运行的 Android 版本完成以下步骤之一:

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

    注意: android:autoMirrored 属性仅适用于简单的 drawable,其双向镜像只是整个 drawable 的图形镜像。如果您的 drawable 包含多个元素,或者镜像 drawable 会改变其解释,则您可以自己执行镜像。只要可能,请咨询双向文本专家,以确定镜像后的 drawable 对用户是否有意义。

Gravity

如果您的应用布局代码使用 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 作为 gravity 值。

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

外边距和内边距

为了在应用中支持 RTL 脚本,请遵循以下与外边距和内边距值相关的最佳实践:

支持每个应用的语言偏好设置

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

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

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

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

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

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

    如需了解详情,请参阅实现新 API 的说明。

另请参阅

其他资源

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

博客文章