Skip to content

6. بین‌المللی‌سازی ویوها

در اینجا به مسئله بین‌المللی‌سازی نما می‌پردازیم. این یک موضوع پیچیده است که در مقاله زیر توسط اسکات هانسلمن به خوبی توضیح داده شده است:

[http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx]

ابتدا، بیایید تعاریف او از اصطلاحات مختلف مرتبط با بین‌المللی‌سازی نماها را مرور کنیم:

بین‌المللی‌سازی (i18n)
اطمینان از اینکه برنامه از زبان‌ها و بومی‌سازی‌های مختلف پشتیبانی می‌کند
محلی‌سازی (l10n)
اطمینان از اینکه برنامه از یک جفت زبان/محلی خاص پشتیبانی می‌کند
جهانی‌سازی
ترکیب Internationalisation و Localisation
زبان
زبان گفتاری – مشخص‌شده با کد ISO (fr: فرانسوی، es: اسپانیایی، en: انگلیسی، ...)
محلیGlobalisationترکیب Internationalisation و QZXW2HTML
یک گویش از زبان – که همچنین با کد ISO مشخص می‌شود (en_GB: انگلیسی بریتانیایی، en_US: انگلیسی آمریکایی، و غیره)

بیایید با یک مثال اولیه به سراغ حل مسئله برویم.

6.1. محلی‌سازی اعداد حقیقی

ما می‌توانیم یک ناهنجاری را در فرم ورودی قبلی تشخیص دهیم:

 

برای عدد حقیقی، [0,3] را وارد کردیم و پذیرفته نشد. باید [0.3] را وارد کنیم:

 

بنابراین فرمت مورد انتظار فرمت آنگلو-ساکسون است، نه فرمت فرانسوی. جست‌وجو در اینترنت برخی راه‌حل‌ها را آشکار می‌کند. در اینجا یکی از آن‌ها آمده است.

اقدامات [GET] و [POST] به شرح زیر تغییر می‌کنند:


    // Action13-GET
    [HttpGet]
    public ViewResult Action13Get()
    {
      return View("Action13Get", new ViewModel11());
}

    // Action13-POST
    [HttpPost]
    public ViewResult Action13Post(ViewModel11 modèle)
    {
      return View("Action13Get", modèle);
}

ویوی [Action13Get.cshtml] به جز اسکریپت‌های جاوااسکریپت، با ویوی [Action12Get.cshtml] یکسان است:


<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action13Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
...
  <script type="text/javascript" src="~/Scripts/myscripts.js"></script>

</head>

توجه: در خط ۵، نسخه jQuery را با نسخه ویژوال استودیوی خود تطبیق دهید.

  • در خط ۹، اسکریپت [myscripts.js] اضافه شده است. این به شرح زیر است:

// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}

$.validator.methods.date = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseDate(value));
}

jQuery.extend(jQuery.validator.methods, {
  range: function (value, element, param) {
    //از افزونه Globalisation برای تجزیهٔ مقدار استفاده کنید        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

// هنگامی که سند بارگذاری می‌شود
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

من در خط ۱ نشان داده‌ام که این اسکریپت کجا پیدا شده است. من تلاش نمی‌کنم آن را توضیح دهم چون خودم هم آن را درک نمی‌کنم. گاهی جاوااسکریپت می‌تواند نسبتاً رمزآلود باشد. در خطوط ۴، ۹ و ۱۵، از ابجکتی به نام [Globalize] استفاده شده است. این توسط کتابخانهٔ جهانی‌سازی JQuery فراهم می‌شود که می‌توان آن را از طریق [NuGet] به‌دست آورد:

  • در [1]، بسته‌های [NuGet] را از پروژه [Exemple-03] مدیریت کنید؛
  • در [2]، بسته‌ها را به‌صورت آنلاین مشاهده کنید؛
  • در [3]، عبارت [globalization] را تایپ کنید؛
  • در [4]، بسته [Globalize] را از پروژه JQuery نصب کنید.

پس از نصب بسته [Globalize]، یک شاخه جدید در پوشه [Scripts] ظاهر می‌شود:

  • در [1]، پوشه‌ای به نام [globalize] ایجاد شده است که حاوی اسکریپت اصلی [globalize.js] می‌باشد؛
  • در [2]، اسکریپت اصلی [globalize.js] با اسکریپت‌های مخصوص یک زبان و منطقه تکمیل شده است؛
  • در [3]، اسکریپت‌های مخصوص زبان فرانسوی، از جمله بلژیکی (BE)، کانادایی (CA)، فرانسوی (FR)، سوئیسی (CH)، لوکزامبورگی (LU)، موناکویی (MC).

فونت [globalize.js] و فونت فرهنگ ما [globalize.culture.fr-FR.js] باید در فهرست فونت‌های صفحه ما [Action13Get.cshtml] گنجانده شوند:


<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action13Get</title>
...
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/myscripts.js"></script>
</head>
  • خط ۵: اسکریپت [globalize];
  • خط ۶: اسکریپت [globalize.culture.fr-FR.js];
  • خط ۷: اسکریپت [myscripts.js];

بیایید بار دیگر به این اسکریپت آخر نگاهی بیندازیم:


// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}

...

// هنگامی که سند بارگذاری می‌شود
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

خطوط ۱۰ تا ۱۳ فرهنگ سمت کلاینت را روی [fr-FR] تنظیم می‌کنند:

  • خط ۱۰: تابع JQuery [ready] زمانی اجرا می‌شود که سند حاوی اسکریپت به طور کامل توسط مرورگر بارگذاری شده باشد؛
  • خطوط ۱۱–۱۲: لوکال سمت کلاینت روی [fr-FR] تنظیم می‌شود. برای این کار، فایل [globalize.culture.fr-FR.js] باید در فهرست اسکریپت‌های جاوااسکریپت مرتبط با سند گنجانده شود.

اکنون می‌توانیم برنامه جدید را آزمایش کنیم:

 

اکنون می‌توانیم [0,3] را برای عدد واقعی وارد کنیم، که قبلاً امکان‌پذیر نبود. با این حال، با یک ناهنجاری دیگر مواجه می‌شویم:

 

در مثال بالا، اعتبارسنجی سمت کلاینت به ما اجازه می‌دهد [11.2] را با نگارش آنگلو-ساکسون وارد کنیم. این مقدار هنگام ارسال فرم در سمت سرور پذیرفته نمی‌شود:

 

باید [11,2] را وارد کنید، و سپس در هر دو سمت کلاینت و سرور کار می‌کند. در سمت کلاینت، نباید نگارش آنگلو-ساکسون پذیرفته شود. این باید امکان‌پذیر باشد...

اکنون به سراغ بین‌المللی‌سازی ویوها می‌رویم. با مثال فرم قبلی ادامه می‌دهیم و آن را در دو زبان ارائه می‌کنیم: فرانسوی و انگلیسی.

6.2. مدیریت فرهنگ

زبان ویوها توسط شیء [Thread.CurrentThread.CurrentUICulture] کنترل می‌شود. برای نمایش صفحات در فرهنگ [fr-FR]، می‌نویسیم:

Thread.CurrentThread.CurrentUICulture=new CultureInfo("fr-FR");

محلی‌سازی (تاریخ‌ها، اعداد، ارزها، زمان‌ها و غیره) توسط شیء [Thread.CurrentThread.CurrentCulture] کنترل می‌شود. مشابه آنچه قبلاً نوشته شد، شما می‌نویسید:

Thread.CurrentThread.CurrentCulture=new CultureInfo("fr-FR");

این دو دستور می‌توانند در سازندهٔ هر کنترلر در برنامه گنجانده شوند. با این حال، ممکن است بخواهیم این کد را که برای همه کنترلرها مشترک است، استخراج کنیم. ما این رویکرد را در پیش می‌گیریم.

ما دو کنترلر جدید ایجاد می‌کنیم:

  
  • [I18NController] کلاس والد برای تمام کنترلرهایی خواهد بود که از بین‌المللی‌سازی استفاده می‌کنند؛
  • [SecondController] یک کنترل‌کننده نمونه است که از [I18NController] مشتق شده است.

کد کنترل‌کننده [I18NController] به شرح زیر است:


using System.Threading;
using System.Web;
using System.Web.Mvc;

namespace Exemples.Controllers
{
  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // زمینهٔ درخواست جاری را بازیابی می‌کند
      HttpContext httpContext = HttpContext.Current;
      // درخواست برای پارامتر [lang] بررسی می‌شود
      // در پارامترهای URL جستجو کنید
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // در پارامترهای ارسال‌شده برای آن جستجو کنید
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // در جلسهٔ کاربر جستجو می‌شود
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        //پارامتر اول هدر HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // محلی fr-FR
        langue = "fr-FR";
      }
      //زبان در جلسه تنظیم شده است
      httpContext.Session["lang"] = langue;
      // تغییر لوکال‌های رشته            
      Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
    }
  }
}
  • خط ۷: [I18NController] از کلاس [Controller] مشتق شده است؛
  • خط ۷: کلاس به صورت [abstract] اعلام شده است تا از نمونه‌سازی مستقیم آن جلوگیری شود: این کلاس فقط در صورتی قابل استفاده است که از آن ارث‌بری شده باشد؛
  • خط ۹: سازنده کلاس هر بار که یک کنترلر مشتق‌شده از [I18NController] نمونه‌سازی می‌شود، اجرا خواهد شد؛
  • خط ۱۲: زمینه درخواست HTTP که در حال حاضر توسط کنترل‌کننده پردازش می‌شود، بازیابی می‌شود؛
  • خط ۱۵: فرض می‌شود که زبان توسط پارامتر [lang] تنظیم شده است، که می‌توان آن را در مکان‌های مختلف یافت. جستجو به ترتیب زیر انجام می‌شود:
    • خط ۱۵: در پارامترهای URL و [?lang=en-US
    • خط ۱۹: در پارامترهای ارسال‌شده [lang=de]،
    • خط ۲۴: در جلسهٔ کاربر،
    • خط ۲۹: در ترجیحات زبانی ارسال‌شده توسط کلاینت HTTP،
    • خط ۲۶: اگر هیچ چیزی یافت نشود، لوکال روی [fr-FR] تنظیم می‌شود؛
  • خط ۳۷: لوکال در جلسه (session) ذخیره می‌شود. این همان جایی است که برای درخواست‌های بعدی بازیابی خواهد شد. کاربر می‌تواند آن را با مشخص کردن آن در پارامترهای یک دستور GET یا POST تغییر دهد؛
  • خطوط ۳۹–۴۰: لوکال برای نمایش ویو پس از پردازش پرس‌وجوی جاری تنظیم می‌شود.

کنترل‌کننده [SecondController] به صورت زیر خواهد بود:


using Exemple_03.Models;
using Exemples.Controllers;
using System.Web.Mvc;

namespace Exemple_03.Controllers
{
    public class SecondController : I18NController
    {
      // Action14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
      }

      // Action14-POST
      [HttpPost]
      public ViewResult Action14Post(ViewModel14 modèle)
      {
        return View("Action14Get", modèle);
      }
    }
}
  • خط ۷: [SecondController] از [I18NController] مشتق شده است. این امر تضمین می‌کند که فرهنگ (culture) نمای (view) مقداردهی اولیه شده است؛
  • خط ۱۳: از قالب نما [ViewModel14] که اکنون ارائه خواهیم کرد، استفاده می‌شود؛
  • خطوط ۱۳ و ۲۰: نما [Action14Get.cshtml] مسئول نمایش فرم است.

6.3. بین‌المللی‌سازی قالب نما [ViewModel14]

قالب نما [ViewModel14] به شرح زیر است:


using Exemple_03.Resources;
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Globalization;
using System.Net.Mail;

namespace Exemple_03.Models
{
  public class ViewModel14 : IValidatableObject
  {

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "chaineaumoins4")]
    [RegularExpression(@"^.{4,}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "chaineauplus4")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [RegularExpression(@"^.{1,4}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine2 { get; set; }

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "chaine4exactement")]
    [RegularExpression(@"^.{4,4}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine3 { get; set; }

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "entier")]
    public int Entier1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "entierentrebornes")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Range(1, 100, ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public int Entier2 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "reel")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    public double Reel1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "reelentrebornes")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Range(10.2, 11.3, ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public double Reel2 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "email")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [EmailAddress(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte", ErrorMessage="")]
    public string Email1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "date1")]
    [RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    public string Regexp1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "date2")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [DataType(DataType.Date)]
    public DateTime Date1 { get; set; }

    //اعتبارسنجی
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // فهرست خطاها
      List<ValidationResult> résultats = new List<ValidationResult>();
      // همان پیام خطا برای همه
      string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();

      //تاریخ ۱
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
      }
      // Email1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Email1" }));
      }
      // Regexp1
      try
      {
        DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
      }
      catch
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Regexp1" }));
      }

      //نمایش لیست خطاها
      return résultats;
    }
  }
}

این مدل نسخه بین‌المللی‌شده مدل قبلی [ViewModel11] است. ما مکانیزم بین‌المللی‌سازی را برای اولین ویژگی از اولین خاصیت توضیح خواهیم داد. سایر ویژگی‌ها از همان مکانیزم پیروی می‌کنند.


    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
public string Chaine1 { get; set; }

در مدل قبلی، [ViewModel11]، این خطوط به شرح زیر بودند:


[Required(ErrorMessage = "Information requise")]
public string Chaine1 { get; set; }

در نسخه بین‌المللی‌سازی‌شده، در خط ۱، متنی که باید نمایش داده شود در یک فایل منبعی قرار می‌گیرد. در اینجا، این فایل [MyResources.resx] (typeof) نامیده می‌شود و در دایرکتوری ریشه پروژه قرار داده شده است. به آن فایل منبعی گفته می‌شود.

ما در اینجا سه فایل منبعی ایجاد کرده‌ایم:

  • [MyResources]: منبع پیش‌فرض زمانی که منبعی برای زبانه‌ی جاری وجود ندارد؛
  • [MyResources.fr-FR]: منبع برای منطقه [fr-FR];
  • [MyResources.en-US]: منبع برای لوکال [en-US];

برای ایجاد یک فایل منبعی، به شرح زیر عمل کنید: [1, 2, 3]:

این کار فایل منبعی به نام [MyResources2.resx] را ایجاد می‌کند. وقتی روی آن دوبار کلیک کنید، صفحه زیر ظاهر می‌شود:

یک فایل منبعی (resource file) یک فرهنگ لغت است که شامل کلیدها و مقادیری است که با آن کلیدها مرتبط هستند. کلید به صورت [1]، مقدار به صورت [2] و دامنه (scope) منبع به صورت [3] وارد می‌شود. برای اینکه این منابع قابل خواندن باشند، باید دامنه [Public] را داشته باشند. بیایید به خط بازگردیم:


    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
  • [ErrorMessageResourceType]: این به فایل منبعی اشاره دارد. پارامتر [typeof] نام فایل است. این پارامتر در طول فرآیند کامپایل به یک کلاس تبدیل می‌شود و باینری آن در اسمبلی پروژه گنجانده می‌شود. بنابراین در نهایت، [MyResources] نام کلاس منبع است؛
  • [ErrorMessageResourceName = "infoRequise"]: به یک کلید در فایل منابعی اشاره دارد. در نهایت، این خط به این معناست که پیام خطایی که باید نمایش داده شود، مقدار موجود در فایل [MyResources] است که با کلید [infoRequise] مرتبط است.

برای ایجاد کلید [infoRequise] و مقدار مربوطه در فایل [MyResources]، مراحل زیر را دنبال کنید:

کلید را به صورت [1]، مقدار را به صورت [2] و دامنهٔ منبع را به صورت [3] وارد کنید.

یک نکتهٔ نهایی برای روشن شدن باقی مانده است: فضای نام کلاس [MyResources]. این در ویژگی‌های فایل [MyResources.resx] تعریف شده است:

در [1]، ما فضای نام کلاس [MyResources] را تعریف می‌کنیم که از فایل منبعی [MyResources.resx] ایجاد خواهد شد. بیایید به خط بین‌المللی‌شده‌ای که در حال بررسی آن بودیم بازگردیم:


[Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]

اپراتور typeof انتظار یک کلاس را دارد، در این مورد کلاس [MyResources]. برای یافتن این کلاس، فضای نام آن باید به کلاس [ViewModel14] وارد شود:


using Exemple_03.Resources;

برای اینکه کلاس [MyResources] قابل مشاهده باشد، پروژه باید حداقل یک بار از زمان ایجاد فایل منبعی [MyResources] ساخته شده باشد. کد این کلاس را می‌توان در فایل [MyResources.Designer.cs] یافت:

  

وقتی روی این فایل دوبار کلیک می‌کنید، به کد کلاس [MyResources] هدایت می‌شوید:


namespace Exemple_03.Resources {
    using System;
    
    
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")]
    [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
    [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
    public class MyResources2 {
        
  ...
         public static string infoRequise {
            get {
                return ResourceManager.GetString("infoRequise", resourceCulture);
            }
        }
    }
}
  • خط ۱: فضای نام کلاس؛
  • خط ۱۱: کلید [infoRequise] به یک ویژگی ایستا از کلاس [MyResources] تبدیل شده است. می‌توان با استفاده از نشانه [MyResources.infoRequise] به آن دسترسی داشت. علاوه بر این، باید توجه داشت که این ویژگی دارای دامنه [public] است. بدون این ویژگی، قابل دسترسی نخواهد بود. ارزش دارد این نکته را به خاطر بسپارید، زیرا متأسفانه دامنه پیش‌فرض [internal] است و اگر فراموش کنید این دامنه را تغییر دهید، می‌تواند باعث خطاهایی شود که درک آن‌ها دشوار است.

چرا اکنون سه فایل منبعی وجود دارد؟

  

ما [MyResources.resx] را ایجاد کرده‌ایم. این منبع ریشه است. در مرحله بعد، به ازای هر ایزوله (زبان) که باید مدیریت شود، به همان تعداد فایل منبع [MyResources.locale.resx] ایجاد می‌کنیم. در اینجا ما در حال مدیریت زبان فرانسوی ([fr-FR]) و انگلیسی آمریکایی ([en-US]) هستیم. هنگامی که زبان جاری نه [fr-FR] و نه [en-US] باشد، از منبع ریشه [MyResources.resx] استفاده می‌شود.

محتوای نهایی [MyResources.resx] به شرح زیر است:

 

اگر محله شناسایی نشود، پیام‌ها به زبان فرانسوی خواهند بود. محتوای نهایی [MyResources.fr-FR.resx] یکسان است و به سادگی با کپی کردن فایل به دست می‌آید.

محتوای نهایی [MyResources.en-US.resx] نیز با کپی کردن فایل و سپس اصلاح به شرح زیر به دست می‌آید:

 

بیایید به نمای [ViewModel14] و متد آن [Validate] بازگردیم:


    //اعتبارسنجی
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // فهرست خطاها
      List<ValidationResult> résultats = new List<ValidationResult>();
      // پیام خطای یکسان برای همه
      string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();

      // تاریخ ۱
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
      }
...
      // فهرست خطاها را نمایش می‌دهد
      return résultats;
}

خط ۷ نشان می‌دهد چگونه یک پیام را از فایل منبعی [MyResources] بازیابی کنیم. در اینجا، می‌خواهیم پیام مرتبط با کلید [infoIncorrecte] را در لوکال جاری بازیابی کنیم:

  • MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo("en-US")) : شیء مرتبط با کلید [infoIncorrecte] را از فایل منبعی [MyResources.en-US.resx] بازیابی می‌کند؛
  • ما دیدیم که کنترل‌کننده [I18NController] فرهنگ جاری را در جلسه‌ای که با کلید [lang] مرتبط است، تنظیم می‌کند. بنابراین، می‌توان فرهنگ جاری را توسط System.Web.HttpContext.Current.Session["lang"] به صورت یک رشته بازیابی کرد؛
  • منبع با نوع [object] بازیابی می‌شود. برای دریافت پیام خطا، متد [ToString] روی آن اعمال می‌شود.

6.4. بین‌المللی‌سازی نمای [Action14Get.cshtml]

نماش نمای فرم را به شرح زیر به‌روزرسانی می‌کنیم:

  

@model Exemple_03.Models.ViewModel14
@using Exemple_03.Resources
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action14Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/globalize.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.fr-FR.js"></script>
  <script type="text/javascript" src="~/Scripts/globalize/cultures/globalize.culture.en-US.js"></script>
  <script type="text/javascript" src="~/Scripts/myscripts2.js"></script>
  <script>
    $(document).ready(function () {
      var culture = '@System.Threading.Thread.CurrentThread.CurrentCulture';
        Globalize.culture(culture);
      });
  </script>

</head>
<body>
  <h3>Formulaire ASP.NET MVC - Internationalisation</h3>
  @using (Html.BeginForm("Action14Post", "Second"))
  {
    <table>
      <thead>
        <tr>
          <th>@MyResources.type</th>
          <th>@MyResources.value</th>
          <th>@MyResources.error</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine1)</td>
          <td>@Html.EditorFor(m => m.Chaine1)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
        </tr>
...
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>
<!--انتخاب زبان -->
@using (Html.BeginForm("Lang", "Second"))
{
  <table>
    <tr>
      <td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
      <td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
    </tr>
  </table>
}

توجه: در خط 14، نسخه jQuery را با نسخه ویژوال استودیوی خود تطبیق دهید.

بیایید با ساده‌ترین بخش شروع کنیم: خطوط ۳۶–۳۸. این خطوط از خواص ایستا کلاس [MyResources] که همین حالا توضیح دادیم استفاده می‌کنند. برای دسترسی به کلاس [MyResources] باید فضای نام آن را وارد کنید (خط ۲).

هنگام بین‌المللی‌سازی پیام‌ها، باید پیام‌های نمایش‌داده‌شده توسط چارچوب اعتبارسنجی سمت کلاینت را نیز در نظر بگیرید. برای انجام این کار، باید از کتابخانه‌های JQuery در خطوط 17–19 استفاده کنید. ما از فایل‌های JQuery برای دو لوکال تحت مدیریت‌مان استفاده می‌کنیم: [fr-FR] و [en-US]. علاوه بر این، ممکن است به یاد داشته باشید که نمای [Action13Get] از اسکریپت جاوااسکریپت زیر، [myscripts.js]، استفاده می‌کرد:


//هنگام بارگذاری سند
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

اکنون، فرهنگ دیگر فقط [fr-FR] نیست؛ بلکه متغیر است. در نتیجه، این خطوط اکنون توسط خود نمای [Action14Get] در خطوط ۲۱ تا ۲۶ تولید می‌شوند. این شش خط در صفحه‌ی HTML که به کلاینت ارسال می‌شود، گنجانده خواهند شد.

  • خط ۲۳: متغیر جاوااسکریپت [culture] با لوکال فعلی ثریدی که درخواست را پردازش می‌کند، مقداردهی اولیه می‌شود. ممکن است به یاد داشته باشید که این متغیر توسط سازنده کلاس [I18NController] مقداردهی اولیه شده بود:

      // زبان را برای جلسه تنظیم کنید
      httpContext.Session["lang"] = langue;
      // محلی‌سازی‌های رشته را تغییر دهید            
      Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

اگر لوکال فعلی [en-US] باشد، اسکریپت جاوااسکریپت جاسازی‌شده در صفحه HTML به صورت زیر درمی‌آید:


  <script>
    $(document).ready(function () {
      var culture = 'en-US';
        Globalize.culture(culture);
      });
</script>

همانطور که قبلاً ذکر شد، تابع [$(document).ready] پس از اتمام بارگذاری صفحه توسط مرورگر اجرا می‌شود. اجرای آن، محله (locale) را برای چارچوب اعتبارسنجی سمت کلاینت تنظیم خواهد کرد. با لوکال [en-US]، پیام‌های خطای فریم‌ورک به زبان انگلیسی خواهند بود و از فایل منبعی به نام [MyResources.en-US.resx] تأمین می‌شوند. خواهیم دید چگونه.

حالا بیایید خطوط ۵۷ تا ۶۵ را بررسی کنیم:


<!-- انتخاب زبان -->
@using (Html.BeginForm("Lang", "Second"))
{
  <table>
    <tr>
      <td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
      <td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
    </tr>
  </table>
}

در اینجا یک فرم دوم داریم؛ فرم اول در خطوط ۳۱–۵۳ قرار دارد. این فرم لینک‌های زیر را در پایین صفحه نمایش می‌دهد:

  • خط ۲: فرم به اکشن [Lang] از کنترلر [Second] ارسال می‌شود. در حال حاضر، نمی‌توانیم هیچ مقداری را که ممکن است ارسال شده باشد، ببینیم؛
  • خطوط ۶ و ۷: کلیک روی لینک‌ها باعث اجرای تابع جاوااسکریپت [postForm] می‌شود. این تابع کجا قرار دارد؟ در اسکریپت [myscripts2.js] که در خط ۲۰ نما به آن ارجاع شده است:

محتوای آن به شرح زیر است:


function postForm(lang, url) {
  // دریافت دومین فرم از سند
  var form = document.forms[1];
  // ویژگی مخفی 'lang' را به آن اضافه کنید
  var hiddenField = document.createElement("input");
  hiddenField.setAttribute("type", "hidden");
  hiddenField.setAttribute("name", "lang");
  hiddenField.setAttribute("value", lang);
  //افزودن فیلد مخفی به فرم
  form.appendChild(hiddenField);
  // ویژگی پنهان 'url' را به آن اضافه می‌کند
  var hiddenField = document.createElement("input");
  hiddenField.setAttribute("type", "hidden");
  hiddenField.setAttribute("name", "url");
  hiddenField.setAttribute("value", url);
  // افزودن فیلد مخفی به فرم
  form.appendChild(hiddenField);
  //ارسال
  form.submit();
}

// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}

$.validator.methods.date = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseDate(value));
}

jQuery.extend(jQuery.validator.methods, {
  range: function (value, element, param) {
    //از افزونهٔ Globalisation برای تجزیهٔ مقدار استفاده کنید        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

خطوط ۲۲ تا ۴۰ همان‌هایی هستند که پیش از این در اسکریپت [myscripts.js] در مثال قبلی استفاده شده‌اند. ما در اینجا به آن‌ها بازنمی‌گردیم. تابع [postForm] که هنگام کلیک بر روی پیوندهای زبان اجرا می‌شود، در خطوط ۱ تا ۲۰ قرار دارد:

  • خط ۱: این تابع دو پارامتر می‌گیرد، [lang] که زبان انتخاب‌شده توسط کاربر است، و [url] که URL است که مرورگر مشتری باید پس از تغییر زبان به آن هدایت شود. این دو پارامتر هنگام فراخوانی تابع مشخص می‌شوند:

<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>
  • خط ۳: مرجعی به فرم دوم در سند بازیابی می‌شود؛
  • خطوط ۵–۸: تگ به‌صورت برنامه‌ای ایجاد می‌شود
<input type="hidden" value="xx-XX"/>

که در آن [xx-XX] مقدار پارامتر تابع [lang] است؛

  • خط ۱۰: مجدداً از طریق برنامه‌نویسی، این تگ به فرم دوم اضافه می‌شود. در نهایت، گویی این تگ از ابتدا در فرم دوم وجود داشته است. بنابراین مقدار آن ارسال خواهد شد. این همان چیزی است که ما می‌خواستیم؛
  • خطوط ۱۱–۱۷: ما همین فرآیند را برای یک تگ تکرار می‌کنیم
<input type="hidden" value="url"/>

که در آن [url] مقدار پارامتر تابع [url] است؛

  • خط ۱۹: فرم دوم اکنون ارسال می‌شود. به کدام URL؟

ما باید به کد فرم دوم در صفحه [Action14Get.cshtml] بازگردیم:


@using (Html.BeginForm("Lang", "Second"))
{
...
}

بنابراین فرم به URL [/Second/Lang] ارسال می‌شود. سپس باید یک اکشن [Lang] را در کنترلر [SecondController] تعریف کنیم. به شرح زیر خواهد بود:


public class SecondController : I18NController
    {
      // Action14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
      }

      // Action14-POST
      [HttpPost]
      public ViewResult Action14Post(ViewModel14 modèle)
      {
        return View("Action14Get", modèle);
      }

      // زبان
      [HttpPost]
      public RedirectResult Lang(string url)
      {
        // کلاینت به URL هدایت می‌شود
        return new RedirectResult(url);
      }

    }
  • خط ۱۸: اکشن تنها به یک [POST] پاسخ می‌دهد؛
  • خط ۱۹: فقط پارامتر با نام [url] را بازیابی می‌کند؛
  • خط ۲۲: به کلاینت دستور می‌دهد که به این URL هدایت شود.

اما پارامتر با نام [lang] چه شده است؟ اکنون باید به خاطر داشته باشیم که کنترل‌کننده [SecondController] از کلاس [I18NController] (خط 1 بالا) مشتق شده است. این کنترل‌کننده است که پارامتر [lang] را مدیریت می‌کند:


  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // بازیابی زمینهٔ درخواست فعلی
      HttpContext httpContext = System.Web.HttpContext.Current;
      //بررسی درخواست برای پارامتر [lang]
      // جستجو برای آن در پارامترهای URL
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // در پارامترهای ارسال‌شده برای آن جستجو کنید
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // در جلسهٔ کاربر جستجو می‌شود
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        //پارامتر اول هدر HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // محلی fr-FR
        langue = "fr-FR";
      }
      //زبان در جلسه تنظیم شده است
      httpContext.Session["lang"] = langue;
      // ویرایش لوکال‌های رشته            
      Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}

در مثالی که در حال بررسی آن هستیم، پارامتر [lang] ارسال شده است. بنابراین، این پارامتر در خط ۱۳ یافت شده، در خط ۳۱ وارد جلسه می‌شود و برای به‌روزرسانی زمینهٔ نخ (thread) جاری در خطوط ۳۳–۳۴ استفاده می‌شود.

در ادامه چه اتفاقی می‌افتد؟ بیایید به لینک‌ها نگاهی بیندازیم:


<td><a href="javascript:postForm('fr-FR','/Second/Action14Get')">Français</a></td>
<td><a href="javascript:postForm('en-US','/Second/Action14Get')">English</a></td>

انحراف URL برابر با [/Second/Action14Get] است. بنابراین، عمل [Action14Get] اجرا می‌شود:


public class SecondController : I18NController
    {
      // Action14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
      }
...
}

پیش از این، سازنده کلاس [I18NController] اجرا می‌شود:


  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // بازیابی زمینهٔ درخواست فعلی
      HttpContext httpContext = System.Web.HttpContext.Current;
      //بررسی درخواست برای پارامتر [lang]
      // در پارامترهای URL به دنبال آن بگردید
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // در پارامترهای ارسال‌شده برای آن جستجو کنید
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // در جلسهٔ کاربر جستجو می‌شود
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        // اولین پارامتر هدر HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // محلی fr-FR
        langue = "fr-FR";
      }
      //زبان در جلسه تنظیم شده است
      httpContext.Session["lang"] = langue;
      // تغییر لوکال‌های رشته            
      Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}

این بار، پارامتر [lang] در جلسه در خط ۱۸ یافت می‌شود. فرض کنیم که مقدار آن [en-US] است. بنابراین این لوکال به لوکال نخ اجرای پرس‌وجو (خطوط ۳۳–۳۴) تبدیل می‌شود. بیایید به عمل [Action14Get] بازگردیم:


      // Action14-GET
      [HttpGet]
      public ViewResult Action14Get()
      {
        return View("Action14Get", new ViewModel14());
}

در خط ۵، یک نمونه از مدل نما [ViewModel14] ایجاد می‌شود:


  public class ViewModel14 : IValidatableObject
  {

    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [Display(ResourceType = typeof(MyResources), Name = "chaineaumoins4")]
    [RegularExpression(@"^.{4,}$", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    public string Chaine1 { get; set; }
....

از آنجا که فرهنگ (culture) نخ جاری [en-US] است، از فایل [MyResources.en-US.resx] استفاده خواهد شد. بنابراین پیام‌های خطا به زبان انگلیسی خواهند بود.

پس از نمونه‌سازی قالب [ViewModel14]، نمای [Action14Get.cshtml] نمایش داده می‌شود:


@model Exemple_03.Models.ViewModel14
@using Exemple_03.Resources
@using System.Threading
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action14Get</title>
  ...
  <script>
    $(document).ready(function () {
      var culture = '@Thread.CurrentThread.CurrentCulture';
        Globalize.culture(culture);
      });
  </script>

</head>
<body>
  <h3>Formulaire ASP.NET MVC - Internationalisation</h3>
  @using (Html.BeginForm("Action14Post", "Second"))
  {
    <table>
      <thead>
        <tr>
          <th>@MyResources.type</th>
          <th>@MyResources.value</th>
          <th>@MyResources.error</th>
        </tr>
      </thead>
      <tbody>
        <tr>
...
        </tr>
<tr>

از آنجا که فرهنگ نخ فعلی [en-US] است، اسکریپت جاسازی‌شده در صفحه در خطوط ۱۵–۲۰ عبارت است از:


  <script>
    $(document).ready(function () {
      var culture = 'en-US';
        Globalize.culture(culture);
      });
  </script>

این اطمینان را می‌دهد که چارچوب اعتبارسنجی با فرمت‌های آمریکایی (تاریخ، ارز، اعداد و غیره) کار خواهد کرد. به همین دلیل، پیام‌های خطوط ۳۰ تا ۳۲ از فایل منبعی [MyResources.en-US.resx] گرفته می‌شوند و بنابراین به زبان انگلیسی خواهند بود.

6.5. نمونه‌هایی از اجرا

در اینجا چند نمونه از خروجی آورده شده است:

  • در [1]، فرم به زبان فرانسوی است؛ در [2]، فرم به زبان انگلیسی است.
  • در [3]، در سمت کلاینت، پیام‌های خطا اکنون به زبان انگلیسی هستند.

اگر به کد منبع صفحه نگاه کنیم، می‌بینیم که این پیام‌های خطا در صفحه جاسازی شده‌اند و بنابراین توسط نما ASP.NET [Action14Get] و قالب آن [ViewModel14] تولید می‌شوند:


        <tr>
          <td><label for="Reel1">Real number</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-number="The field Real number must be a number." data-val-required="Required data" id="Reel1" name="Reel1" type="text" value="0" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Reel1" data-valmsg-replace="true"></span></td>
        </tr>
        <tr>
          <td><label for="Reel2">Real number in range [10.2-11.3]</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-number="The field Real number in range [10.2-11.3] must be a number." data-val-range="Invalid data" data-val-range-max="11.3" data-val-range-min="10.2" data-val-required="Required data" id="Reel2" name="Reel2" type="text" value="0" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Reel2" data-valmsg-replace="true"></span></td>
</tr>

6.6. بین‌المللی‌سازی تاریخ‌ها

بین‌المللی‌سازی مسئله‌ای پیچیده است. بنابراین بیایید به ویژگی [Date1] و تقویم آن نگاهی بیندازیم:

  

می‌توانیم ببینیم که تقویم یک تقویم فرانسوی است، در حالی که فرهنگ صفحه [en-US] است. در HTML5، یک ویژگی [lang] وجود دارد که به شما امکان می‌دهد زبان صفحه یا یکی از اجزای آن را تنظیم کنید. سپس می‌توانید کد زیر را در نمای [Action14Get.cshtml] بنویسید:


@model Exemple_03.Models.ViewModel14
@using Exemple_03.Resources
@using System.Threading
@{
  Layout = null;
  var lang = Session["lang"] as string;  
}

<!DOCTYPE html>

<html lang="@lang">
<head>
...
  • خط ۶: زبان را از جلسه بازیابی کنید؛
  • خط ۱۱: ویژگی [lang] صفحه را روی این مقدار تنظیم کنید.

آزمایش‌ها نشان می‌دهند که تقویم حتی زمانی که صفحه به زبان انگلیسی نمایش داده می‌شود، همچنان به زبان فرانسوی باقی می‌ماند. همچنین مشکلی در مورد فیلد تاریخ دیگر در فرم وجود دارد:

در [1]، تاریخ همچنان به فرمت فرانسوی dd/mm/yyyy (20/11/2013) درخواست می‌شود، در حالی که فرمت آمریکایی mm/dd/yyyy (10/21/2013) است. ما سعی خواهیم کرد این دو مشکل را با یک نمای جدید و یک قالب نمای جدید حل کنیم.

JQuery و UI پروژه‌ای مشتق‌شده از پروژه JQuery هستند و کامپوننت‌هایی برای فرم‌ها، از جمله یک تقویم، فراهم می‌کنند. این تقویم را می‌توان بین‌المللی‌سازی کرد. این همان چیزی است که قصد داریم نشان دهیم.

برای شروع، بیایید [JQuery UI] را به پروژه خود اضافه کنیم.

پس از نصب JQuery و UI، عناصر جدیدی در پروژه ظاهر می‌شوند:

  • در [1]، کتابخانه [JQuery UI] در هر دو نسخه عادی و فشرده‌شده؛
  • [2]، فایل استایل برای [JQuery UI

تقویم JQuery UI به‌طور پیش‌فرض به زبان انگلیسی است. برای بین‌المللی‌سازی آن، باید اسکریپت‌هایی را اضافه کنید که در URL [https://github.com/jquery/jquery-ui/tree/master/ui/i18n] یافت می‌شوند:

برای نمایش تقویم JQuery UI به زبان فرانسوی، محتویات فایل [jquery.ui.datepicker-fr.js] بالا را در پوشه [Scripts] داخل پروژه کپی کنید.

کد مربوط به نمای جدید [Action15.cshtml] با کپی کردن نمای قبلی [Action14.cshtml] و سپس اصلاح آن به دست می‌آید. ما فقط تغییرات را نشان می‌دهیم:


@model Exemple_03.Models.ViewModel15
@using Exemple_03.Resources
@using System.Threading
@{
  Layout = null;
}

<!DOCTYPE html>

<html lang="@Model.Culture">
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action15</title>
...
  <link rel="stylesheet" href="~/Content/themes/base/jquery-ui.css" />
  <script type="text/javascript" src="~/Scripts/jquery-ui-1.10.3.js"></script>
  <script type="text/javascript" src="~/Scripts/jquery.ui.datepicker-fr.js"></script>
  <script>
    $(document).ready(function () {
      var culture = '@Thread.CurrentThread.CurrentCulture';
      Globalize.culture(culture);
      $("#Date1").datepicker($.datepicker.regional['@Model.Regionale']);
    });
  </script>
</head>
<body>
  <h3>@MyResources.titre</h3>
  @using (Html.BeginForm("Action15", "Second"))
  {
    <table>
...
        <tr>
          <td>@Html.LabelFor(m => m.Date1)</td>
          <td>@Html.TextBox("Date1", Model.StrDate1)</td>
          <td>@Html.ValidationMessageFor(m => m.Date1)</td>
        </tr>
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
  <!-- انتخاب یک زبان -->
  @using (Html.BeginForm("Lang", "Second"))
  {
    <table>
      <tr>
        <td><a href="javascript:postForm('fr-FR','/Second/Action15')">Français</a></td>
        <td><a href="javascript:postForm('en-US','/Second/Action15')">English</a></td>
      </tr>
    </table>
  }
</body>
</html>

توجه: خط ۱۶ – نسخه jQuery-UI را با نسخه‌ای که دانلود کرده‌اید مطابقت دهید.

  • خط ۱۵: به استایل‌شیت از JQuery UI ارجاع دهید؛
  • خط ۱۶: ما به نسخه دانلود شده JQuery UI ارجاع می‌دهیم؛
  • خط ۱۷: ما به اسکریپت تقویم فرانسوی که همین حالا دانلود کرده‌ایم ارجاع می‌دهیم؛
  • خط ۳۴: متد [Html.TextBox] یک تگ در اینجا تولید خواهد کرد، یک تگ [input] از نوع [text]، با id برابر [Date1] و نام [Date1];
  • خط ۱۹: پس از اتمام بارگذاری صفحه، تابع JQuery UI [datepicker] بر روی عنصری با شناسه [Date1] اعمال خواهد شد، یعنی عنصر در خط ۳۴. این تابع تضمین می‌کند که وقتی کاربر فوکوس را روی فیلد ورودی [Date1] قرار می‌دهد، یک تقویم ظاهر شود تا بتواند تاریخ را وارد کند. تابع [datepicker] پارامتر مشخص‌کننده زبان تقویم را می‌پذیرد. متغیر [@Model.Regionale] باید به صورت زیر تنظیم شود:
  • 'fr' برای تقویم فرانسوی،
  • '' برای تقویم انگلیسی؛

قالب نمای قبلی، [Action15.cshtml]، به قالب زیر، [ViewModel15]، تبدیل خواهد شد:

کد آن مربوط به قالب کمی اصلاح‌شده [ViewModel14] است. ما فقط تغییرات را نشان می‌دهیم:


using Exemple_03.Resources;
...
using System.Web;

namespace Exemple_03.Models
{
  [Bind(Exclude = "Culture,Regionale,StrDate1,FormatDate")]
  public class ViewModel15 : IValidatableObject
  {

...
    [Display(ResourceType = typeof(MyResources), Name = "date1")]
    [RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoIncorrecte")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    public string Regexp1 { get; set; }

    [Display(ResourceType = typeof(MyResources), Name = "date2")]
    [Required(ErrorMessageResourceType = typeof(MyResources), ErrorMessageResourceName = "infoRequise")]
    [DataType(DataType.Date)]
    public DateTime Date1 { get; set; }

    // سازنده
    public ViewModel15()
    {
      // محلی فعلی
      Culture = HttpContext.Current.Session["lang"] as string;
      cultureInfo=new CultureInfo(Culture);
      // منطقه تقویم JQuery
      Regionale = MyResources.ResourceManager.GetObject("regionale", cultureInfo).ToString();
      // قالب تاریخ
      FormatDate = MyResources.ResourceManager.GetObject("formatDate", cultureInfo).ToString();
    }



    // اعتبارسنجی
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // فهرست خطاها
      List<ValidationResult> résultats = new List<ValidationResult>();
      // پیام خطای یکسان برای همه
      string errorMessage = MyResources.ResourceManager.GetObject("infoIncorrecte", cultureInfo).ToString();
...
      // Regexp1
      try
      {
        DateTime.ParseExact(Regexp1, FormatDate, cultureInfo);
      }
      catch
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Regexp1" }));
      }

      // فهرست خطاها را بازمی‌گرداند
      return résultats;

    }

    // fields outside the action template
    public string Culture { get; set; }
    public string Regionale { get; set; }
    public string StrDate1 { get; set; }
    public string FormatDate { get; set; }

    // داده‌های محلی
    private CultureInfo cultureInfo;
  }
}

در مقایسه با مدل قبلی، [ViewModel14]، ما چهار ویژگی اضافی داریم:

  • خط ۶۰: زبان بومی نما، «fr-FR» یا «en-US». این زبان بومی در سازنده در خط ۲۶ مقداردهی اولیه می‌شود؛
  • خط ۶۱: لوکال تقویم منطقه‌ای برای JQuery، 'fr' برای تقویم فرانسوی، '' برای تقویم انگلیسی. این فیلد توسط خط ۲۹ از سازنده (constructor) مقداردهی اولیه می‌شود؛
  • خط ۶۳: فرمت تاریخ از خط ۱۵: 'dd/MM/yyyy' برای تاریخ فرانسوی، 'MM/dd/yyyy' برای تاریخ انگلیسی. این فیلد در خط ۳۱ از سازنده مقداردهی اولیه می‌شود؛
  • خط ۶۲: رشته متنی که باید در فیلد ورودی برای [Date1] نمایش داده شود. این فیلد توسط اقدام مقداردهی اولیه خواهد شد؛
  • خط ۴۷: تاریخ [Regexp1] اکنون بر اساس قالب فرهنگ جاری اعتبارسنجی می‌شود.

مقادیر ویژگی‌های [Regionale] و [FormatDate] در فایل‌های منبعی [MyResources] یافت می‌شوند. فایل‌های منبعی فرانسوی [MyResources]، [MyResources.fr-FR] و [1] و فایل منبعی انگلیسی [2] به شرح زیر به‌روزرسانی شده‌اند:

ما تقریباً آماده‌ایم. ما یک اکشن [Action15] را به کنترلر [SecondController] اضافه می‌کنیم:


      // Action15
      public ViewResult Action15(FormCollection formData)
      {
        // روش HTTP
        string method = Request.HttpMethod.ToLower();
        // قالب
        ViewModel15 modèle = new ViewModel15();
        if (method == "get")
        {
          modèle.StrDate1 = "";
        }
        else
        {
          TryUpdateModel(modèle, formData);
          modèle.StrDate1 = modèle.Date1.ToString(modèle.FormatDate);
        }
        // نمایش نما
        return View("Action15", modèle);
}
  • خط ۲: متد [Action15] هر دو [GET] و [POST] را مدیریت می‌کند. در مورد دوم، مقادیر ارسال‌شده از پارامتر [formData] بازیابی می‌شوند؛
  • خط ۵: متد HTTP از پرس‌وجو فراخوانی می‌شود؛
  • خط ۷: قالب برای نمای نمایش‌داده‌شده (فرم) ایجاد می‌شود؛
  • خطوط ۸–۱۱: در صورت وجود یک فرمان [GET]، فیلد ورودی برای [Date1] با یک رشته خالی مقداردهی اولیه می‌شود؛
  • خطوط ۱۲–۱۶: در صورت سفارش [POST]:
    • خط ۱۴: قالب با مقادیر ارسال‌شده مقداردهی اولیه می‌شود؛
    • خط ۱۵: فیلد ورودی برای [Date1] با رشته‌ای که نمایانگر مقدار [Date1] است، و بر اساس لوکال فعلی قالب‌بندی شده، مقداردهی اولیه می‌شود ([dd/MM/yyyy] برای تاریخ فرانسوی، [MM/dd/yyyy] برای تاریخ بریتانیایی؛
  • خط ۱۸: نما [Action15.cshtml] با قالب خود نمایش داده می‌شود.

بیایید چند تست اجرا کنیم:

  • در [1]، یک تقویم فرانسوی وقتی صفحه به زبان فرانسوی است؛
  • در [2]، یک تقویم بریتانیایی وقتی صفحه به زبان انگلیسی است؛
  • [3]، تاریخ به فرمت فرانسوی وقتی صفحه به زبان فرانسوی است؛
  • [4]، همان تاریخ به فرمت انگلیسی وقتی صفحه به زبان انگلیسی است؛

6.7. Conclusion

همان‌طور که می‌بینید، موضوع بین‌المللی‌سازی برنامه موضوعی پیچیده است...