各種の言語および文化をサポートする

アプリには、文化に固有のリソースが含まれていることがあります。たとえば、アプリは、現在のロケールの言語に翻訳された文化固有の文字列を使用できます。文化固有のリソースは、それ以外の部分と切り離すことをおすすめします。Android は、システムのロケール設定に基づいて、言語固有のリソースや文化固有のリソースを解決します。さまざまなロケールをサポートするには、Android プロジェクト内でリソース ディレクトリを使用します。

アプリユーザーの文化に合わせてカスタマイズしたリソースを指定できます。また、ユーザーの言語や文化に適した各種のリソースタイプを提供することが可能です。たとえば、下記のスクリーンショットの場合、アプリは、文字列リソースとドローアブル リソースを、デバイスのデフォルト ロケール(en_US)とスペイン語ロケール(es_ES)で表示しています。

現在のロケールに応じて異なるテキストやアイコンを表示するアプリ

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

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

さまざまな言語をサポートする場合、ロケール別のリソースを使用すること以外にも対応すべき点があります。たとえば、アラビア語やヘブライ語のように RTL(右から左)記述法を使用する言語を UI ロケールとして選択するユーザーもいます。また、英語などの LTR 記述法を使用する言語を UI ロケールとして設定しているユーザーであっても、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 ロケールの場合に使用されるアイコン

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

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

ソースコードや各種 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" />
    

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

アプリでよく行われるのがテキストの書式設定です。ローカライズ メッセージを書式設定するには、テキストや数値データを適切な位置に挿入します。残念ながら、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 テキストに含まれるのか、その後の 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 フレームワークに含まれているバージョンの 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 ユーザー インターフェースを優先して使います。これには右揃えのメニュー、右揃えのテキスト、次に進むことを示す左向きの矢印が含まれます。

たとえば、設定アプリの画面の LTR バージョンと RTL バージョンの比較を図 4 に示します。

通知領域は右上隅付近に右揃えで配置されます。アプリバーのメニューボタンは左上隅付近に配置されます。画面のメイン部分のコンテンツは LTR で左揃えで表示されます。[戻る] ボタンは左下隅付近に左側を指して表示されます。通知領域は左上隅付近に左揃えで配置されます。アプリバーのメニューボタンは右上隅付近に配置されます。画面のメイン部分のコンテンツは RTL で右揃えで表示されます。[戻る] ボタンは右下隅付近に右側を指して表示されます。
図 4: LTR バージョンと RTL バージョンの画面

アプリに RTL サポートを追加する場合は、特に以下の点に注意する必要があります。

  • RTL テキスト ミラーリングがアプリ内でサポートされるのは、Android 4.2(API レベル 17)以降を搭載しているデバイスで使用する場合に限られます。古いデバイスでテキスト ミラーリングをサポートする方法については、旧式アプリ向けのサポートを提供するをご覧ください。
  • アプリが RTL テキスト方向をサポートしているかテストするには、開発者向けオプションを使用してテストを実行し、RTL 記述法を使用してアプリを使用するユーザーを招待します。

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

アプリの中で UI レイアウトをミラーリングして RTL ロケールで RTL 表示にするには、次のセクション内で手順を説明します。

ビルドファイルとマニフェスト ファイルを編集する

アプリ モジュールの build.gradle ファイルとアプリ マニフェスト ファイルを次のように編集します。

build.gradle(Module: app)

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

AndroidManifest.xml

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

注: アプリのターゲットが Android 4.1.1(API レベル 16)以前の場合、android:supportsRtl 属性と、アプリのレイアウト ファイル内にあるすべての start 属性値および end 属性値は無視されます。この場合、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

ターゲット SDK バージョンや、left 属性および right 属性の定義の有無、start 属性および end 属性の定義の有無に基づいて、システムが UI 配置属性を処理する方法を表 2 に示します。

表 2: ターゲット SDK バージョンと定義済み属性に基づく UI 要素配置処理

ターゲットは Android 4.2(API レベル 17)以降か?
left と right は定義されているか? start と end の定義 結果
startend が解決され、leftright をオーバーライド
× leftright だけを使用
× startend だけを使用
× leftright を使用(startend は無視)
× × leftright だけを使用
× × startendleftright に変換

方向別のリソースや言語別のリソースを追加する

ここでは、各種の言語やテキスト方向に合わせたカスタム値を含む特殊なバージョンのレイアウト、ドローアブル、値のリソース ファイルを追加する手順について説明します。

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 に加えて、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)以降を搭載しているデバイスの場合、デバイス開発者向けオプションの [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 記述法をサポートするには、マージン値とパディング値に関する以下のベスト プラクティスに従うようにしてください。

  • 方向固有の属性である leftMarginrightMargin ではなく、それに相当する getMarginStart()getMarginEnd() を使用します。
  • setMargins() を使用する場合、アプリが RTL 記述法を検出したら、left 引数と right 引数の値を交換します。
  • アプリがカスタム パディング ロジックを持つ場合は、setPadding()setPaddingRelative() をオーバーライドします。

関連ドキュメント