Skip to content

6. تدويل العرض

سنناقش هنا مسألة تدويل العرض. هذه مسألة معقدة، ويمكن العثور على وصف جيد لها في المقالة التالية التي كتبها سكوت هانسلمان:

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

أولاً، دعونا نستعرض تعريفاته للمصطلحات المختلفة المتعلقة بتدويل العرض:

التدويل (i18n)
جعل التطبيق يدعم لغات وإعدادات محلية مختلفة
التوطين (l10n)
جعل التطبيق يدعم زوجًا معينًا من اللغة/الإعدادات المحلية
العولمة
الجمع بين التدويل والتوطين
اللغة
اللغة المنطوقة – المحددة برمز ISO (fr: الفرنسية، es: الإسبانية، en: الإنجليزية، ...)
الإعدادات
نوع من اللغة – يُشار إليه أيضًا برمز ISO (en_GB: الإنجليزية البريطانية، en_US: الإنجليزية الأمريكية، ...)

لنبدأ بمعالجة المشكلة من خلال مثال أول.

6.1. تحديد موضع الأعداد الحقيقية

قد تلاحظ وجود شذوذ في نموذج الإدخال السابق:

Image

بالنسبة للعدد الحقيقي، كتبنا [0,3] ولم يتم قبوله. يجب عليك كتابة [0.3]:

Image

وبالتالي، فإن التنسيق المتوقع هو التنسيق الأنجلوساكسوني، وليس التنسيق الفرنسي. ويكشف البحث السريع عبر الإنترنت عن بعض الحلول. وإليك أحدها.

تصبح إجراءات [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] باستثناء نصوص JavaScript:


<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>

ملاحظة: في السطر 5، اضبط إصدار jQuery ليتوافق مع إصدار Visual Studio لديك.

  • السطر 9، أضفنا نصًا برمجيًا [ 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) {
    //Use the Globalization plugin to parse the value        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});
 
// au chargement du document
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

لقد أشرت في السطر 1 إلى المكان الذي تم العثور فيه على هذا البرنامج النصي. لن أحاول شرحه لأنني لا أفهمه. قد تكون لغة JavaScript غامضة بعض الشيء في بعض الأحيان. في الأسطر 4 و9 و15، يتم استخدام كائن [Globalize]. يتم توفير هذا الكائن بواسطة مكتبة jQuery Globalization، والتي يمكن الحصول عليها عبر [NuGet]:

  • في [1]، قم بإدارة حزم [NuGet] لمشروع [Example-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>

  • السطر 5: البرنامج النصي [globalize
  • السطر 6: البرنامج النصي [globalize.culture.fr-FR.js
  • السطر 7: البرنامج النصي [myscripts.js

دعونا نلقي نظرة فاحصة على هذا البرنامج النصي الأخير:


// http://blog.instance-factory.com/?p=268
$.validator.methods.number = function (value, element) {
  return this.optional(element) ||
      !isNaN(Globalize.parseFloat(value));
}
 
...
 
// au chargement du document
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

تقوم الأسطر 10–13 بتعيين الثقافة من جانب العميل إلى [fr-FR]:

  • السطر 10: يتم تنفيذ دالة jQuery [ready] عندما يتم تحميل المستند الذي يحتوي على البرنامج النصي بالكامل بواسطة المتصفح؛
  • السطور 11–12: يتم تعيين الثقافة من جانب العميل إلى [fr-FR]. لكي يعمل هذا، يجب تضمين الملف [globalize.culture.fr-FR.js] في قائمة نصوص JavaScript المرتبطة بالمستند.

الآن يمكننا اختبار التطبيق الجديد:

Image

يمكننا الآن إدخال [0.3] كرقم حقيقي، وهو ما لم نتمكن من فعله من قبل. ومع ذلك، نواجه مشكلة أخرى:

Image

في الأعلى، يسمح لنا التحقق من صحة البيانات من جانب العميل بإدخال [11.2] باستخدام الترميز الأنجلوساكسوني. لا يتم قبول هذه القيمة من جانب الخادم عند إرسال النموذج:

Image

يجب أن نكتب [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");

يمكن وضع هذين البيانين في منشئ كل وحدة تحكم في التطبيق. ومع ذلك، قد نرغب أيضًا في استخراج هذا الرمز المشترك بين جميع وحدات التحكم. وسنتبع هذا النهج.

نقوم بإنشاء وحدتي تحكم جديدتين:

Image

  • ستكون [I18NController] هي الفئة الأساسية لجميع وحدات التحكم التي تستخدم الترجمة؛
  • [SecondController] هي وحدة تحكم نموذجية مشتقة من [I18NController].

فيما يلي كود وحدة التحكم [I18NController]:


using System.Threading;
using System.Web;
using System.Web.Mvc;
 
namespace Exemples.Controllers
{
  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // retrieve the context of the current query
      HttpContext httpContext = HttpContext.Current;
      // examine the query for the [lang] parameter
      // look for it in the URL parameters
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // look for it in the posted parameters
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // search for it in the user's session
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        // 1st header parameter HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // culture fr-FR
        langue = "fr-FR";
      }
      // put your tongue in session
      httpContext.Session["lang"] = langue;
      // changing thread cultures            
      Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
    }
  }
}

  • السطر 7: [I18NController] مشتق من فئة [Controller
  • السطر 7: تم إعلان الفئة على أنها [abstract] لمنع الإنشاء المباشر: لا يمكن استخدامها إلا بعد اشتقاقها؛
  • السطر 9: سيتم تنفيذ منشئ الفئة في كل مرة يتم فيها إنشاء مثيل لوحدة تحكم مشتقة من [I18NController
  • السطر 12: نسترد سياق طلب HTTP الذي تتم معالجته حاليًا بواسطة وحدة التحكم؛
  • السطر 15: نفترض أن اللغة يتم تعيينها بواسطة معلمة [lang] التي يمكن العثور عليها في مواقع مختلفة. نبحث بالترتيب التالي:
    • السطر 15: في معلمات عنوان URL [?lang=en-US
    • السطر 19: في المعلمات المنشورة [lang=de]،
    • السطر 24: في جلسة عمل المستخدم،
    • السطر 29: في تفضيلات اللغة المرسلة من قبل عميل HTTP،
    • السطر 26: إذا لم يتم العثور على شيء، يتم تعيين الإعدادات المحلية إلى [fr-FR
  • السطر 37: نقوم بتخزين الإعدادات المحلية في الجلسة. ومن هنا سيتم استردادها للطلبات اللاحقة. يمكن للمستخدم تغييرها عن طريق تضمينها في معلمات طلب GET أو POST؛
  • السطران 39-40: نقوم بتعيين الإعدادات المحلية للعرض الذي سيتم عرضه بعد معالجة الطلب الحالي.

سيكون وحدة التحكم [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);
      }
    }
}

  • السطر 7: [SecondController] يمتد من [I18NController]. وهذا يضمن تهيئة الإعدادات المحلية للعرض المراد عرضه؛
  • السطر 13: نستخدم نموذج العرض [ViewModel14]، الذي سنقدمه بعد قليل؛
  • السطران 13 و 20: تعرض طريقة العرض [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; }
 
    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // error list
      List<ValidationResult> résultats = new List<ValidationResult>();
      // the same error msg for all
      string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();
 
      // Date 1
      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 the list of errors
      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; }

في النسخة الدولية، السطر 1، يتم وضع النص المراد عرضه في ملف موارد. هنا، يُسمى هذا الملف [MyResources.resx] (typeof) وقد تم وضعه في جذر المشروع. ويُسمى ملف موارد.

لقد أنشأنا ثلاثة ملفات موارد هنا:

  • [MyResources]: المورد الافتراضي في حالة عدم وجود مورد للغة الحالية؛
  • [MyResources.fr-FR]: المورد الخاص بالإعدادات المحلية [fr-FR
  • [MyResources.en-US]: المورد الخاص بالإعدادات المحلية [en-US

لإنشاء ملف موارد، اتبع الخطوات التالية [1، 2، 3]:

يؤدي هذا إلى إنشاء ملف الموارد [MyResources2.resx]. عند النقر المزدوج عليه، ستظهر الصفحة التالية:

ملف الموارد هو قاموس يحتوي على مفاتيح وقيم مرتبطة بتلك المفاتيح. أدخل المفتاح في [1]، والقيمة في [2]، ونطاق المورد في [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]:

Image

عند النقر المزدوج على هذا الملف، يمكنك الوصول إلى كود فئة [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);
            }
        }
    }
}

  • السطر 1: مساحة اسم الفئة؛
  • السطر 11: أصبح المفتاح [infoRequise] خاصية ثابتة لفئة [MyResources]. ويمكن الوصول إليه عبر الترميز [MyResources.infoRequise]. بالإضافة إلى ذلك، لاحظ أن هذه الخاصية لها نطاق [public]. وبدون ذلك، لن يكون من الممكن الوصول إليها. من المهم تذكر ذلك لأن النطاق الافتراضي هو [internal]، وهذا قد يتسبب في أخطاء يصعب فهمها إذا نسيت تغيير النطاق.

لماذا يوجد الآن ثلاثة ملفات موارد؟

Image

لقد أنشأنا [MyResources.resx]. هذا هو المورد الجذري. بعد ذلك، نقوم بإنشاء عدد من ملفات الموارد [MyResources.locale.resx] يساوي عدد اللغات (اللغات) المطلوب إدارتها. هنا نتعامل مع الفرنسية [fr-FR] والإنجليزية الأمريكية [en-US]. عندما لا تكون الإعدادات المحلية الحالية [fr-FR] ولا [en-US]، يتم استخدام المورد الجذري [MyResources.resx].

المحتوى النهائي لملف [MyResources.resx] هو كما يلي:

Image

ستكون الرسائل باللغة الفرنسية عندما لا يتم التعرف على الإعدادات المحلية. المحتوى النهائي لملف [MyResources.fr-FR.resx] مطابق ويتم الحصول عليه ببساطة عن طريق نسخ الملف.

يتم الحصول على المحتوى النهائي لملف [MyResources.en-US.resx] أيضًا عن طريق نسخ الملف ثم تعديله على النحو التالي:

Image

لنعد إلى عرض [ViewModel14] وطريقة [Validate] الخاصة به:


    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // liste des erreurs
      List<ValidationResult> résultats = new List<ValidationResult>();
      // le même msg d'erreur pour tous
      string errorMessage=MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo(System.Web.HttpContext.Current.Session["lang"] as string)).ToString();
 
      // Date 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
      }
...
      // on rend la liste des erreurs
      return résultats;
}

يوضح السطر 7 كيفية استرداد رسالة من ملف الموارد [MyResources]. هنا، نريد استرداد الرسالة المرتبطة بالمفتاح [infoIncorrecte] في الثقافة الحالية:

  • MyResources.ResourceManager.GetObject("infoIncorrecte", new CultureInfo("en-US")) : يسترد الكائن المرتبط بالمفتاح [infoIncorrecte] من ملف الموارد [MyResources.en-US.resx
  • لقد رأينا أن وحدة التحكم [I18NController] تحدد الثقافة الحالية في الجلسة المرتبطة بالمفتاح [lang]. وبالتالي، يمكن استرداد الثقافة الحالية باستخدام System.Web.HttpContext.Current.Session["lang"] as string؛
  • يتم استرداد المورد كـ [object]. للحصول على رسالة الخطأ، نطبق عليها طريقة [ToString].

6.4. تدويل العرض [Action14Get.cshtml]

نقوم بتحديث عرض النموذج على النحو التالي:

Image


@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>
<!-- choice of language -->
@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 مع إصدار Visual Studio الخاص بك.

لنبدأ بالجزء الأبسط، الأسطر 36–38. تستخدم هذه الأسطر الخصائص الثابتة لفئة [MyResources] التي وصفناها للتو. للوصول إلى فئة [MyResources]، يجب استيراد مساحة اسمها (السطر 2).

في الرسائل المُترجمة، يجب عليك أيضًا تضمين الرسائل التي يعرضها إطار عمل التحقق من الصحة من جانب العميل. للقيام بذلك، استخدم مكتبات jQuery في الأسطر 17–19. نستخدم ملفات jQuery لللغتين اللتين ندعمهما: [fr- FR] و[en-US]. بالإضافة إلى ذلك، قد تتذكر أن طريقة العرض [Action13Get] استخدمت البرنامج النصي JavaScript التالي [myscripts.js]:


// document loading
$(document).ready(function () {
  var culture = 'fr-FR';
  Globalize.culture(culture);
});

الآن، لم تعد الثقافة مجرد [fr-FR]؛ بل أصبحت متنوعة. لذلك، يتم الآن إنشاء هذه الأسطر بواسطة عرض [Action14Get] نفسه في الأسطر 21–26. سيتم تضمين هذه الأسطر الست في صفحة HTML المرسلة إلى العميل.

  • السطر 23: يتم تهيئة متغير JavaScript [culture] بالثقافة الحالية للخيط الذي يعالج الطلب. قد تتذكر أن هذا تم تهيئته بواسطة منشئ فئة [I18NController]:


      // on met la langue en session
      httpContext.Session["lang"] = langue;
      // on modifie les cultures du thread            
      Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(langue);
Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;

إذا كانت الثقافة الحالية هي [en-US]، يصبح نص JavaScript المضمن في صفحة HTML كما يلي:


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

لقد ذكرنا سابقًا أن الدالة [$(document).ready] يتم تنفيذها بمجرد انتهاء المتصفح من تحميل الصفحة. سيؤدي تنفيذها إلى تعيين الثقافة لإطار عمل التحقق من الصحة من جانب العميل. مع الثقافة [en-US]، ستكون رسائل الخطأ الخاصة بالإطار باللغة الإنجليزية وستأتي من ملف الموارد [MyResources.en-US.resx]. سنرى كيف.

الآن دعونا نفحص الأسطر 57–65:


<!-- language selection -->
@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>
}

وهذا نموذج ثانٍ؛ أما النموذج الأول فيرد في الأسطر 31-53. يعرض هذا النموذج الروابط التالية في أسفل الصفحة:

  • السطر 2: يتم إرسال النموذج إلى الإجراء [Lang] في وحدة التحكم [Second]. في الوقت الحالي، لا نرى أي قيم يمكن إرسالها؛
  • السطران 6 و7: يؤدي النقر على الروابط إلى تشغيل دالة JavaScript [postForm]. أين توجد هذه الدالة؟ في البرنامج النصي [myscripts2.js] المشار إليه في السطر 20 من العرض:

محتواها كما يلي:


function postForm(lang, url) {
  // on récupère le deuxième formulaire du document
  var form = document.forms[1];
  // on lui ajoute l'hidden attribute lang
  var hiddenField = document.createElement("input");
  hiddenField.setAttribute("type", "hidden");
  hiddenField.setAttribute("name", "lang");
  hiddenField.setAttribute("value", lang);
  // ajout du champ caché dans le formulaire
  form.appendChild(hiddenField);
  // on lui ajoute l'hidden attribute url
  var hiddenField = document.createElement("input");
  hiddenField.setAttribute("type", "hidden");
  hiddenField.setAttribute("name", "url");
  hiddenField.setAttribute("value", url);
  // ajout du champ caché dans le formulaire
  form.appendChild(hiddenField);
  // soumission
  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) {
    //Use the Globalization plugin to parse the value        
    var val = Globalize.parseFloat(value);
    return this.optional(element) || (
        val >= param[0] && val <= param[1]);
  }
});

الأسطر 22–40 هي نفسها الموجودة بالفعل في البرنامج النصي [myscripts.js] المستخدم في المثال السابق. لن نعيد تناولها هنا. توجد الدالة [postForm]، التي يتم تنفيذها عند النقر على روابط اللغات، في الأسطر 1–20:

  • السطر 1: تأخذ الدالة معلمتين، [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>
  • السطر 3: نسترد مرجعًا إلى النموذج الثاني في المستند؛
  • الأسطر 5-8: نقوم بإنشاء العلامة برمجياً
<input type="hidden" value="xx-XX"/>

حيث [xx-XX] هي قيمة المعلمة [lang] للدالة؛

  • السطر 10: مرة أخرى، باستخدام الكود، نضيف هذا الحقل إلى النموذج الثاني. في النهاية، يتصرف هذا الحقل كما لو كان موجودًا في النموذج الثاني منذ البداية. وبالتالي، سيتم إرسال قيمته. وهذا بالضبط ما أردناه؛
  • الأسطر 11–17: نكرر نفس العملية لعلامة
<input type="hidden" value="url"/>

حيث [url] هي قيمة المعلمة [url] للدالة؛

  • السطر 19: تم الآن إرسال النموذج الثاني. إلى أي عنوان 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);
      }
 
      // language
      [HttpPost]
      public RedirectResult Lang(string url)
      {
        // we redirect the client to url
        return new RedirectResult(url);
      }
 
    }
  • السطر 18: لا يستجيب الإجراء إلا لـ [POST
  • السطر 19: يسترد فقط المعلمة المسماة [url]؛
  • السطر 22: يوجه العميل لإعادة التوجيه إلى عنوان URL هذا.

ولكن ماذا حدث للمعلمة المسماة [lang]؟ يجب أن نتذكر الآن أن وحدة التحكم [SecondController] مشتقة من فئة [I18NController] (السطر 1 أدناه). وهذه الوحدة هي التي تتعامل مع المعلمة [lang]:


  public abstract class I18NController : Controller
  {
    public I18NController()
    {
      // on récupère le contexte de la requête courante
      HttpContext httpContext = System.Web.HttpContext.Current;
      // on examine la requête à la recherche du paramètre [lang]
      // on le cherche dans les paramètres de l'URL
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // on le cherche dans les paramètres postés
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // on le cherche dans la session de l'utilisateur
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        // 1er paramètre de l'entête HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // culture fr-FR
        langue = "fr-FR";
      }
      // on met la langue en session
      httpContext.Session["lang"] = langue;
      // on modifie les cultures du thread            
      Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}

في مثالنا، يتم تمرير المعلمة [lang] بالرجوع. وبالتالي، سيتم العثور عليها في السطر 13، وتخزينها في الجلسة في السطر 31، واستخدامها لتحديث ثقافة الخيط الحالي في السطرين 33-34.

ماذا يحدث بعد ذلك؟ دعونا نراجع الروابط:


<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()
    {
      // on récupère le contexte de la requête courante
      HttpContext httpContext = System.Web.HttpContext.Current;
      // on examine la requête à la recherche du paramètre [lang]
      // on le cherche dans les paramètres de l'URL
      string langue = httpContext.Request.QueryString["lang"];
      if (langue == null)
      {
        // on le cherche dans les paramètres postés
        langue = httpContext.Request.Form["lang"];
      }
      if (langue == null)
      {
        // on le cherche dans la session de l'utilisateur
        langue = httpContext.Session["lang"] as string;
      }
      if (langue == null)
      {
        // 1er paramètre de l'entête HTTP AcceptLanguages
        langue = httpContext.Request.UserLanguages[0];
      }
      if (langue == null)
      {
        // culture fr-FR
        langue = "fr-FR";
      }
      // on met la langue en session
      httpContext.Session["lang"] = langue;
      // on modifie les cultures du thread            
      Thread.CurrentThread.CurrentCulture = new CultureInfo(langue);
      Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
}

هذه المرة، سيتم العثور على المعلمة [lang] في الجلسة في السطر 18. لنفترض أن قيمتها هي [en-US]. وبالتالي، تصبح هذه الثقافة هي ثقافة الخيط الذي ينفذ الطلب (السطران 33-34). لنعد إلى الإجراء [Action14Get]:


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

السطر 5، سيتم إنشاء مثيل لنموذج العرض [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; }
....

نظرًا لأن ثقافة مؤشر الترابط الحالي هي [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]، فإن البرنامج النصي المضمن في الصفحة في الأسطر 15–20 هو:


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

يضمن هذا أن إطار عمل التحقق من الصحة سيستخدم التنسيقات الأمريكية (التاريخ، العملة، الأرقام، إلخ). وللسبب نفسه، سيتم استرداد الرسائل الموجودة في الأسطر 30–32 من ملف الموارد [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] وتقويمها:

Image

يمكننا أن نرى أن التقويم هو تقويم فرنسي، على الرغم من أن ثقافة الصفحة هي [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>
...

  • السطر 6: استرداد الإعدادات المحلية من الجلسة؛
  • السطر 11: نضبط سمة [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 باللغة الإنجليزية بشكل افتراضي. لتدويله، تحتاج إلى إضافة البرامج النصية الموجودة على الرابط [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>
  }
  <!-- language selection -->
  @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>

ملاحظة: في السطر 16، اضبط إصدار jQuery UI ليتوافق مع الإصدار الذي قمت بتنزيله.

  • السطر 15: قم بالإشارة إلى ورقة أنماط jQuery UI؛
  • السطر 16: قم بالإشارة إلى الإصدار الذي تم تنزيله من jQuery UI؛
  • السطر 17: أشر إلى البرنامج النصي للتقويم الفرنسي الذي قمنا بتنزيله للتو؛
  • السطر 34: ستقوم طريقة [Html.TextBox] بإنشاء علامة [input] هنا، من النوع [text]، مع المعرف [Date1] والاسم [Date1
  • السطر 19: عند انتهاء تحميل الصفحة، سيتم تطبيق دالة jQuery UI [datepicker] على العنصر الذي يحمل المعرف [Date1]، أي العنصر الموجود في السطر 34. تضمن هذه الدالة أنه عند تركيز المستخدم على حقل الإدخال [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; }
 
    // manufacturer
    public ViewModel15()
    {
      // Culture of the moment
      Culture = HttpContext.Current.Session["lang"] as string;
      cultureInfo=new CultureInfo(Culture);
      // Regional calendar JQuery
      Regionale = MyResources.ResourceManager.GetObject("regionale", cultureInfo).ToString();
      // date format
      FormatDate = MyResources.ResourceManager.GetObject("formatDate", cultureInfo).ToString();
    }
 
 
 
    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      // error list
      List<ValidationResult> résultats = new List<ValidationResult>();
      // the same error msg for all
      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 the list of errors
      return résultats;
 
    }
 
    // fields outside the action model
    public string Culture { get; set; }
    public string Regionale { get; set; }
    public string StrDate1 { get; set; }
    public string FormatDate { get; set; }
 
    // local data
    private CultureInfo cultureInfo;
  }
}

بالمقارنة مع النموذج السابق [ViewModel14]، لدينا أربع خصائص إضافية:

  • السطر 60: لغة العرض، 'fr-FR' أو 'en-US'. يتم تهيئة هذه اللغة في المنشئ في السطر 26؛
  • السطر 61: الثقافة الإقليمية لتقويم jQuery، 'fr' للتقويم الفرنسي، '' للتقويم الإنجليزي. يتم تهيئة هذا الحقل في السطر 29 من المنشئ؛
  • السطر 63: تنسيق التاريخ من السطر 15: 'dd/MM/yyyy' للتاريخ الفرنسي، 'MM/dd/yyyy' للتاريخ الإنجليزي. يتم تهيئة هذا الحقل في السطر 31 من المنشئ؛
  • السطر 62: السلسلة المراد عرضها في حقل الإدخال [Date1]. سيتم تهيئة هذا الحقل بواسطة الإجراء؛
  • السطر 47: يتم الآن التحقق من صحة التاريخ [Regexp1] وفقًا لتنسيق الإعدادات المحلية الحالية.

توجد قيم خصائص [Regionale] و [FormatDate] في ملفات الموارد [MyResources]. تتغير ملفات الموارد الفرنسية [MyResources] [MyResources.fr-FR] [1] وملف الموارد الإنجليزي [2] على النحو التالي:

لقد أوشكنا على الانتهاء. نضيف إجراءً [Action15] إلى وحدة التحكم [SecondController]:


      // Action15
      public ViewResult Action15(FormCollection formData)
      {
        // method HTTP
        string method = Request.HttpMethod.ToLower();
        // model
        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);
        }
        // view display
        return View("Action15", modèle);
}

  • السطر 2: تعالج الطريقة [Action15] كل من طلبات [GET] و[POST]. في الحالة الأخيرة، يتم استرداد القيم المرسلة في المعلمة [formData
  • السطر 5: يتم استرداد طريقة HTTP للطلب؛
  • السطر 7: يتم إنشاء قالب العرض المراد عرضه (النموذج)؛
  • الأسطر 8-11: في حالة طلب [GET]، يتم تهيئة حقل الإدخال [Date1] بسلسلة فارغة؛
  • الأسطر 12-16: في حالة طلب [POST]:
    • السطر 14: يتم تهيئة النموذج بالقيم المرسلة،
    • السطر 15: يتم تهيئة حقل الإدخال [Date1] بسلسلة تمثل قيمة [Date1] منسقة وفقًا للإعدادات المحلية الحالية [dd/MM/yyyy] للتاريخ الفرنسي، و[MM/dd/yyyy] للتاريخ الإنجليزي؛
  • السطر 18: يتم عرض طريقة العرض [Action15.cshtml] مع القالب الخاص بها.

دعونا نجري بعض الاختبارات:

  • في [1]، تقويم باللغة الفرنسية عندما تكون الصفحة باللغة الفرنسية؛
  • في [2]، تقويم باللغة الإنجليزية عندما تكون الصفحة باللغة الإنجليزية؛
  • في [3]، تاريخ بتنسيق فرنسي عندما تكون الصفحة باللغة الفرنسية؛
  • في [4]، نفس التاريخ بتنسيق إنجليزي عندما تكون الصفحة باللغة الإنجليزية؛

6.7. الخلاصة

كما نرى، فإن موضوع تدويل التطبيقات موضوع معقد...