GrapeCity.devlog

グレープシティ開発支援ツールの最新情報をお届けします。製品のTIPSやリリース情報、イベントのお知らせなどをいち早く発信中です。

Xamarin.iOSで開発したアプリをローカライズする

この記事は Xamarin その1 Advent Calendar 2017 - Qiita の21日目です。

グレープシティ株式会社で開発ツールのプロダクトマーケティングを担当する福地と申します。
2016年のAdvent Calendarでは、Xamarin.Formsアプリのローカライズをテーマに書きましたが、2017年もテーマは同じローカライズです。

2016年はXamarin用コンポーネントの「Xuni(ズーニー)」を提供していましたが、2017年はその製品が昇格(?)して、.NET Framework用コンポーネントセット「ComponentOne Studio」の一部になりました。
「ComponentOne Studio for Xamarin」として、Xamarin.Forms、Xamarin.Android、Xamarin.iOS向けのUIコンポーネントを提供しています。

ワールドワイドで提供している製品ですので、コンポーネントの内部はもちろん、ドキュメントやサンプルも国際化する必要があります。
そのため、ローカライズ技術とは切っても切れない縁があるのです。

サンプルをバージョンアップして実行

2016年は、シンプルなXamarin.Formsのサンプルアプリを作成し、UIに表示する文字列を日本語と英語で切り替えました。

まず、このサンプルプロジェクトを最新環境に置き換えます。 開発環境のVisual Studio for Macは更新済みなので、Xamarin.FormsのパッケージをNuGetから取得して更新します。
2.5.0.122203(2017年12月20日現在の最新)に更新しました。

単純なアプリなのでそのまま動くはずです。
しかし、予想に反しうまくいきません。

f:id:GrapeCity_dev:20171220185630p:plain

図のようにAndroidは日本語表示されていますが、iOSは英語表記のままです。 シミュレーター上部に"通信事業者"と表示されているので、デバイスの設定が日本語であることはわかります。
しかし、画面に現在のカルチャー設定を表示している部分が、iOSではen-USで英語のモードです。つまり、アプリ画面を表示するiOSの動作としては正しいことを意味します。このことからアプリ側に問題があると考えられます。

iOSで正常動作しない

そこでiOSのバージョンを変更してみます。 同じアプリをiPhone 7 (10.3.1)のシミュレータで起動したところ、正常に日本語表示されました。

左:iPhone 7 (10.3.1)、右:iPhone 7 (11.2)

f:id:GrapeCity_dev:20171220195156p:plain

このことから、問題はアプリとiOS 11にありそうです。 原因を調査するためXamarinのバグを調べると以下の問題が報告されていました。

59596 – Xamarin iOS not get the correct CurrentCultureInfo

「カルチャー情報(CurrentCultureInfo)がiOSで正しく取得できない」
iOS 11でアプリを実行した画面では、en-USと表示されていますからどうやらこの現象のようです。

この問題は、Xamarin側で修正されるようですが、「Target Milestone:15.6」 との記載がありますので、次期バージョン(執筆時点でバージョンは15.5)まで解決を待たなくてはいけません。

結論としてこの記事を執筆した時点では、
「iOS 11でXamarin.Formsのローカライズ機能は正常に動作しない」
ということになります。


Xamarin.iOSのローカライズ

前述の問題の回避策にはなりまりませんが、XamarinプラットフォームにはXamarinネイティブとしてiOSアプリを開発する方法がありますので、Xamarin.iOSで開発するアプリをローカライズすることにします。

Xamarin.Formsのアプリと同様に、ボタンをタッチする単純なアプリをXamarin.iOSで作ります。

アプリの開発方法についてはここでは解説を割愛しますが、今回はVisual Studio 2017の環境でおおまかに以下のような流れで作成しました。 ビルドやiOSシミュレータを実行するためのXamarin Mac Agentを稼働させるmacOSは別途用意し、Visual Studioからリモート接続可能な状態に設定済みです。

  1. Visual Studio 2017で、「新しいプロジェクト」を作成
    テンプレートは[Visual C#]-[iOS]-[Universal]-[単一ビューアプリ(iOS)]を選択
  2. main.storyboardを選択して画面を作成
    以前はXcodeを利用して画面作成をする必要がありましたが、現在はVisual Studioで実行できます。
  3. 画面にコントロールを追加
    main.storyboard にラベル(UILabel)を2つ、ボタン(UIButton)を1つ追加しそれぞれに命名

ローカライズの初期設定

Xamarin.iOSのローカライズは、Xcodeで開発するネイティブのiOSアプリの方法とほぼ同じです。
まず、多言語のUIを表示するための準備を行います。プロジェクトにあるInfo.plistを編集し、以下のキーに値を設定します。

  • CFBundleDevelopmentRegion デフォルトの言語。StoryBoardで表示する言語になります。
  • CFBundleLocalizations ローカライズ対象の言語です。配列として複数指定できます。

Info.plistは、Visual Studioではなく直接テキストエディタで編集したほうが簡単です。直接コードを追加することができます。
このサンプルではベースの言語を英語(en)とし、日本語環境は日本語表示(ja)をします。その値を設定したコード例です。Info.plist<dict>タグで囲まれた最後尾に追加して保存します。

<key>CFBundleDevelopmentRegion</key>
    <string>en</string>
<key>CFBundleLocalizations</key>
<array>
    <string>ja</string>
</array>

ローカライズリソースの準備

ユーザーインタフェースの表示文字列は、言語設定に応じて言語を切り替えることになります。
そのために言語に対応した文字列リストを作成し、別ファイルで管理します。

  1. プロジェクトにフォルダを新規作成 以下のフォルダを作成します。
    プロジェクト直下:Base.lproj
    Resourcesフォルダの下:
    英語(en)用 : en.lproj
    日本語(ja)用: ja.lproj
  2. 作成したフォルダにファイルを作成 Base.lprojに、Main.storyboardを移動します。
    en.lprojja.lprojには、Main.stringsの名称でプレーンテキストファイルを作成します。

完成したフォルダ構成は以下のようになります。

f:id:GrapeCity_dev:20171220200807p:plain

次に、作成したMain.stringsに、文字列を設定していきます。
このファイル名はストーリーボードであるMain.storyboardに関連付けた命名です。

ストーリーボードに配置したLabelやButtonなどのコントロールと表示する文字列を関連付けたリストの作成です。
各コントロールには、Localization IDが割り当てられています。 Main.storyboardのデザイン画面で、コントロールを選択するとそのプロパティページにLocalization IDが表示されています。このIDをコントロールのオブジェクト名にして、その文字列表示用のプロパティに対して値を設定していきます。

f:id:GrapeCity_dev:20171220201312p:plain

ここで設定したボタンの場合、Localization ID226です。 そこに表示する文字列を指定していきます。

/* 英語 用  Resources/en.lproj/Main.strings */
"226.normalTitle" = "Touch";

/* 日本語用 Resources/ja.lproj/Main.strings */
"226.normalTitle" = "タッチ";

ここで注意したいのが、ストーリーボードとプロパティ名が異なることです。
前述のボタン(UIButton)の場合、表示する文字列はストーリーボード上は「Text」ですが、「normalTitle」に設定します。
同様にラベル(UILabel)の場合は小文字の「text」になります。

これでストーリーボードのローカライズが完了し、英語、日本語のそれぞれの環境で画面の表記がそれぞれの設定になりました。

画面だけでなく、コードの内部でもローカライズした文字列が必要になります。次にそれを解説します。

Localizable.stringsの利用

コードでローカライズした文字列を使う場合は、Localizable.stringsファイルを利用します。ファイル名を変更すると正しく表示されないので注意が必要です。
前述のフォルダ構成の図は、すでに設置済みの状態です。このファイルもそれぞれの言語用に用意します。

Localizable.stringsに記述する内容はキーと値のペアで構成されます。実行中に該当するキーが無い場合は、キーが文字列として表示できるので、キーをデフォルト言語で記述しておけば簡略化もできます。

/* 英語 用  Resources/en.lproj/Localizable.strings */
"message" = "Touch {0} times.";

/* 日本語用 Resources/ja.lproj/Localizable.strings */
"message" = "{0}回タッチされました。";

Localizable.stringsに設定した内容は、コード内で呼び出して利用します。 NSBundleクラスのLocalizedStringメソッドで、キーを指定して実行すると、キーに対応したそれぞれのロケールに設定している文字列が取得できます。

// タッチ回数を表示するためのメッセージをLocalizable.stringsから取得
string msgstring = NSBundle.MainBundle.LocalizedString("message", "optional");

以上で完成です。 上記を含めC#のコードを記述したのは'ViewController.cs'のみです。そのコード全体も記載しておきます。

using Foundation;
using System;

using UIKit;

namespace LocalizationSample
{
    public partial class ViewController : UIViewController
    {
        // タッチ回数をカウントする変数
        int count = 0;
        // タッチ回数を表示するためのメッセージをLocalizable.stringsから取得
        string msgstring = NSBundle.MainBundle.LocalizedString("message", "optional");
        public ViewController(IntPtr handle) : base(handle)
        {
        }
        public override void ViewDidLoad()
        {
            base.ViewDidLoad();
            // 起動時にCultureInfoを取得して表示
            cultureText.Text += System.Globalization.CultureInfo.CurrentCulture.ToString();
        }
        public override void DidReceiveMemoryWarning()
        {
            base.DidReceiveMemoryWarning();
        }

        // ボタンをタッチしたタイミングで発生
        partial void BtnTouch_TouchUpInside(UIButton sender)
        {
            // タッチ回数をラベルに表示
            count += 1; 
            msgLabel.Text = String.Format(msgstring, count.ToString());
        }
    }
}

表示結果

日本語、英語それぞれのロケールで表示した結果が以下です。

f:id:GrapeCity_dev:20171221085509p:plain

iOS 11の iPhone 8 シミュレータでも正しく表示できています。 ローカライズ用のリソースを設定したMain.stringsファイルとLocalizable.stringsファイルに記述した内容は単純な文字列です。
そのため、そのままXcodeを利用したiOSアプリ開発に利用できます。逆に、既存のiOSアプリからこのリソース取り出し、そのままXamarin.iOSのアプリに利用することも可能です。

Xamarin.Formsは独自フォーマットのResxファイルを利用するので、それと比較すると容易に移行できることがわかります。 その前提として、はじめからローカライズを想定して文字列リソースを別途もつような設計であることも必要です。

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

Xamarin.iOS用 GitHub - fukuchima/LocalizationSample.iOS

Xamarin.Forms用 GitHub - fukuchima/LocalizationSample

おわりに

冒頭で紹介したXamarin.Formsのバグは回避策もあるのでそれを解説することも検討したのですが、いずれ無駄になると思われる情報を解説するよりは、基礎的な内容でも役に立つ情報を公開するほうが良いと考え変更しました。
他の誰かが私が直面した壁にぶつかることを回避し、お役立ていただけたら幸いです。


Xamarin プラットフォーム向けコンポーネント

冒頭でも宣伝しましたがこの記事を執筆する元になっているのが、 Xamarinで利用できるUIコンポーネントの「ComponentOne Studio」の製品リリース作業です。
製品に含まれるサンプルプロジェクトは、同じ方法でUI文字列の日本語化を行っています。サンプルプロジェクトには評価用のライセンスも付属し、そのまま動きますのでご利用ください。

ComponentOne Studio for Xamarin (コンポーネントワンスタジオ) | グレープシティ株式会社