複数の言語と文化をサポートする

アプリには、特定の文化に固有のリソースが含まれていることがあります。たとえば、アプリには、現在のロケールの言語に翻訳された、文化固有の文字列が含まれていることがあります。一般に、文化固有のリソースを、アプリの残りの部分とは別個にするほうが良いでしょう。Android では、システム ロケール設定に基づいて言語固有および文化固有のリソースを解決します。複数ロケールのサポートを提供するには、Android プロジェクトの中でリソース ディレクトリーを使用します。

アプリ利用者の文化に合わせて調整されたリソースを指定することができます。ユーザーの言語や文化に適切なリソース タイプを提供することができます。たとえば、次のスクリーンショットは、端末のデフォルト(en_US)ロケールとスペイン語(es_ES)ロケールで、アプリが文字列リソースおよびドローアブル リソースを表示しているところを示しています。

このアプリでは現在のロケールに応じたテキストやアイコンを表示します

図 1. 現在のロケールに応じて異なるリソースを使用するアプリ

Android SDK Tools (Android プロジェクトの作成を参照)を使ってプロジェクトを作成した場合、プロジェクトのトップレベルに res/ ディレクトリが自動的に作られます。この res/ ディレクトリには、さまざまなリソースの種類のサブディレクトリがあります。また res/values/strings.xml など、文字列値を保持するいくつかのデフォルト ファイルもあります。

複数言語のサポートには、ロケール固有のリソースを使用する以上のことが関係します。スクリプトユーザーによっては、UI ロケールとして、アラビア語やヘブライ語などの右から左へ書く(RTL)スクリプトを使用する言語を選択します。UI ロケールとして、英語などの LTR スクリプトを使用する言語を設定しているにもかかわらず、RTL スクリプトを使用する言語のコンテンツを表示したり生成したりするユーザーもいます。それらの 2 種類のユーザーをサポートするには、アプリで次のことをする必要があります。

  • 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 ロケールに使用されるアイコン

注:ビットマップ ドローアブルのローカライズ バージョンを提供する場合など、すべてのリソースタイプについて、ロケール識別子(または何らかの構成識別子)を使用できます。詳細については、ローカライズをご覧ください。

アプリでリソースを使用する

ソースコードやその他の XML ファイルの中で、各リソースの name 属性を使用してリソースを参照できます。

ソースコードでは、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" />

メッセージ中のテキストを書式設定する

アプリで最も一般的な作業の 1 つは、テキストの書式設定です。ローカライズしたメッセージは、テキストや数値データを適切な位置に挿入することにより書式設定されます。残念ながら、RTL UI や RTL データを扱う際に、単純な書式設定では表示が不正確になったり、さらには解読不能なテキスト出力になったりする可能性があります。

アラビア語、ヘブライ語、ペルシャ語、ウルドゥー語などの言語は、全体としては RTL の方向で書かれます。しかし、数字や埋め込み LTR テキストなどの一部の要素については、RTL テキストの中に置かれているとしても、それらは LTR 方向で書かれます。英語など、LTR スクリプトを使用する言語も双方向です。というのは、RTL 方向で表示する必要のある埋め込み RTL スクリプトが含まれる場合があるからです。

ほとんどの場合、そのような埋め込みの逆方向テキストを生成するのはアプリ自体です。アプリは、ローカライズされたメッセージにも、任意の言語—および任意のテキスト方向—のテキスト データを挿入します。このような複数方向混在では、どこからどこまでが逆方向テキストなのかを示すものが含まれていないことも珍しくありません。アプリ生成のテキストにはこのような特徴があり、それらが問題の原因となります。

双方向テキストに関するシステムのデフォルト処理により通常は期待通りにテキストが表示されますが、アプリがローカライズ済みメッセージの中に逆方向テキストを挿入した場合には、適切に表示されない可能性があります。テキストが正しく表示されない例として、以下にいくつかの状況を示します。

  • メッセージの先頭に挿入される場合:

    PERSON_NAME から電話がかかってきました

  • 住所や電話番号の中で数字から始まる場合:

    987 654-3210

  • 電話番号などで区切り記号から始まる場合:

    +19876543210

  • 句読点記号で終わる場合:

    大丈夫ですか?

  • すでに 2 方向が含まれている場合:

    ヘブライ語の単語 בננה はバナナを意味します。

たとえば、アプリで、「%s のことですか?」というメッセージを表示することがあり、実行時にその%s を住所に置き換えることになっているとします。アプリでは、複数の UI ロケールをサポートしており、メッセージはロケール固有のリソースから取られるため、RTL ロケールが使用されているなら RTL 方向が使用されることになります。ヘブライ語 UI の場合、次のように表示されることになります。

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

しかし、ロケール言語のテキストが含まれないデータベースから候補が来る場合があるかもしれません。たとえば、問題の住所がカリフォルニアにある場合、それは英語のテキストを使用するデータベースの中にあります。テキスト方向に関するヒントを提供することなく「15 Bay Street, Laurel, CA」という住所を RTL メッセージの中に挿入した場合、結果は予期しないもの、または間違ったものになります。

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

家番号が意図したように住所の左側ではなく、住所の右側に表示されるため、家番号が奇妙な郵便番号であるかのようになってしまいます。LTR テキスト方向を使用するメッセージの中に RTL テキストを含める場合も、同じ問題が発生することがあります。

説明と解決策

前の例で問題が発生する原因は、テキスト書式設定で「15」が住所の一部であることを指定していないため、システムは「15」がそれに先行する RTL テキストの一部なのか、それともその後に来る LRT テキストの一部なのかを判別できないということにあります。

この問題を解決するには、ローカライズしたメッセージに挿入するテキストのすべての断片について、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 フレームワークに含まれる BidiFormatter のバージョンを使用します。それ以外の場合は、Support Library に含まれる BidiFormatter のバージョンを使用します。

数値を書式設定する

アプリのロジックの中で数字を文字列に変換するには、メソッド呼び出しではなく、書式設定文字列を使用します。

Kotlin

var myIntAsString = "$myInt"

Java

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

これにより数字はロケールに該当する方法で書式設定されます。それには異なる数字セットの使用も含まれる場合があります。

ペルシャ語や多くのアラビア語ロケールなど、独自の数字セットを使用するロケールの端末で SQL クエリを作成するために String.format() を使用する際、クエリに渡すパラメータが数字の場合に問題が発生します。というのは、数字はロケールの数字で書式設定されますが、それらの数字は SQL で無効な文字だからです。

ASCII 書式設定の数字を保持し、SQL クエリを有効な状態に保つためには、String.format() のオーバーロード バージョンのうち、最初のパラメータとしてロケールを指定するものを使用する必要があります。ロケール引数は Locale.US でなければなりません。

レイアウト ミラーリングをサポートする

RTL スクリプトを使用する人にとって望ましいのは、RTL ユーザー インターフェースです。これには、右揃えのメニュー、右揃えのテキスト、そして次に進むことを示す左向きの矢印が含まれます。

図 4 に、設定アプリの画面の LTR バージョンと、それに対応する RTL バージョンの比較を示します。

通知領域は右上隅付近で右揃えになっており、アプリ バーのメニュー ボタンは左上隅付近にあり、画面主要部分のコンテンツは左揃えで LTR で表示され、戻るボタンは左下隅付近にあって左矢印になっています。 通知領域は左上隅付近で左揃えになっており、アプリ バーのメニュー ボタンは右上隅付近にあり、画面主要部分のコンテンツは右揃えで RTL で表示され、戻るボタンは右下隅付近にあって右矢印になっています。
図 4. 画面の LTR 版と RTL 版

アプリに RTL サポートを追加する際には、次の点に注意することが特に重要です。

  • RTL テキスト ミラーリングがアプリでサポートされるのは、Android 4.2(API レベル 17)以上が実行されている端末で使用する場合のみです。旧型端末でテキスト ミラーリングをサポートする方法については、従来のアプリのためのサポートを提供するをご覧ください。
  • アプリで RTL テキスト方向がサポートされているかどうかをテストするには、開発者向けオプションを使用したテストを実行し、アプリを使用するよう RTL スクリプトを使用する人を招待します。

注:ミラーリングの対象となる要素と対象ではない要素のリストを含め、レイアウト ミラーリングに関連する付加的な設計ガイドラインについては、双方向マテリアル デザインのガイドラインをご覧ください。

アプリの中で UI レイアウトをミラーリングして、RTL ロケールで RTL 表示にするには、以降のセクションに示されている手順を実行します。

build ファイルと manifest ファイルに変更を加える

アプリのモジュールの build.gradle ファイルとアプリの manifest ファイルに、次のような変更を加えます。

build.gradle(モジュール: app)

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 レイアウト ミラーリングは、アプリの中で自動的には実行されません。

既存のリソースを更新する

既存の各レイアウト リソース ファイルの中で、left および right をそれぞれ start および end に変換します。そのようにするなら、フレームワークによりアプリの 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 のバージョン、left 属性と right 属性が定義されているかどうか、またstart 属性と end 属性が定義されているかどうかに基づいて、システムが UI の揃え属性をどう処理するかを示します。

表 2. ターゲット SDK のバージョンおよび定義されている属性に基づく、UI 要素の揃え動作

ターゲットが Android 4.2
(API レベル 17) 以降か?
left と right の定義? start と end の定義? 結果
Yes Yes Yes startend が解決され、leftright をオーバーライド
Yes Yes No leftright のみ使用
Yes No Yes startend のみ使用
No Yes Yes leftright を使用(startend は無視)
No Yes No leftright のみ使用
No No Yes start および endleft および right に解決

方向固有リソースと言語固有リソースを追加する

このステップには、複数の言語やテキスト方向のためにカスタマイズした値を含むレイアウト、ドローアブル、および値のリソース ファイルを追加することが関係します。

Android 4.2(API レベル 17)以上の場合、-ldrtl((layout-direction-right-to-left)および -ldltr(layout-direction-left-to-right)のリソース識別子を使用できます。既存のリソースの読み込みでの下位互換性を保つため、Android の旧バージョンでは、適切なテキスト方向を推測するために、リソースの言語識別子が使用されます。

ヘブライ語、アラビア語、ペルシャ語などの RTL スクリプトをサポートするために特定のレイアウト ファイルを追加するとしましょう。そのためには、次の例に示されているように、layout-ldrtl/ ディレクトリを res/ ディレクトリの中に追加します。

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)以下の場合は、start および end に加えて、left および right の属性も含めます。

レイアウトで 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)以上が実行されている端末では、端末開発者向けオプションForce RTL layout direction を有効にすることができます。この設定により、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 のバージョンに基づいて、以下の手順のうちのいずれか 1 つを実行します。

  • Android 4.3(API レベル 18)以下が実行されている端末の場合、-ldrtl リソース ファイルを追加および定義する必要があります。
  • Android 4.4(API レベル 19)以上の場合、ドローアブルを定義する際に android:autoMirrored="true" を使用できます。これにより、RTL レイアウト ミラーリングの処理がシステムによって自動的に実行されるようになります。

    注:android:autoMirrored 属性が動作するのは、双方向ミラーリングがドローアブル全体の単純なグラフィック ミラーリングとなるような単純なドローアブルの場合のみです。ドローアブルに複数の要素が含まれる場合、またはドローアブルを反転するとその解釈方法が変化するような場合は、自分でミラーリングを実行しなければなりません。可能なら常に双方向処理の達人に相談して、ドローアブルのミラーリングがユーザーにとって意味のあるものかどうかを確認してもらってください。

Gravity

アプリのコードで Gravity.LEFT または Gravity.RIGHT が使用されている場合、それらの値をそれぞれ Gravity.START および Gravity.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;
}

つまり、Gravity の値に start および end を使用している場合であっても、左揃え値および右揃え値を処理する既存のコードはそのまま使用できるということです。

注:Gravity の設定を適用する際には、Gravity.apply() のオーバーロード バージョンのうち、layoutDirection 引数が含まれるバージョンを使用してください。

マージンとパディング

アプリで RTL スクリプトをサポートするには、マージンとパディングの値に関連した以下のベスト プラクティスに従ってください。

  • 方向固有の属性 leftMargin および rightMargin ではなく、それに対応する getMarginStart() および getMarginEnd() を使用します。
  • setMargins() を使用する場合、アプリで RTL スクリプトを検出したなら、left 引数と right 引数を交換します。
  • アプリにパディングに関するカスタム ロジックが含まれている場合は、setPadding() および setPaddingRelative() をオーバーライドします。

関連ドキュメント