支援不同的語言和文化

透過集合功能整理內容 你可以依據偏好儲存及分類內容。

應用程式可內含特定文化專屬的資源。舉例來說,應用程式可以加入文化專屬字串,並翻譯成目前語言代碼的語言。建議您將文化專屬資源與應用程式的其餘部分區隔開來。Android 會根據系統語言代碼設定來解析語言和文化專屬資源。您可以使用 Android 專案中的資源目錄,為不同語言代碼提供支援。

您可以針對使用自家應用程式的使用者,指定專為相關文化量身打造的資源。您可提供任何適用於使用者語言和文化的資源類型。例如,以下螢幕截圖顯示應用程式根據裝置預設 (en_US) 語言代碼和西班牙文 (es_ES) 語言代碼分別顯示的字串及可繪製資源。

應用程式會根據目前語言代碼顯示不同的文字和圖示

圖 1. 應用程式會根據目前語言代碼使用不同資源

如果您是使用 Android SDK 工具建立專案 (請參閱建立 Android 專案相關說明),則工具會在專案頂層建立 res/ 目錄。這個 res/ 目錄提供各種資源類型的子目錄,還有一些用來保存字串值的預設檔案 (例如 res/values/strings.xml)。

支援不同語言並不侷限於使用語言代碼專屬的資源。部分使用者會選擇使用從右至左 (RTL) 指令碼的語言 (例如阿拉伯文或希伯來文),做為使用者介面語言代碼。其他使用者就算設定使用從左至右 (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-es/strings.xml

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

美國的國旗圖示 (預設語言代碼),/mipmap/country_flag.png

美國的國旗圖示

圖 2. 用於預設 (en_US) 語言代碼的圖示

西班牙的國旗圖示 (es_ES 語言代碼),/mipmap-b+es+ES/country_flag.png

西班牙的國旗圖示

圖 3. 用於 es_ES 語言代碼的圖示

注意事項:您可以針對任何資源類型使用語言代碼限定詞 (或任何設定限定詞),例如當您想提供點陣圖可繪項目的本地化版本時。詳情請參閱本地化的相關說明。

使用應用程式中的資源

您可以利用每項資源的 name 屬性,在原始碼和其他 XML 檔案中參照資源。

在原始碼中,您可以使用 R.<resource type>.<resource name> 語法參照資源。系統提供各種透過此方式接受資源的方法,

例如:

Kotlin

// Get a string resource from your app's Resources
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 from your app's Resources
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 使用者介面或 RTL 資料時,簡單的格式化可能會顯示錯誤或甚至無法閱讀的輸出文字。

如阿拉伯文、希伯來文、波斯文和烏都文等語言整體上都以 RTL 方向書寫,但部分元素 (例如數字和嵌入式的 LTR 文字) 則是以 LTR 方向書寫,夾雜在其他 RTL 文字中。此外,使用 LTR 指令碼的語言 (包括英文) 也是雙向語言,因為這類語言可能包含必須以 RTL 方向顯示的 RTL 嵌入式指令碼。

在多數情況下,應用程式會自行產生這類方向相反的嵌入式文字。應用程式會在本地化訊息中插入任意語言 (以及任意文字方向) 的文字資料。這種方向混合的現象通常都沒有明確指出反方向文字開始和結束的位置。應用程式產生的文字具有這些特徵,是造成大多數問題的原因。

雖然系統對雙向文字的預設處理方式通常可正常顯示文字,但當應用程式將文字插入本地化訊息時,系統仍可能無法正確顯示文字。在下列情況下,系統很有可能無法正確顯示文字:

  • 插入訊息最開頭的位置:

    PERSON_NAME 來電

  • 開頭是數字,例如在地址或電話號碼中:

    987 654-3210

  • 開頭是標點符號,例如在電話號碼中:

    +19876543210

  • 結尾為標點符號:

    確定嗎?

  • 原本就包含兩個方向:

    בננה 這個字詞是希伯來文的香蕉。

範例

舉例來說,假設某個應用程式有時需要顯示「你是不是要輸入 %s?」訊息,並在執行階段插入地址來取代 %s。由於應用程式支援不同的使用者介面語言代碼,因此訊息是來自語言代碼專屬的資源,並在使用 RTL 語言代碼時採用 RTL 方向。如果是希伯來文使用者介面,該訊息應顯示為:

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

但該建議可能來自資料庫,且當中未提供符合指定語言代碼的語言文字。舉例來說,假如相關地址是位於美國加州的某個地點,則該地址在資料庫中會顯示為英文。如果在 RTL 訊息中插入「15 Bay Street, Laurel, CA」這個地址,但未提供任何文字方向相關提示,則系統可能無法產生預期或正確的結果:

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

請注意,門牌號碼顯示在地址的右側,而不是如預期的左側,因此門牌號碼看起來更像奇怪的郵遞區號。如果在採用 LTR 文字方向的訊息中加入 RTL 文字,也可能會發生這種問題。

說明與解決方案

前述範例會發生問題的原因在於文字格式轉換程式並未指定「15」是地址的一部分,因此系統無法判斷「15」是屬於 RTL 文字且應置於這段文字之前,還是屬於 LTR 文字且應置於這段文字之後。

如要解決這個問題,請針對插入至本地化訊息中的「每段」文字,使用 BidiFormatter 類別中的 unicodeWrap() 方法。您不應使用 unicodeWrap() 的情況包括:

  • 文字要插入至機器可讀取的字串中,例如 URI 或 SQL 查詢。
  • 您知道該段文字已正確包裝。

unicodeWrap() 方法會偵測字串的方向,並以宣告該方向的萬國碼 (Unicode) 格式字元妥善包裝。由於現在「15」是出現在宣告為 LTR 的文字中,因此會顯示在正確的位置:

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

下列程式碼片段說明如何使用 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));

注意:如果應用程式指定的是 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 對應項目之間的對比:

通知區域位於畫面右上角附近靠右對齊,應用程式列內的選單按鈕位於左上角附近,畫面主要區域中的內容靠左對齊且會從左至右顯示,而返回按鈕則位於左下角附近並指向左側。 通知區域位於畫面左上角附近靠左對齊,應用程式列內的選單按鈕位於右上角附近,畫面主要區域中的內容靠右對齊且會從右至左顯示,而返回按鈕則位於右下角附近並指向右側。
圖 4. 畫面的 LTR 和 RTL 變化版本

在應用程式中新增 RTL 支援時,請務必謹記以下要點:

  • 只有在搭載 Android 4.2 (API 級別 17) 以上版本的裝置上使用時,應用程式才能支援 RTL 文字鏡像功能。如要瞭解如何在舊版裝置上支援文字鏡像功能,請參閱提供對舊版應用程式的支援一節。
  • 如要測試應用程式是否支援 RTL 文字方向,請使用開發人員選項進行測試,並邀請使用 RTL 指令碼的使用者來使用您的應用程式。

注意:如要查看版面配置鏡像功能相關的其他設計指南 (包括必須和不應建立鏡像的元素清單),請參閱雙向性質感設計指南。

如要建立應用程式中 UI 版面配置的鏡像,讓它根據 RTL 語言代碼顯示從右至左的版面配置,請完成以下各節所述的步驟。

修改版本和資訊清單檔案

請修改應用程式模組的 build.gradle 檔案和應用程式資訊清單檔案,如下所示:

build.gradle (Module: 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 文字方向。

主畫面小工具支援 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;
    }
}

注意:為避免發生相容性問題,請使用 Android SDK Build Tools 23.0.1 以上版本。

使用開發人員選項進行測試

在搭載 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

舉例來說,如果您使用的是以下程式碼:

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 (例如 LocaleManager 中的 getApplicationLocales()setApplicationLocales() 方法),可讓應用程式在執行階段設定與系統語言不同的語言。

    如果應用程式採用自訂應用程式內語言選單,應透過這些 API 確保使用者無論在何處選取語言偏好設定,都能獲得一致的使用者體驗。此外,公用 API 也有助於減少樣板程式碼的數量,可支援分割 APK,並支援應用程式自動備份功能來儲存應用程式層級的使用者語言設定。

    為了提供與 Android 舊版本的回溯相容性,AndroidX 中也提供同等的 API。建議使用 Appcompat 1.6.0-beta01 以上版本。

    詳情請參閱導入新版 API 的操作說明。

另請參閱

其他資源

如要進一步瞭解如何支援舊版裝置,請參閱下列資源:

網誌文章