支持不同的语言和文化

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

最好将特定于文化的资源与应用的其余部分分开。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文件,其中包含您的字符串值。

支持不同的语言不仅仅是使用特定于区域设置的资源。一些用户为其 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区域设置的图标。

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

在您的应用中使用资源

使用每个资源的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 数据时,简单的格式化可能会显示不正确甚至无法读取的文本输出。

阿拉伯语、希伯来语、波斯语和乌尔都语等语言是从右到左(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 (模块: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

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

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

在 Android 4.2(API 级别 17)及更高版本中,您可以使用 -ldrtl(layout-direction-right-to-left)和 -ldltr(layout-direction-left-to-right)资源限定符。为了保持与现有资源的向后兼容性,旧版本的 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 代码,您可以通过设置 absoluteGravitylayoutDirection 相匹配来使其适应此更改。

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

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 的说明。

另请参阅

其他资源

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

博文