ComponentOne Information

ComponentOne Studio/Wijmo/Xuniの最新情報を公開中

Xamarin.FormsアプリのUIを日本語以外でも表示する

この記事は Xamarin Advent Calendar 2016 (その1) の14日目です。

Xuni(ズーニー)プロダクトマネージャの福地と申します。
Xamarin関連製品を担当しており、業務上必要に迫られて調査した件について紹介します。調査の過程でつまづいた細かい点も盛り込みました。
f:id:ComponentOne_JP:20161207115924p:plain

国際化、ローカライズ

リソースファイルを使ったローカライズの方法は、.NET Frameworkの手法を引き継いでいることもあり、いたることろに情報があります。この記事はそれらをXamarin.Forms向けに焼き直した内容です。調査したのは少し前ですが、サンプルを含めてすべて作り直しましたので鮮度は保っています。

アプリを多言語に対応してリリースする場合には、ユーザーインタフェースなどをアプリが利用される言語に合わせる必要があります。このことを国際化やローカライズといいます。

ローカライズは広範囲にわたります。UIに表示される単語だけでなく、単位や数字の表現、画像や映像がその対象になる場合もありますし、言語によってはGenderつまり男性/女性名詞の使い分けや文字の表示方向なども対象になります。
すべてを考慮していたのでは範囲が広すぎるので今回の記事ではUIに表示する文字列を日本語と英語で表すことを中心に解説します。

ちなみに国際化、ローカライズを「I18n」「L10n」と表現するのを見かけたことがあると思います。

これは、英単語を省略した記述方法です。 英語では、
国際化 = Internationalization
ローカライズ = Localization
とても長い単語になるため これを中抜きにした表現です。先頭と最後尾の文字で間にある文字数を挟むことで省略しています。

Internationalization
I123456789012345678n →間に18文字入るので18
I18n


Localization
L1234567890n →間に10文字入るので10
L10n

Xamarin.Forms アプリの例

それではローカライズ例をサンプルアプリをもとに解説します。
ベースにするのは、Xamarin.Formsで開発する単純なアプリです。ボタンをタッチするとタッチ数をカウントアップします。また、この記事全体の簡素化とXamarin Studioでソリューションを利用することもふまえて、ターゲットアプリはAndroidとiOSのみにします。

f:id:ComponentOne_JP:20161207102236p:plain

サンプルのプロジェクト構成は以下のようになります。 f:id:ComponentOne_JP:20161208120642p:plain

  • LocalizationSample : PCLプロジェクト
  • LocalizationSample.Droid : Android用プロジェクト
  • LocalizationSample.iOS : iOS用プロジェクト

今回はシンプルなXamarin.Formsアプリなのでコードや設定を行うのはすべてPCLであるLocalizationSampleのプロジェクトが対象です。

Visual Studioでリソースファイルを作成

Xamarin.Formsのアプリは、.NET Frameworkの仕組みを利用したローカライズが可能です。
Android、iOS、UWPアプリでUIを共通化する目的でXamarin.Formsを採用していますので、PCLにUIに表示する文字列を設定すれば各アプリで適切な表示ができます。
具体的にはリソースファイル(.RESX)をPCLに作成し、UIに表示する文字列をロケールごとに用意します。それを表示に利用することになります。

その手順は以下です。ここではVisual Studio 2015を利用して解説します。Xamarin Studioでも同様のことが可能ですが、Visual Studioを採用している理由はのちほど説明します。

1.RESXファイルの作成
PCLプロジェクトにRESXファイルを新規に追加します。これがベースのリソースファイルになります。
RESXファイルはXML形式で記載されたファイルです。対象文字列を記載するためのキーと、その文字列、そしてコメントで構成されています。ローカライズ対象の文字列はすべてのこのファイルに記述します。
各言語対応する場合は、このファイルと同じものを用意して対象文字列部分をそれぞれの言語に変更することになります。

ファイル名は意味がわかるようにAppResources.resxとし、複数言語用のファイルをまとめて管理できるように「Resources」フォルダを作成してその下に保存します。

f:id:ComponentOne_JP:20161207111741p:plain

リソースファイルを作成すると自動でVisual Studio内にリソースエディタが起動します。リソースエディタを利用すると、一覧表示の形式でリソースの追加や削除、編集が行えます。
このツールは現時点(2016年12月)でXamarin Studioには用意されていません。通常のテキストエディタでXMLファイルを直接操作することになります。そのためこの解説ではVisual Studioを利用しています。

リソースファイルを追加するとプロジェクトには2つのファイルが追加されます。

AppResources.resx
リソースをXML形式で記述したファイル

AppResources.Designer.cs    
RESXファイルの各要素用のPartialクラスが記述されています。  
自動で作成されるファイルです。手動で編集する必要はありません。

2.リソースの追加
リソースエディタを使用してリソースの要素を追加します。サンプルアプリのスクリーンショットから、表示する内容、つまり言語によってUIを変更する必要がある要素は「タイトル、メッセージ、ボタンテキスト」の3つであることがわかります。

f:id:ComponentOne_JP:20161206173359p:plain

それぞれをリソースエディタに入力します。キーになる「名前」も含め自由に設定できます。

f:id:ComponentOne_JP:20161206174532p:plain

上記を保存したRESXファイルの内容です。データ部分のみを抽出しています。

<data name="buttonText" xml:space="preserve">
  <value>タッチ</value>
  <comment>ボタンに表示</comment>
</data>
<data name="Title" xml:space="preserve">
  <value>ローカライズサンプル</value>
  <comment>タイトル</comment>
</data>
<data name="TouchMSG" xml:space="preserve">
  <value>回タッチされました。</value>
  <comment>メッセージ</comment>
</data>

また、リソースエディタで文字列を入力する際に、SHIFT+Enterを入力することで改行できます。リソースエディタ上で改行表示される値は、そのまま改行付きで表示されXMLファイルにも改行した状態で保存されています。
f:id:ComponentOne_JP:20161206175110p:plain

リソースファイルの利用

ベースになるリソースファイルができたので、アプリのUI文字列を変更します。現在アプリに表示されている文字列は、コード内で直接記述していました。その部分をリソースファイルから取得するように改造します。

1.ビルド時の設定
作成したリソースをPCLプロジェクトがビルドされる時に、アプリ内に含まれるように設定する必要があります。
ソリューションエクスプローラーからAppResources.RESXのプロパティを変更します。
設定するプロパティは2つです。すでに以下の内容に設定済みの場合は変更不要です。
カスタムツール:ResXFileCodeGenerator
ビルドアクション:埋め込みリソース
f:id:ComponentOne_JP:20161207162520p:plain

2.AppResources クラス
コード内からRESXファイルに定義した各文字列要素を取得するには、AppResources クラスを利用します。
このクラスを利用してRESXファイルの「名前(name)」に定義した文字列を取得して各プロパティに設定します。 文字列を直接設定していたコードを、取得したリソースを設定するコードに置き換えました。

f:id:ComponentOne_JP:20161207111144p:plain

//  (省略)
using LocalizationSample.Resources; 

//  (省略)

public MainView()
{

    //Title = "ローカライズサンプル";
    //button.Text = "タッチ";
    Title = AppResources.Title;
    button.Text = AppResources.buttonText;

    button.Clicked += Button_Clicked;
    label.HorizontalOptions = LayoutOptions.Center;

    Content = new StackLayout
    {
        Orientation = StackOrientation.Vertical,
        VerticalOptions = LayoutOptions.Center,
         Children = { label, button }
    };
}

private void Button_Clicked(object sender, EventArgs e)
{
    count += 1;
    // label.Text = count.ToString() + "回タッチされました。";
    label.Text = count.ToString() + AppResources.TouchMSG;
}

現在リソースにある文字列は、コードに直接記述した内容と同じなのでアプリの実行結果は変わりませんが、これでアプリ側の多言語化の準備が整いました。

英語表示用のリソースを作成

これまでのリソースファイルの設定だと、実は「英語表示に設定されたデバイスでUIが日本語表示される設定」でした。既定のリソースファイルであるAppResources.resxに日本語リソースを設定したためです。

リソースで定義した内容でUI文字列が設定されることを確かめるためにも、AppResources.resxに英語の文字列を設定して改めて実行してみます。リソースエディタでそれぞれの値を以下の内容に変更しました。

名前 新しい値
Title ローカライズサンプル L10n Sample
buttonText タッチ Touch
TouchMSG 回タッチされました times touch

そのまま実行すると英語表示に変わります。

f:id:ComponentOne_JP:20161206180828p:plain

日本語表示用のリソースを作成

既定のリソースが英語表記になったので、ここから分岐して日本語リソースを別ファイルに作成します。

1.AppResources.ja.resxの作成
前述の方法でResourcesフォルダにリソースファイルを追加します。この時に日本語用にするため、ファイル名をAppResources.ja.resxに設定します。

既定のリソースファイルに、日本語であることを識別するための.jaを挿入したファイル名にしてください。この識別を元にアプリはUIに使用するリソースを選択します。デバイスに設定した言語に合う識別子が無い場合は、既定のリソースであるAppResources.resxが適用されます。

なおjajpに誤記すると既定のリソース(英語表記)が採用され日本語表示されません。見た目で気づきにくいのでご注意ください。 参考までに各言語の識別用文字列の一部を以下に列挙しました。 ja : 日本語
fr : フランス語
de : ドイツ語
kr : 韓国語
zh-Hans : 中国語(繁体字)

作成したファイルには先に作成したAppResources.resxの内容をすべてコピーします。「名前」の部分がキーになるので、すべてのリソースファイルで一致している必要があります。キーが異なってもエラーにはなりませんが、既定のリソース(ここではAppResources.resx)にキーが存在するものであればその内容が表示されます。
他のRESXファイルの内容を複製する場合は、リソースエディタを使用して「行選択でコピーした内容」を新しく作成したRESXファイルに貼り付けると簡単にできます。

複製が完了したら、AppResources.ja.resxの「値」をすべて日本語にします。コメントはオプションなので、どの言語で記述されていてもも他に影響は与えません。

2.英語環境で実行
アプリを実行する実機やエミュレーターの「言語」が、Englishの状態でアプリを実行すると、前項で指定した英語の文字列がUIに表示されます。日本語が設定されていれば日本語表示です。つまりロケールに応じて変更されることになります。

  • 言語を切り替える
    簡易的なテストの場合は、コードで設定することで言語の変更が可能です。 System.Globalization.CultureInfo クラスを利用して、表示言語用のCultureを作成します。
    それをAppResourceクラスのCultureに設定することで、デバイスやエミュレータの設定に関係なく対象言語用のリソースで表示できます。
// リソースのCulture 情報をコードで強制的に設定(日本語用)
AppResources.Culture = new System.Globalization.CultureInfo("ja-JP");
// リソースのCulture 情報をコードで強制的に設定(英語用)
AppResources.Culture = new System.Globalization.CultureInfo("en-US");
  • 不自然な表現を訂正
    文章を表示する場合、日本語と英語では表現を変更しないと不自然でやさしくないUIになってしまうケースがあります。
    今回の例ではメッセージは、 “Tapped 3 times.” のほうがより良い翻訳表現です。UIとしてもっと良い表現はありますが、ここでのポイントは表示する数値である3が文中に含まれていることです。プログラム上は変数が含まれることを意味します。

    数値を挟む前後の言葉をそれぞれリソース文字列にしてから文章を組み立てる方法もありますが、スマートな方法とは言えません。この場合はパラメータを利用します。
    プログラミングの基礎的な部分なのであえて解説するまでもありませんが、ローカライズの観点で見落としがちな部分です。サンプルを変更するコード例を記載します。

    まず、パラメータを含めた文章を作成しそれぞれのリソースを変更します。
    AppResources.resx : Tapped {0} times.
    AppResources.ja.resx: {0} 回タッチしました。

    次に、コードを数値をパラメータとして渡すように変更します。

label.Text = String.Format(AppResources.TouchMSG, count.ToString());

これで英語版も自然な形の文章で表示されます。
ただし、リソース要素の中にパラメータ用の「{0}」が含まれていることを周知しておかないと、他の人が無駄なところで時間を浪費する結果になりかねませんのでご注意ください。

UIにかぎらず

これまで解説してきたように、リソースファイルを使って取得するのは文字列です。 文字列の形であればさまざまな用途に利用できます。 例えば、言語やロケールに応じて異なるWebサイトにアクセスさせる場合や、アプリの中の固定データを定義しておくこともできます。

XAMLで作成した画面のローカライズ

ここまではC#でリソースを設定する方法でUIを変えてきました。しかしXamarin.FormsでのUIはXAMLを利用するケースが多くなります。 このサンプルアプリの画面をXAMLで作ったものも用意して変更します。

PCLプロジェクトに新たにForms XAML PageをXamlMainViewという名称で作成しこれに置き換えます。呼び出し元のApp.csも変更します。

// XAMLで画面定義
var content = new XamlMainView();
MainPage = new NavigationPage(content);
マークアップ拡張

XAMLで記述した画面要素をローカライズするためには、別途クラスを作成してその中でリソースを設定する必要があります。
また、XAMLの各要素のTextプロパティにはデータバインディングを利用する場合と同様に、マークアップ拡張機能で{}を利用して記述します。
XAMLの記述内容は以下です。ContentPageTitleButtonTextに設定しています。

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="LocalizationSample.XamlMainView"
             xmlns:local="clr-namespace:LocalizationSample;assembly=LocalizationSample"
             Title ="{local:TranslateExtension Key = Title}">

  <StackLayout Orientation="Vertical" VerticalOptions="Center">
    <Label x:Name ="label" Text="" HorizontalOptions="Center"/>
    <Button x:Name ="button" 
            Text="{local:TranslateExtension Key = buttonText}" 
            HorizontalOptions="Center"/>
  </StackLayout>

</ContentPage>

Click時のイベント記述は同じです。

public XamlMainView()
{
    InitializeComponent();
    // イベントの処理
    button.Clicked += (sender, e) => {
        count += 1;
        label.Text = String.Format(AppResources.TouchMSG, count.ToString());
    };
}

また、XAMLのコンテンツプロパティ属性機能を利用することで、XAML記述を簡略化できます。上記で作成した「TranslateExtensionクラス」に、追加することで省略記述した場合のプロパティをKeyに設定することができます。

マークアップ拡張したTranslateExtensionクラスのコード例

using System;
using System.Resources;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace LocalizationSample
{
    [ContentProperty("Key")]  // Keyを規定のプロパティに指定 
    public class TranslateExtension : IMarkupExtension
    {
        public string Key { get; set; }

        // IMarkupExtension インタフェースの ProvideValueメソッドを実装
        public object ProvideValue(IServiceProvider serviceProvider)
        {
            if (string.IsNullOrEmpty(Key))
                return "Error";

            // Keyプロパティに指定されたキーをもとにリソースから文字列を取得
            ResourceManager resmgr = Resources.AppResources.ResourceManager;
            return resmgr.GetString(Key) ?? "Error";
        }
    }
}

XAMLでマークアップ拡張を指定したコード例

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="LocalizationSample.XamlMainView"
             xmlns:local="clr-namespace:LocalizationSample;assembly=LocalizationSample"
             Title ="{local:TranslateExtension Key = Title}">

  <StackLayout Orientation="Vertical" VerticalOptions="Center">
    <Label x:Name ="label" Text="" HorizontalOptions="Center"/>

    <!-- マークアップ拡張であるTranslateExtensionを指定した記述例 -->
    <!-- 
    <Button x:Name ="button" 
            Text="{local:TranslateExtension Key = buttonText}" 
            HorizontalOptions="Center"/>
    -->

    <!-- TranslateExtension で ContentPropertyに "Key" を指定している場合の記述例 -->
    <Button x:Name ="button" 
            Text="{local:Translate buttonText}" 
            HorizontalOptions="Center"/>

  </StackLayout>

</ContentPage>

実行結果は、C#で記述した時と同様に表示しました。

f:id:ComponentOne_JP:20161207112943p:plain

マークアップ拡張を利用することは、コードで表示内容を変更できることを意味します。リソースの文字列を加工して設定することも可能です。

Xamarin Nativeアプリのローカライズ

Xamarin.Android、Xamarin.iOSも同様にローカライズが可能です。これらはAndroid、iOSネイティブアプリの方法に近いやりかたです。
この作業も行っていますので別の機会に解説したいと思います。

ローカライズの入り口

今回解説した部分はローカライズの入り口です。しかしUIは最初に目にする部分であるためアプリの重要な部分です。
アプリを多言語化する予定はなくても、コードと表示を分割していくメリットはあるのでUI文字列はリソースに定義していくと良いと思います。

作成したサンプルプロジェクトはGitHubで公開しています。 実用的なアプリではありませんのでローカライズ用の見本としてご利用ください。

GitHub - fukuchima/LocalizationSample

参考情報

XamarinのWebサイトにローカライズに関する情報とサンプルがあります。UIだけでなく画像を使ったサンプルを提供していますので、より深く知りたい方は公式ドキュメントをご参照ください。

developer.xamarin.com

おわりに

最後に宣伝です。 Xamarinで利用できるUIコンポーネントのXuni(ズーニー)に含まれるサンプルプロジェクトは、これまで説明してきた方法でUI文字列の日本語化を行っています。コードはトライアル版で確認できますのでご利用ください。サンプルプロジェクトには評価用のライセンスも付属しています。


追記:この記事で紹介している「Xuni(ズーニー)」は、2017年7月に提供を終了し、新たに「ComponentOne Studio for Xamarin」として提供しています。

ComponentOne