喜帳面の日記

50歳越えおやじのASP.NET MVC への挑戦日記です。

Visual Studio Express 2012 for Web でいってみる 22.多言語対応をやってみた。

 

今回はResourcesを使った多言語対応にチャレンジしてみました。
初心者が行き当たりばったりにいろいろ動かしてみたときのメモなので、記載内容には勝手な解釈や誤りもあるかと思います。お気づきの点がありましたら、ご指摘よろしくお願いします。

 多言語対応って、自分のサイトには関係ないねって思ってはいるんですが、もし、もし、対応するとしたらどんな方法になるのかな?、簡単なら下準備だけしておこうと思い、ちょっと調べてちょっとだけやってみました。

なので、お手軽な手法をとってます。本当はもっとじっくり取り組むべき課題なのかもしれませんが、、、

さて、例によってWeb検索で参考にさせて頂いたページは

 

 
あと、以下の書籍の
4.3 入力の検証

 このあたりの情報をもとに、以下は、MVC4のテンプレートで生成される、「ユーザー登録」を英語対応してみたときのメモ書きです。

 ターゲットはこのページ

f:id:SannomiyaNotes:20130208162754p:plain

ASP.NET MVC 4 Web アプリケーション (Razor) のデフォルトで作成したプロジェクトの「登録」ページです。このページを英語でも表示されるようにします。

まず、今回採用した手法は、前述の各コンテンツ情報から、「リソース」を利用するのが最もお手軽そうな手法かなと思い、「リソース」に「文字列」を「アクセス修飾子 = Public」で登録してあっちこっちで使い回す方法を取ることにしました。

アクセス修飾子 = Publicにすることによるデメリットもあろうかと思いますが、この際(よくわかってないから)無視してしまいます。

1.リソースを格納するフォルダを作成する

特定のページグループで使用するローカルリソースは[App_LocalResources]フォルダ、すべてのページでページで使用できるグローバルリソースは[App_GlobalResources]フォルダに格納してHttpContextオブジェクトのメソッドで取り出すのが定石だったようですが、ローカルとグローバルを区別せず任意のフォルダを使用する方法が前述のコンテンツで記載されてましたのでそれに従うようにします。とはいえ、いろんなページに登場する文字列と、どう考えても1つのページにしか出てこないだろうという文字列はあるかと思いますんでその区分はしました。

今回用意したフォルダとファイルは以下の通りです。

f:id:SannomiyaNotes:20130208183223p:plain

 

 

ルート直下に

[Resources]フォルダを作成。そのフォルダ内に[Models]-[Account] , [Views]-[Account]といた風に、基本のフォルダ構造を[Resources]下に再現したような形式をとっています。そして[SharedStrings]フォルダを用意しました。

[SharedStrings]フォルダ以外のフォルダには、個別に対応するリソースファイルを配置しています。

例えば、[Views]-[Account]-[Register.cshtml]でのみ使用する文字列は、[Resources]-[Views]-[Account]-[Register.resx](デフォルト)か[Register.en.resx](英語用)から取り出し、

共通の文字列は[Resources]-[SharedStrings]-[Strings.resx](デフォルト)か[Strings.en.resx](英語用)から取り出します。

このあたりのフォルダ構造なんかは「お好み」ですきにしていいようです。

ただファイル名には規約「filename.XX.resx」があります。
[filename]は任意で、[XX]の部分はカルチャ名、en,it,fr,jpなどを指定します。
規定の言語用は「カルチャ」を省略します。

 .リソースを登録する

フォルダができたのでリソースファイルを登録します。

共通で使う文字列を[Resources]-[SharedStrings]-[Strings.resx]に登録します。

リソースの新規登録は、[SharedStrings]フォルダを右クリック、[追加]-[新しい項目]で[新しい項目の追加ダイアログ]を表示。ダイアログで[Web]-[全般]-[アセンブリ リソースファイル]を選択を指定し名前を入れて[追加]をクリックします。

登録画面が表示されます。

<Strings.resx>

f:id:SannomiyaNotes:20130208180246p:plain

アクセス修飾子を「Public」に変更します。

名前と値を登録します。

同様に英語版を用意します。

<Strings.en.resx>

f:id:SannomiyaNotes:20130208182208p:plain

同様に[Register.resx]と[Register.en.resx]、

[ValidationStrings.resx]と[ValidationStrings.en.resx]も登録。

<Register.resx>

f:id:SannomiyaNotes:20130208183802p:plain

<Register.en.resx>

f:id:SannomiyaNotes:20130208184048p:plain

 

<ValidationStrings.resx> 

f:id:SannomiyaNotes:20130208184610p:plain

<ValidationStrings.en.resx>

f:id:SannomiyaNotes:20130208184751p:plain

 

尚、各resxファイルのプロパティは以下の通り。

f:id:SannomiyaNotes:20130208185146p:plain

カスタムツール:PublicResXFileCodeGenerator

ビルドアクション:埋め込まれたリソース

カスタムツールが空白になっている場合は、resxファイルのアクセス修飾子がPublicになっているか確認してください。

これで、一通りの多言語化したい文字列のリソース登録の終了です。

実は「AccountController」のValidationで使用するエラーメッセージは他にもたくさんあり、本来はもっと登録しなきゃなんないのですが、今日のところはここまでにしときます。ちょっと手間ですね。

.Modelを変更する

 対象のクラスは、[Models]-[AccountModels.cs]のRegisterModelです。

変更前は

    public class RegisterModel
    {
        [Required]
        [Display(Name = "ユーザー名")]
        public string UserName { get; set; }

        [Required]
        [StringLength(100
                           , ErrorMessage = "{0} の長さは {2} 文字以上である必要があります。"
                           , MinimumLength = 6)]
        [DataType(DataType.Password)]
        [Display(Name = "パスワード")]
        public string Password { get; set; }

        [DataType(DataType.Password)]
        [Display(Name = "パスワードの確認入力")]
        [Compare("Password", ErrorMessage = "パスワードと確認のパスワードが一致しません。")]
        public string ConfirmPassword { get; set; }
    }

ざっと見たところ、Display(Name)、StringLength()やCompare()のエラーメッセージの文字列部分が対象になります。

以下のように変更してみました。

(プロジェクト名はMvc4ApplicationLとなっています。)

使用するNamespace を追加

using Mvc4ApplicationL.Resources.SharedStrings;
using Mvc4ApplicationL.Resources.Models.Account;

1行目のSharedStringsは共通使用する文字列を格納している[Strings.resx]のあるフォルダ 。

2行目は、[Account]グループで使用する[ValidationStrings.resx]のあるフォルダを指定しています。

RegisterModelの変更後は以下のようになりました。

public class RegisterModel
{
    //ユーザー名
    [Required]
    [Display(ResourceType = typeof(Strings), Name = "Resユーザー名")]
    public string UserName { get; set; }
    
    //パスワード
    [Required]
    [StringLength(100, ErrorMessageResourceType = typeof(ValidationStrings)
                     , ErrorMessageResourceName = "ResStringLength"
                     , MinimumLength = 6)]
    [DataType(DataType.Password)]
    [Display(ResourceType = typeof(Strings), Name = "Resパスワード")]
    public string Password { get; set; }

    //パスワードの確認入力
    [DataType(DataType.Password)]
    [Display(ResourceType = typeof(Strings), Name = "Resパスワードの確認入力")]
    [Compare("Password"
            , ErrorMessageResourceType = typeof(ValidationStrings)
            , ErrorMessageResourceName = "ResPasswordMustMatch"
            )]
    public string ConfirmPassword { get; set; }
}

Display(Name)の変更。ユーザー名の部分を例に取ると、

[Display(ResourceType = typeof(Strings), Name = "Resユーザー名")]

「Strings」の部分は、リソースクラス Mvc4ApplicationL.Resources.SharedStrings.Stringsを指定しています。

 そのクラス内の名前="Resユーザー名"の値を指定しています。

StringLength()の変更。

パスワードの長さチェック部の変更前は、

[StringLength(100
                   ,ErrorMessage = "{0} の長さは {2} 文字以上である必要があります。"
                   ,MinimumLength = 6
                    )]

これで、入力値が6文字未満の場合、「パスワードの長さは6文字以上である必要があります。」とエラーメッセージが表示されます。

変更後は

[StringLength(100
                 , ErrorMessageResourceType = typeof(ValidationStrings)
                 , ErrorMessageResourceName = "ResStringLength"
                 , MinimumLength = 6)]

「ValidationStrings」の部分は、リソースクラス Mvc4ApplicationL.Resources.Models.Account.ValidationStringsを指定しています。

そのクラス内の名前="ResStringLength"の値を指定しています。

リソース登録内容は

規定の言語:「{0} の長さは {2} 文字以上である必要があります。」

英語(en) :「{0} must be at least {2} characters long.」

これで、言語が英語の場合、「Password must be at least 6 characters long.」と表示されます。

Compare()の変更も同様にErrorMessageの部分を

ErrorMessageResourceType = typeof(ValidationStrings) :リソースクラス

ErrorMessageResourceName = "ResPasswordMustMatch":名前

の2つの引数に置き換えてOKでした。

なお [Required]については特に変更していませんが、省略された場合英語の場合では

「The [DisplayName] field is required.」と表示されます。

 

.Controllerを変更する

 対象のコントローラーは[AccountController.cs]です。Modelの変更と同様に日本語のメッセージ定数部分をリソースを使用するように置き換えます。が、大量にあるので今回は一部分のみ記載します。例えば、このクラスの最終部分にある

ErrorCodeToString(MembershipCreateStatus createStatus) このメソッドの

入力したユーザー名が既に使用されている場合のエラーメッセージ部分は以下のように変更しました。

switch (createStatus)
{
    case MembershipCreateStatus.DuplicateUserName:
      //return "このユーザー名は既に存在します。別のユーザー名を入力してください。";
        return Resources.Views.Account.Register.ResourceManager.GetString("ResDuplicateUserName");
 以下省略

ResourceManagerを直接呼び出します。

 

.Viewを変更する

対象のViewは、[Views]-[Account]-[Register.cshtml]です。

変更後のソースは以下のようになりました。

@model Mvc4ApplicationL.Models.RegisterModel

@using Mvc4ApplicationL.Resources.SharedStrings; @*追加*@
@using Mvc4ApplicationL.Resources.Views.Account; @*追加*@

@{
  @*ViewBag.Title = "登録";*@
    ViewBag.Title = @Strings.Res登録;
}

<hgroup class="title">
    <h1>@ViewBag.Title.</h1>
  @*<h2>新しいアカウントを作成します。</h2>*@
    <h2>@Register.ResTitleMassage</h2>         
</hgroup>

@using (Html.BeginForm()) {
    @Html.AntiForgeryToken()
    @Html.ValidationSummary()

    <fieldset>
        <legend>登録フォーム</legend>
        <ol>
            <li>
                @Html.LabelFor(m => m.UserName)
                @Html.TextBoxFor(m => m.UserName)
            </li>
            <li>
                @Html.LabelFor(m => m.Password)
                @Html.PasswordFor(m => m.Password)
            </li>
            <li>
                @Html.LabelFor(m => m.ConfirmPassword)
                @Html.PasswordFor(m => m.ConfirmPassword)
            </li>
        </ol>
      @*<input type="submit" value="登録" />*@
        <input type="submit" value=@Strings.Res登録 />


    </fieldset>
}

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

先頭に@usingでこのページで使用するリソースフォルダを指定してます。

文字列を取得したい部分で、「@ResourceFile.name」と記述すればOKです。

簡単ですよね。今回はてきとうなリソース名を付けてますが、本番ではしっかり命名規則を作る必要がありそうです。

そろそろ実行してみますが、その前に1つやっておくことがあります。

.Web.configを変更する

 <system.web>に以下の1行を追加しておきます。

<globalization culture="auto" uiCulture="auto" />

これで準備完了です。

デバッグ実行してみる

 ヘッダー部の「登録」リンクをクリックすると、規定の言語で「登録」ページが表示されます。

f:id:SannomiyaNotes:20130208162754p:plain

次にブラウザーの言語設定を英語に変更してみます。

IEの場合は、[ツール]-[インターネットオプション] [全般]の[デザイン]-[言語]ボタンをクリックして[言語の優先順位]ダイアログを表示して、言語の優先順位を変更します。

言語のリストに英語が存在しない場合は[追加]ボタンをクリックして、英語(米国)[en-US]を追加します。その上で英語を先頭に移動します。

f:id:SannomiyaNotes:20130209091243p:plain

設定が終わったら、登録ページを「最新の情報に更新(F5)」します。

f:id:SannomiyaNotes:20130209091452p:plain

英語で表示されました。

Validationを確認してみます。

[Required]

f:id:SannomiyaNotes:20130209091800p:plain

 [StringLength]と[Compare]

f:id:SannomiyaNotes:20130209092016p:plain

ユーザー名の重複チェック

f:id:SannomiyaNotes:20130209092151p:plain

と英語対応ができてきました。

今回は、言語指定をIEの「言語の優先順位」で設定しましたが、プログラムで変更する方法については、「冒頭の書籍」や以下のページが参考になります。

ASP.NET MVC 2 Localization complete guide」

http://adamyan.blogspot.jp/2010/02/aspnet-mvc-2-localization-complete.html#!/2010/02/aspnet-mvc-2-localization-complete.html

また、「冒頭の書籍」には言語別のViewを呼び出す手法についての記述もあります。

今回は以上です。

 

---  2013/02/14  追記  ---

アカウント回りの英語対応をやってみて、リソースのフォルダとファイルについては結局、以下のようにroot下の[Resources]、その中に[Global]フォルダを作り、システム全体で共用する文字列をに格納し、[SharedStrings]フォルダでは、コントロール単位で共用するリソースファイルを作成することにしました。モデル、コントローラー、ビューで文字列を共用するケースも多いので、コントローラー単位にリソースファイルを作成しています。

f:id:SannomiyaNotes:20130214182930p:plain

 

ちなみに文脈的にどうしても言語を判定して文章を分けたいケースが出てきました。文章の途中に変数を入れるケースなど、リソースファイルを使っての置き換えでは厳しい(英語力不足の為、、)ケースです。

その場合、C#側で言語を取り出し、ViewBagにセットしてビュー側でif文で分岐させました。例えば

<コントローラー>
using System.Threading;
//言語情報の取得
ViewBag.TwoLetterISOLanguageName = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;

 

<ビュー>
@*言語によってメッセージを切り替える*@
 @if (ViewBag.TwoLetterISOLanguageName == "en")
 {
     <p>Login User name is  <strong> @User.Identity.Name </strong></p>
 }
 else
 {
     <p> <strong> @User.Identity.Name </strong>としてログインしています。</p>
 }

追記以上です。

 ---  2013/05/09  追記  ---

続編追加しました。

Visual Studio Express 2012 for Web でいってみる 28.javascriptでResourceObjectを使う方法