データベースを使用したASP.NET MVCアプリケーションのローカライズ



この記事の焦点は限定され、データベース全体のローカライズについて説明します。たとえば、リソースファイル(resx)を使用してローカライズを行う方法について詳しくは、 MVC 2:ローカライズガイドをご覧ください。 ビューを使用したローカライズについては、リンクもあります。



最初に、サイトのローカライズのオプションについて簡単に説明し、ResourceProviderFactoryを作成する例を示してから、デモ用の小さなアプリケーションを作成します。





ローカリゼーションオプション



多くの議論や記事では、2つのローカライズオプションのみが言及されています。たとえば、 ASP.NET MVC 3国際化の記事では、多くのリンクを見つけることができ、次の点を強調しています。

-リソースファイル(resx)

-異なる「ビュー」を使用(ビュー)

最初の方法は通常、静的変数に使用されます:フィールド名、検証など。 2番目を使用することの大きな欠点は、同じコードをコピーするために多くの手作業を行う必要があることです。レイアウトをわずかに変更する必要がある場合でも、多くの言語でサイトの構造を想像することは難しく、ファイルの数は膨大になり、時には翻訳がディレクトリに分割されます、もちろんより視覚的になりますが、拡張性には多くのことが望まれます。 私の場合、管理パネルから追加された動的コンテンツを翻訳する必要がありました。管理パネルからresxファイルを編集するオプションを検討しませんでしたが、アマチュアの言うように、実装を自分で見つけることができます。

-データベースを使用したローカライズ

もちろん、これら3つのオプションをすべて組み合わせることができます。



実装例



Entity Framework Code Firstを使用するため、空のMVC 3プロジェクトを作成していることをすぐに言う必要があります。この記事ではメンバーシッププロバイダーを書き換えません。これを行う方法の例については、 カスタムメンバーシッププロバイダーを参照してください 。 「管理パネル」は一般公開されます。もちろん、Stephen Sandersonの本で示されているように、設定ファイルを介して承認を実装することもできましたが、この記事は別のものです。



倉庫のパロディを作成してみましょう。4つのフィールドを持つ製品テーブルがあります。

-識別子

-製品名(データベースを使用して翻訳します)

-価格(ローカライズ中の検証の問題を示すためにこのフィールドが必要です)

-配達日(以前と同様)



次のステップでは、Productクラスを作成し、データ注釈を使用して属性を設定します(このオプションが気に入らない場合は、Fluent APIを使用できます。大規模なプロジェクトでアクセスする必要があります)。DbContextを作成します。



public class Product { public int ProductId { get; set; } [Required] [StringLength(128)] public string Name { get; set; } [Required] public decimal Price { get; set; } [Required] public DateTime ImportDate { get; set; } } public class ProductDbContext : DbContext { public DbSet<Product> Products { get; set; } }
      
      







次に、コントローラーとすべてのアクション(アクション)を自動的に生成します。少し怖いので、スタイルを追加する必要がありますが、生成には少し異なるアプローチを使用することをお勧めします。 たとえば、次のような1つのビューでの作成と編集の組み合わせ: ASP.NET MVCに関する悪い注意。 1つのアイデアを削除するパート1(そして唯一のアイデアは、できるだけコピーアンドペーストを少なくしてください。



その結果、次のようなテーブルができました。







少しグーグルのResourceProviderFactoryに目を向けると、MSDN Extending the ASP.NET 2.0 Resource-Provider Modelのかなり古い記事と、実装例を含むResourceProviderFactoryクラスの説明が見つかりましたが、4番目のフレームワークについてです。 同じコードプロジェクトに既製の例がありますが、これも基礎とすることができます: ASP.NET 2.0カスタムSQL Server ResourceProvider



次に、翻訳を保存するためのクラスを作成します。



 public class GlobalizationResource { public int GlobalizationResourceId { get; set; } [Required] [StringLength(128)] public string ResourceObject { get; set; } [Required] [StringLength(128)] public string ResourceName { get; set; } [Required] [StringLength(5)] public string Culture { get; set; } [Required] [StringLength(4000)] public string ResourceValue { get; set; } }
      
      







データベースコンテキストに追加することを忘れないでください。 MSDNからcodeprojectと例の間の平均的な実装を取得しました。約150行あるため、コードは記事の最後にダウンロードできます。 そして、プロバイダーを構成に追加します。



  <system.web> <globalization enableClientBasedCulture="true" resourceProviderFactoryType="DbLocalizationExample.Models.CustomResourceProviderFactory" uiCulture="auto" culture="auto" /> ... </system.web>
      
      







すべては問題ありませんが、ローカライズを確認するために言語を選択する機能が必要です。Cookieを使用してローカライズを保存します(セッションを使用することはお勧めしません。ここでも、言語を選択する必要があります)。 afana.meからのアイデアを基礎として、そのようなクラスを取得します。



 public static class CultureHelper { private static readonly List<string> Cultures = new List<string> { "ru-RU", // first culture is the DEFAULT "en-US", }; /// <summary> /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US" /// </summary> /// <param name="name">Culture's name (eg en-US)</param> public static string GetValidCulture(string name) { if (string.IsNullOrEmpty(name)) return GetDefaultCulture(); // return Default culture if (Cultures.Contains(name)) return name; // Find a close match. For example, if you have "en-US" defined and the user requests "en-GB", // the function will return closes match that is "en-US" because at least the language is the same (ie English) foreach (var c in Cultures) if (c.StartsWith(name.Substring(0, 2))) return c; return GetDefaultCulture(); // return Default culture as no match found } public static string GetDefaultCulture() { return Cultures.ElementAt(0); // return Default culture } public static string GetCultureFromCookies(HttpRequest request) { string cultureName = null; // Attempt to read the culture cookie from Request HttpCookie cultureCookie = request.Cookies["_culture"]; if (cultureCookie != null) { cultureName = cultureCookie.Value; } else if (request.UserLanguages != null) { cultureName = request.UserLanguages[0]; // obtain it from HTTP header AcceptLanguages } // Validate culture name return GetValidCulture(cultureName); // This is safe } private static string AcceptLanguage() { return HttpUtility.HtmlAttributeEncode(System.Threading.Thread.CurrentThread.CurrentUICulture.ToString()); } public static IHtmlString MetaAcceptLanguage<T>(this HtmlHelper<T> html) { return new HtmlString(String.Format(@"<meta name=""accept-language"" content=""{0}"" />", AcceptLanguage())); } public static IHtmlString GlobalizationLink<T>(this HtmlHelper<T> html) { return new HtmlString(String.Format(@"<script src=""../../Scripts/globalization/cultures/globalize.culture.{0}.js"" type=""text/javascript""></script>", AcceptLanguage())); } }
      
      







ここで、Cookieを設定および読み取るためのアクションを追加するだけです。



 public ActionResult SetCulture(string culture) { // Validate input culture = CultureHelper.GetValidCulture(culture); // Save culture in a cookie HttpCookie cookie = Request.Cookies["_culture"]; if (cookie != null) { cookie.Value = culture; // update cookie value } else { cookie = new HttpCookie("_culture"); cookie.HttpOnly = false; // Not accessible by JS. cookie.Value = culture; cookie.Expires = DateTime.Now.AddYears(1); } Response.Cookies.Add(cookie); return RedirectToAction("Index"); }
      
      







また、カルチャを設定し、キャッシュを使用するためにGetVaryByCustomStringをチェックするためのGlobal.asaxのロジック。



 protected void Application_AcquireRequestState(object sender, EventArgs e) { string cultureName = CultureHelper.GetCultureFromCookies(Request); // Modify current thread's culture Thread.CurrentThread.CurrentCulture = CultureInfo.CreateSpecificCulture(cultureName); Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(cultureName); } public override string GetVaryByCustomString(HttpContext context, string arg) { // It seems this executes multiple times and early, so we need to extract language again from cookie. if (arg == "culture") // culture name (eg "en-US") is what should vary caching { string cultureName = CultureHelper.GetCultureFromCookies(Request); return cultureName.ToLower();// use culture name as cache key, "es", "en-us", "es-cl", etc. } return base.GetVaryByCustomString(context, arg); }
      
      







翻訳ロジックに関するいくつかの言葉:私のデータベースはデフォルト言語を保存します。 Productオブジェクトだけですが、翻訳を追加したい場合は、ビューに書き込みます。



 @(Culture == "ru-RU" ? item.Name : HttpContext.GetLocalResourceObject("/Home/Index", "Product_" + item.ProductId))
      
      





これにより、データベースにデフォルト値が自動的に追加されます。 最初のパラメーターはわかりやすくするためにパスに似ていますが、任意の文字列を使用できます(発表ではデータベース内での制限は128です)、2番目は一意の識別子です。



レイアウトで、言語を選択する機能を追加します。

 <div class="language"> <span>@Html.ActionLink("rus", "SetCulture", "Home", new { culture = "ru-RU" }, null)</span> <span>@Html.ActionLink("eng", "SetCulture", "Home", new { culture = "en-US" }, null)</span> </div>
      
      







実行できますが、存在しなかったため、コンテキストを変更し(リソースのクラスを追加しました)、EFはテーブルからのデータの表示を拒否しました。 [表示] -> [ 他のウィンドウ ] -> [ パッケージマネージャーコンソール ]に移動して、次のように入力します(各行を個別に入力)。



 Update-Package EntityFramework Enable-Migrations
      
      





これで、移行を作成してデータベースを更新できます。



 Add-Migration AddGlobalizationResources Update-Database
      
      





しかし、ここで動揺しています。スタジオは、移行履歴のない古いEFでデータベースを作成したため、データベースを手で削除しないように、インデックスにそのような行を追加します(移行に反対する場合は、Application_Startに追加する方が正しいです)ただし、これによりすべてのデータが削除されることに注意してください):



 Database.SetInitializer(new DropCreateDatabaseIfModelChanges<ProductDbContext>());
      
      





コンパイルしてアクセスした後、削除してください。将来的には移行のすべてのメリットを享受できるためです: EF 4.3 Automatic Migrations Walkthrough



作業の結果は次のようになります。







英語版はデータベースに自動的に作成され、ロシア語版は製品に保存されます。 データベースを編集するためのロジックはあなたに任せます。複雑なことは何もありません。



クライアント検証



言語を切り替えるとき、10進数と日時に問題があります。 ロシア語の場合は「4.00」、英語の場合は「4.00」です。 日付にも問題があります:12/21/2012および12/21/2012。 これらの問題を解決するために、 グローバライズを使用し、jquery ui datapickerを有効にして形式を自動的に設定し、日付の入力を簡単にします。



以下をレイアウトに追加します(グローバリゼーションの「コア」、特定の言語のグローバリゼーション、クライアント側のメタタグ、数値の検証とjquery ui datapickerの変更のための一般的なスクリプト):

 <script src="@Url.Content("~/Scripts/globalization/globalize.js")" type="text/javascript"></script> @Html.GlobalizationLink() @Html.MetaAcceptLanguage() <script src="@Url.Content("~/Scripts/common.js")" type="text/javascript"></script>
      
      







これはクライアント側の検証のほんの一部です;ローカライズの例はここにあります: ASP.NET MVC 3国際化-パート2(NerdDinner)



まとめ



独自のリソースプロバイダーを作成する方法、その作業を示す小さなアプリケーションを作成する方法、およびこのトピックに関する詳細情報を参照できる共有リンクについて説明しました。 Jon Skeetは彼の著書「C#in Depth」で、ここに示されているコードは単なる例であると書いているので、ここから取ったコードがあなたのために働くことを保証することはできません。 私は完全な翻訳キャッシュを使用します。多くの情報がある場合、ライフタイムを設定するなど、翻訳を徐々にダウンロードする必要があります。 翻訳を編集するときは、常にキャッシュをクリアして、データがすぐに表示されるようにする必要があることに注意してください(これは、翻訳編集ロジックを実装するときです)。







プロジェクトはここからダウンロードできます(Visual Studio 2010): リンク(2.89 Mb) (この例は、ダイナミクスのローカライズのみを示しています。静的な翻訳を追加すると、桁違いに簡単です。したがって、コードには記事の説明のみが含まれます)



ソース



参照資料



ASP.NET 2.0リソースプロバイダーモデルの拡張

ResourceProviderFactoryクラス

ASP.NET 2.0カスタムSQL Server ResourceProvider

ASP.NET MVC 3国際化

ASP.NET MVC 3国際化-パート2(NerdDinner)





フリーマンA.サンダーソンS.-プロASP.NET MVC 3フレームワーク第3版-2011

ジュリアラーマンとローワンミラー-プログラミングエンティティフレームワーク:コードファースト-2012



注:このASP.NET MVC 3国際化ガイドをローカライズする場合、MVC 4で実行されない基本クラスのExecuteCore()はMVC 4 ExecuteCoreでは機能しないことに注意してください




All Articles