Xamarin逆引きTips
MvvmCrossで文字列をローカライズ(多言語化)するには?
MvvmCrossでのiOS/Androidアプリ開発において、ViewModelの文字列リソースを多言語化してローカライズする方法を解説する。
iOS/Androidアプリをストアに公開すると、日本だけではなく、英語圏やそれ以外の多くの国々で使用されることになるため、多言語化(国際化)とローカライズ(まとめてローカライズと表記)の重要性も高い。MvvmCrossにもローカライズ機構が用意されており、これを使用することで、プラットフォーム間で共通した方法によりローカライズを行うことができる。今回はMvvmCrossのJsonLocalizationプラグインを用いてローカライズを行う方法を解説する。
MvvmCrossの標準ローカライズ機能
MvvmCrossをプロジェクトに導入すると、Cirrious.MvvmCross.Localizationアセンブリが標準で追加される。この中にはローカライズされたテキストを提供するオブジェクトのインターフェースであるIMvxTextProvider
と、これを利用してローカライズリソースに対してバインディングを行うためのMvxLanguageBinder
とMvxLanguageConverter
が含まれている。
IMvxTextProvider
はパラメーターに渡された型とリソースキーを基に、対応する文字列リソースを返すインターフェースだ。このインターフェースを独自に実装することで、ローカライズ機能を作ることもできるが、リファレンス実装としてJsonLocalizationプラグインが用意されている。このプラグインを使用すれば、ViewModelごとにリソースキーと文字列を定義したJSONファイルを用意することでリソースの定義を行えるようになる。
JsonLocalizationプラグインを用いたローカライズの実施
JsonLocalizationプラグインは、JSONファイルからの文字列リソースの読み込みの機能は提供しており、アプリの起動中に言語を切り替えるといった要望に対しても柔軟に対応できるように考慮されている半面、実際に使用できるようにするための組み込みに多少の準備を必要とする。
今回のアプリでは、JsonLocalizationプラグインを使ったローカライズの一例として、起動時に日本語に設定されている端末では日本語が、それ以外の場合は英語が表示されるように実装する。
プロジェクトの準備
「Tips:MvvmCrossのプロジェクトをセットアップするには?」の手順に従い、MvvmCrossプロジェクトを作成する。このとき、ソリューションの作成時に[Create a project within solution directory.]にチェックを入れておくことを忘れないようにしていただきたい。
次に「Tips:MvvmCrossでWebBrowserプラグインを使用するには?」でWebBrowserプラグインを追加する際と同様の手順で、NuGetから以下のプラグインを各プロジェクトに追加する。
- MvvmCross - File Plugin
(MvvmCross.HotTuna.Plugin.File) - MvvmCross - ResourceLoader Plugin
(MvvmCross.HotTuna.Plugin.ResourceLoader) - MvvmCross - Json Plugin
(MvvmCross.HotTuna.Plugin.Json) - MvvmCross - JsonLocalisation Plugin
(MvvmCross.HotTuna.Plugin.JsonLocalisation)
ローカライズリソースの作成
ローカライズするためのリソースを定義するファイルを作成する。iOS/Androidで共通のファイルを使うことから、一度、プロジェクト外のところでファイルを作成しておき、プロジェクトにはリンクという形でそれらのファイルを含めるとよい。
まず、ソリューションファイル(.slnファイル)と同じフォルダーを開き、LanguageResource
というフォルダーを作成する。このフォルダーの中にViewModelごとにデフォルト定義となるJSONファイルを作成し、各言語のサブフォルダーを作成してその中にその言語へローカライズしたJSONファイルを作成することとなる(なお、今回の手順を用いる場合、アプリに含まれる全てのViewModelに対してJSONファイルを作成しておく必要がある。作成しない場合、実行時のログ出力にファイルが見つからない旨の警告が出力される)。
まずは、この中にデフォルト定義となる英語のファイルを作成する。FirstViewModel.json
というファイルを作成し、以下のような内容とする。
{
"Test": "Test Text",
"Sample1": "Sample 1 Text",
"Sample2": "Sample 2 Text"
}
|
キーにリソースキーを持ち、値に表示されるテキストが指定されたシンプルなJSONとなっている。
次に、日本語リソースを作成する。LanguageResource
フォルダーの中にja-JP
フォルダーを作成し、先ほど作成したFirstViewModel.json
をja-JP
フォルダーにコピーして、コピーしたファイルの内容を以下のように変更する。
{
"Test": "テストテキスト",
"Sample1": "サンプルテキスト1",
"Sample2": "サンプルテキスト2"
}
|
キーは同じだが、表示される内容である値の方は日本語になっている。
ここまでの操作で、ソリューションフォルダーの内容は以下のみのような構造になっているはずだ。
作成したLanguageResource
フォルダーをプロジェクトに含める。
iOSアプリ
iOSアプリのプロジェクトは、Resources
というフォルダーがプロジェクトにデフォルトで存在する。この中に、先ほど作ったLanguageResource
フォルダーをリンクとして追加すればよい。Xamarin Studioで行う場合は、Resources
フォルダーを右クリックし、(表示されるコンテキストメニューから)[追加]-[Add Existing Folder]を選択する。ファイル選択のダイアログでLanguageResource
フォルダーを選択すると、どのファイルを含めるかの選択が表示される。作成したJSONファイルにチェックを入れて[OK]ボタンをクリックする。次に表示されるダイアログは「どのように追加するか」を選択するものとなっているので、3つ目の選択肢である[Add a link to the file]を選択する(図2)。
Androidアプリ
同様に、AndroidはAssets
フォルダーに対してLanguageResource
フォルダーのリンクを作成する。
TextProviderBuilderの実装
JsonLocalizationプラグインでローカライズリソースを扱うには、MvxTextProviderBuilder
クラスのサブクラスとしてTextProviderBuilder
クラスを実装し、MvvmCrossの初期化処理の中に含める必要がある。MvxTextProviderBuilder
は、ローカライズされたJSONファイルを読み込み、IMvxTextProvider
に提供する。
まず、Coreプロジェクトを右クリックし、[追加]-[新しいフォルダー]の順にポイントしてServices
フォルダーを作成する。次に、このフォルダーを右クリックして[追加]-[新しいファイル]を開いて新規作成のダイアログを表示し、[General]の中から[空のクラス]を選択し、「TextProviderBuilder」という名前を入力し、[新規]ボタンをクリックしてファイルを作成する。作成されたTextProviderBuilder.csファイルを以下のように編集する。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Cirrious.CrossCore.IoC;
using Cirrious.MvvmCross.Plugins.JsonLocalisation;
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Localization;
using Cirrious.CrossCore;
namespace LocalizeSample.Core.Services
{
public class TextProviderBuilder : MvxTextProviderBuilder
{
public const string GeneralNamespace = "LocalizeSample"; // 1
const string RootFolderForResources = "LanguageResource"; // 2
public TextProviderBuilder()
: base(GeneralNamespace, RootFolderForResources)
{
}
protected override IDictionary<string, string> ResourceFiles // 3
{
get
{
return this.GetType() // 4
.GetTypeInfo()
.Assembly
.CreatableTypes()
.Where(t => t.Name.EndsWith("ViewModel"))
.ToDictionary(t => t.Name, t => t.Name);
}
}
}
public static class TextProviderClassExtension
{
public static string GetText(this MvxViewModel viewModel, string name) // 5
{
var loader = Mvx.Resolve<IMvxTextProvider>();
var type = viewModel.GetType();
return loader.GetText(TextProviderBuilder.GeneralNamespace, type.Name, name);
}
public static IMvxLanguageBinder CreateTextSource(this MvxViewModel viewModel) // 6
{
return new MvxLanguageBinder(TextProviderBuilder.GeneralNamespace, viewModel.GetType().Name);
}
}
}
|
MvxTextProviderBuilder
クラスは、コンストラクターにネームスペース(1)とローカライズリソースが入っているフォルダーの名前(2)を指定する。IMvxTextProvider
の仕組み上はネームスペースで分類できるようになっているものの、JsonLocalizatonプラグインを使用する場合は固定の値を使用するようになっている。ローカライズリソースが入っているフォルダーの名前については、先ほど作成したローカライズリソースを含むフォルダーの名前であるLanguageResourceを指定している。
また、このクラスは抽象クラスとなっており、これを継承するクラスは、アプリが使用するローカライズリソースのJSONファイル名の一覧であるResourceFiles
プロパティを実装する必要がある(3)。今回は、Coreプロジェクトに含まれている全ての型から、“ViewModel”で終わるクラスを列挙して使用している(4)。
また、今回はTextProviderを簡単に扱えるよう、MvxViewModelに対する拡張メソッドも定義する。GetText
拡張メソッド(5)はViewModelに対応するローカライズリソースからテキストを取得するメソッド、CreateTextSource
拡張メソッド(6)はLanguageバインディングで使用するTextSource
プロパティの値を作成するものだ。
MvxTextProviderBuilder
を実装したら、これをIoCコンテナーに登録する必要がある。CoreプロジェクトのApp.csファイルを開き、以下のように編集する。
using System.Globalization;
using Cirrious.CrossCore;
using Cirrious.CrossCore.IoC;
using Cirrious.MvvmCross.Localization;
using Cirrious.MvvmCross.Plugins.JsonLocalisation;
using LocalizeSample.Core.Services;
namespace LocalizeSample.Core
{
public class App : Cirrious.MvvmCross.ViewModels.MvxApplication
{
public override void Initialize()
{
CreatableTypes()
.EndingWith("Service")
.AsInterfaces()
.RegisterAsLazySingleton();
// この行を追加
SetupTextBuilder();
RegisterAppStart<ViewModels.FirstViewModel>();
}
// ----- ▼▼▼ ここから追加 ▼▼▼ -----
void SetupTextBuilder()
{
var builder = new TextProviderBuilder(); // 1
if (CultureInfo.CurrentUICulture.Name == "ja-JP")
{
builder.LoadResources(CultureInfo.CurrentUICulture.Name); // 2
}
var textProvider = builder.TextProvider;
Mvx.RegisterSingleton<IMvxTextProviderBuilder>(builder); // 3
Mvx.RegisterSingleton<IMvxTextProvider>(textProvider); // 4
}
// ----- ▲▲▲ ここまで追加 ▲▲▲ -----
}
}
|
まず、先ほど実装したTextProviderBuilder
クラスのインスタンスを作成する(1)。
TextProviderBuilder
は、デフォルトでLanguageResource
フォルダー直下のリソースがロードされた状態で作成されるが、ローカライズ済みのリソースが必要な場合はLoadResources
メソッドでLanguageResource
フォルダー内にあるサブフォルダーの名前を指定することで切り替えることができる。今回は、UIカルチャがja-JP(日本語)の場合に、ja-JP
フォルダーの内容を読み込むようにしている(2)。
TextProviderBuilder
にリソースが読み込まれたら、TextProviderBuilder
のTextProvider
プロパティからIMvxTextProvider
オブジェクトを取得し、Mvx.RegisterSingleton
メソッドを使ってTextProviderBuilder
(3)とIMvxTextProvider
(4)をそれぞれIoCコンテナーに登録する。
これでローカライズリソースの準備が整った。
ViewModel内でローカライズリソースを取得する
まず、FirstViewModelのデフォルトテキストをローカライズする。FirstViewModel.csファイルを以下のように編集する。
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Localization;
using LocalizeSample.Core.Services;
namespace LocalizeSample.Core.ViewModels
{
public class FirstViewModel
: MvxViewModel
{
// ----- ▼▼▼ ここから追加 ▼▼▼ -----
public FirstViewModel()
{
_hello = this.GetText("Test"); // 1
}
// ----- ▲▲▲ ここまで追加 ▲▲▲ -----
private string _hello = "Hello MvvmCross";
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
}
}
|
コンストラクターで、ローカライズ値“Test”に設定されている値を取得して、_hello
フィールドに直接代入している(1)。GetText
メソッドは先ほどTextProviderBuilder
とあわせて実装した拡張メソッドだ。
実行例
この状態でアプリを実行すると、端末の言語設定が英語の場合は英語で「Test Text」が、日本語の場合は「テストテキスト」が入力された状態で起動する。
ローカライズリソースにバインディングする
ローカライズする文字列リソースに対してViewModel内での処理が不要な場合、Viewからローカライズリソースに対して直接バインディングすることもできる。そのためには、ViewModelにTextSource
という名前でIMvxLanguageBinder
型のプロパティを実装する必要がある。
先ほどのFirstViewModelに実装するのであれば、以下のようになる。
using Cirrious.MvvmCross.ViewModels;
using Cirrious.MvvmCross.Localization;
using LocalizeSample.Core.Services;
namespace LocalizeSample.Core.ViewModels
{
public class FirstViewModel
: MvxViewModel
{
public FirstViewModel()
{
_hello = this.GetText("Test");
}
// ----- ▼▼▼ ここから追加 ▼▼▼ -----
private IMvxLanguageBinder _textSource = null;
public IMvxLanguageBinder TextSource // 1
{
get {
if (_textSource == null)
{
_textSource = this.CreateTextSource(); // 2
}
return _textSource;
}
}
// ----- ▲▲▲ ここまで追加 ▲▲▲ -----
private string _hello = "Hello MvvmCross";
public string Hello
{
get { return _hello; }
set { _hello = value; RaisePropertyChanged(() => Hello); }
}
}
}
|
TextSource
プロパティをViewModelに実装した(1)。
このプロパティでは、最初にアクセスされたときにIMvxLanguageBinder
オブジェクトを作成するようにしているが(2)、これもTextProviderBuilder
を作る際に用意したCreateTextSource
拡張メソッドを使用して作成するようにしている。
もし、アプリケーション共通で基底となるViewModelを用意する場合、その基底のViewModelにのみTextSource
プロパティを実装する形にするのもよい。
iOSアプリ
次に、iOSアプリにバインディング先のラベルを作成して、リソースを表示する。TouchプロジェクトのFirstView
クラスを以下のように編集する。
using Cirrious.MvvmCross.Binding.BindingContext;
using Cirrious.MvvmCross.Touch.Views;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using UIKit;
namespace LocalizeSample.Touch.Views
{
[Register("FirstView")]
public class FirstView : MvxViewController
{
public override void ViewDidLoad()
{
View = new UIView { BackgroundColor = UIColor.White };
base.ViewDidLoad();
// ios7 layout
if (RespondsToSelector(new Selector("edgesForExtendedLayout")))
{
EdgesForExtendedLayout = UIRectEdge.None;
}
var label = new UILabel(new CGRect(10, 10, 300, 40));
Add(label);
var textField = new UITextField(new CGRect(10, 50, 300, 40));
Add(textField);
// ----- ▼▼▼ ここから追加 ▼▼▼ -----
var sampleLabel1 = new UILabel(new CGRect(10, 90, 300, 40));
Add(sampleLabel1);
var sampleLabel2 = new UILabel(new CGRect(10, 130, 300, 40));
Add(sampleLabel2);
this.BindLanguage(sampleLabel1, "Sample1"); // 1
this.BindLanguage(sampleLabel2, v => v.Text, "Sample2"); // 2
// ----- ▲▲▲ ここまで追加 ▲▲▲ -----
var set = this.CreateBindingSet<FirstView, Core.ViewModels.FirstViewModel>();
set.Bind(label).To(vm => vm.Hello);
set.Bind(textField).To(vm => vm.Hello);
set.Apply();
}
}
}
|
TextSource
プロパティにバインディングしてローカライズする場合は、BindLanguage
拡張メソッドを使用する。ラベルなど、デフォルト・バインディング・プロパティが文字列のビューウィジェットの場合は、バインディング先のオブジェクトと、バインディングするリソースキーを指定することで、バインディングが完了する(1)。
デフォルト・バインディング・プロパティ以外のプロパティにバインディングする場合など、バインディング先のプロパティを変える場合は、第2引数に対象となるプロパティを参照することで対応できる(2)。
なお、BindLanguage
の操作はBindingSetの外で行っても構わない。
Androidアプリ
Androidの場合は、local:MvxBind
属性の代わりにlocal:MvxLang
属性を使用してバインディングを定義する。Droidプロジェクトの[Resources]-[layout]フォルダーにあるFirstView.axmlファイルを開き、以下のように編集する。
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:local="http://schemas.android.com/apk/res-auto"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<EditText
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello" />
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxBind="Text Hello" />
<!-- ----- ▼▼▼ ここから追加 ▼▼▼ ----- -->
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxLang="Text Sample1" /><!-- 1 -->
<TextView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:textSize="40dp"
local:MvxLang="Text Sample2" />
<!-- ----- ▲▲▲ ここまで追加 ▲▲▲ ----- -->
</LinearLayout>
|
言語リソースにバインディングする際は、local:MvxLang
属性を使用する。バインディング先のプロパティ名と、プロパティの代わりに参照するリソースキーを指定する(1)。
実行例
この状態でアプリを実行すると、追加したラベルに端末の言語設定が英語の場合は英語で「Sample 1 Text」「Sample 2 Text」が、日本語の場合は「サンプルテキスト1」「サンプルテキスト2」が表示された状態で起動する。
単純にローカライズしたテキストを表示するだけであれば、TextSource
プロパティだけ実装し、ローカライズリソースに対して直接バインディングできる。
※以下では、本稿の前後を合わせて5回分(第56回~第60回)のみ表示しています。
連載の全タイトルを参照するには、[この記事の連載 INDEX]を参照してください。
56. Xamarin.FormsでAzureモバイルサービスによるToDoアプリを作成するには?
ひな型プロジェクトが用意されているXamarin.iOSやXamarin.Androidではなく、Xamarin.FormsからAzureモバイルサービスを活用する基本的な方法を、簡単なToDoアプリを題材に解説する。
57. MvvmCrossで画像をバインディングするには?
MvvmCrossでのiOS/Androidアプリ開発において、画像のURLをViewへバインディングできるMvxImageViewの使い方を説明する。
58. 【現在、表示中】≫ MvvmCrossで文字列をローカライズ(多言語化)するには?
MvvmCrossでのiOS/Androidアプリ開発において、ViewModelの文字列リソースを多言語化してローカライズする方法を解説する。
59. MvvmCrossでViewModelからViewにイベントを通知するには?(Messengerパターン)
MvvmCrossでのiOS/Androidアプリ開発において、ViewModelからViewにイベントを通知するMessengerパターンの実装方法を紹介する。
60. XamarinのUIやコードの実行結果を簡単に確認できる「Sketches」を使うには?
Xamarin.Formsのレイアウトなどを、ビルドすることなくREPL環境で手軽に確認できるSketchesの使い方を紹介する。