Register now for Android Dev Summit 2019!

各種の言語と文化のサポート

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

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

アプリで現在のロケールに応じて異なるテキストとアイコンが表示されます

図 1. ロケールに応じて異なるリソースが使用されているアプリ

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

さまざまな言語をサポートするということは、ロケール固有のリソースを使用する以外にも対応すべき点がでてきます。たとえば、アラビア語やヘブライ語のように右から左へ書く(RTL)スクリプトを使用する言語を UI ロケールとして選択するユーザーもいます。また、UI ロケールとして英語などの LTR スクリプトを使用する言語を設定していても、RTL スクリプトを使用する言語のコンテンツを表示したり生成したりするユーザーもいます。両方のユーザーに対応するには、アプリで次の処理を実行する必要があります。

  • RTL ロケールに RTL UI レイアウトを採用する。
  • 書式設定されたメッセージに表示されるテキストデータの向きを検出して、宣言する。この処理は通常、テキストデータの向きを自動的に判断するメソッドを呼び出すだけで実行されます。

ロケール ディレクトリとリソース ファイルを作成する

ロケールのサポートを追加するには、res/ に追加ディレクトリを作成します。各ディレクトリ名が次の形式に従っている必要があります。

    <resource type>-b+<language code>[+<country code>]
    

たとえば、values-b+es/ には言語コードが es のロケールを対象とした文字列リソースが含まれます。同様に、mipmap-b+es+ES/ には言語コード es、国コード ES のロケールを対象としたアイコンが含まれます。実行時のデバイスのロケール設定に従って適切なリソースが読み込まれます。詳しくは、代替リソースを提供するをご覧ください。

サポートするロケールを決めたら、リソースのサブディレクトリとファイルを作成します。次に例を示します。

    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 メッセージに挿入すると、結果は予期しないもの、または間違ったものになります。

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

番地が住所の左側ではなく右側に表示されるため、郵便番号のようにみえてしまいます。LTR テキスト方向を使用するメッセージに RTL テキストを含める場合も、同じ問題が発生する可能性があります。

説明と解決策

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

この問題を解決するには、ローカライズしたメッセージに挿入するすべてのテキスト部分について BidiFormatter クラスの unicodeWrap() メソッドを使用します。ただし、次の場合は unicodeWrap() を使用しないでください。

  • URI や SQL クエリなど、コンピュータで扱うことのできる文字列にテキストを挿入する場合。
  • テキスト部分が適切に処理するもので囲まれているとわかっている場合。

unicodeWrap()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);
    

これにより、ロケールに合わせて数字が書式設定され、その際異なる数字セットが使用される場合があります。

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

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

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

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

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

通知領域は右上近くで右揃えになります。アプリバーのメニューボタンは左上近く、画面のメイン部分のコンテンツは LTR で左揃えで表示され、戻るボタンは左下近くに表示され左側を指しています。 通知領域は左上近くで右揃えになります。アプリバーのメニューボタンは左上近く、画面のメイン部分のコンテンツは RTL で右揃えで表示され、戻るボタンは LTR に表示され右側を指しています
図 4. LTR と RTL の画面

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

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

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

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

build ファイルと manifest ファイルを変更する

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

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 属性および 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 のバージョン、left 属性と right 属性の定義の有無、start 属性と end 属性の定義の有無に基づいて、UI 配置属性がどのように処理されるかを示しています。

表 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 スクリプトをサポートするために特定のレイアウト ファイルを追加するとします。このためには、次の例に示すように 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)以下の場合は、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)以上が実行されているデバイスでは、デバイス開発者向けオプションの [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 のバージョンに基づいて、以下のいずれかを実行します。

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

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

重力

アプリのコードで Gravity.LEFT または Gravity.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;
    }
    

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

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

マージンとパディング

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

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

関連ドキュメント