6. تدويل العروض
سنتناول هنا مشكلة تدويل العروض. إنها مشكلة معقدة يمكن العثور على وصف جيد لها في المقالة التالية لـ Scott Hanselman:
[http://www.hanselman.com/blog/GlobalizationInternationalizationAndLocalizationInASPNETMVC3JavaScriptAndJQueryPart1.aspx]
لنبدأ أولاً بتعريفه للمصطلحات المختلفة المتعلقة بتدويل العروض:
التدويل (i18n) | جعل التطبيق يدعم لغات وإعدادات محلية مختلفة |
التوطين (l10n) | جعل التطبيق يدعم لغة وإعدادات محلية محددة |
العولمة | الجمع بين Internationalisation و Localisation |
اللغة | اللغة المستخدمة – يُشار إليها برمز ISO (fr: الفرنسية، es: الإسبانية، en: الإنجليزية، ...) |
الإعدادات المحلية | نوع من اللغة – يُشار إليه أيضًا برمز ISO (en_GB: الإنجليزية البريطانية، en_US: الإنجليزية الأمريكية، ...) |
لنبدأ بمثال أول.
6.1. ترجمة الأعداد الحقيقية
يمكن ملاحظة وجود شذوذ في نموذج الإدخال السابق:
![]() |
بالنسبة للعدد الحقيقي، قمنا بكتابة [0,3] ولم يتم قبوله. يجب كتابة [0.3]:
![]() |
وبالتالي، فإن التنسيق المطلوب هو التنسيق الأنجلوساكسوني وليس التنسيق الفرنسي. عند البحث على الإنترنت، نجد بعض الحلول. وإليك أحدها.
تصبح الإجراءات [GET] و [POST] كما يلي:
// الإجراء 13-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>
ملاحظة: في السطر 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) {
//استخدم المكون الإضافي Globalization لتحليل القيمة
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);
});
لقد أشرت في السطر 1 إلى المكان الذي عُثر فيه على هذا البرنامج النصي. لن أحاول شرحه لأنني لا أفهمه. تتميز لغة جافا سكريبت أحيانًا ببعض الجوانب الغامضة. في الأسطر 4 و9 و15، يتم استخدام كائن [Globalize]. يتم توفير هذا الكائن بواسطة مكتبة JQuery Globalization التي يمكن الحصول عليها باستخدام [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>
- السطر 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));
}
...
// عند تحميل المستند
$(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] في قائمة نصوص جافا سكريبت المرتبطة بالوثيقة.
الآن، يمكننا اختبار التطبيق الجديد:
![]() |
يمكننا الآن كتابة [0,3] للرقم الحقيقي، وهو ما لم يكن ممكنًا من قبل. لكننا نواجه مشكلة أخرى:
فيما سبق، تسمح لنا عملية التحقق من صحة البيانات من جانب العميل بكتابة [11.2] باستخدام الترميز الأنجلوساكسوني. لا يتم قبول هذه القيمة من جانب الخادم عند التحقق من صحة النموذج:
يجب كتابة [11,2] وهنا يعمل الأمر على جانب العميل والخادم. يجب ألا يتم قبول الترميز الأنجلوساكسوني على جانب العميل. لا بد أن هذا ممكن...
لنتناول الآن تدويل العروض. سنواصل باستخدام مثال النموذج السابق بتقديمه بلغتين: الفرنسية والإنجليزية.
6.2. إدارة الثقافة
يتم التحكم في لغة العروض بواسطة الكائن [Thread.CurrentThread.CurrentUICulture]. لعرض الصفحات باللغة [fr-FR]، نكتب:
يتم التحكم في الترجمة (التواريخ والأرقام والعملات والساعات، ...) بواسطة الكائن [Thread.CurrentThread.CurrentCulture]. وبطريقة مشابهة لما تمت كتابته سابقًا، نكتب:
يمكن أن تكون هاتان التعليمتان في منشئ كل وحدة تحكم في التطبيق. ولكن قد نرغب أيضًا في تحليل هذا الرمز المشترك بين جميع وحدات التحكم. سنتبع هذا المسار.
نقوم بإنشاء عنصرين تحكم جديدين:
![]() |
- [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;
}
}
}
- السطر 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; }
// التحقق من الصحة
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();
// التاريخ 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
}
// البريد الإلكتروني 1
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; }
في النسخة الدولية، السطر 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]:
عند النقر المزدوج على هذا الملف، يمكن الوصول إلى كود الفئة [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]، وهذا يتسبب في أخطاء يصعب فهمها عندما ننسى تغيير هذا النطاق.
لماذا الآن، ثلاثة ملفات موارد؟
لقد أنشأنا [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();
// التاريخ 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult(errorMessage, new string[] { "Date1" }));
}
...
// عرض قائمة الأخطاء
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]
نقوم بتطوير عرض النموذج بالطريقة التالية:
![]() |
@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 مع إصدار Visual Studio الخاص بك.
لنبدأ بالأبسط، الأسطر 36-38. تستخدم هذه الأسطر الخصائص الثابتة لفئة [MyResources] التي وصفناها للتو. للوصول إلى فئة [MyResources]، يجب استيراد مساحة اسمها (السطر 2).
في الرسائل المُترجمة، يجب أيضًا تضمين تلك التي يعرضها إطار عمل التحقق من الصحة على جانب العميل. للقيام بذلك، يجب استخدام مكتبات JQuery في الأسطر 17-19. نستخدم ملفات JQuery للثقافتين اللتين نديرهما [fr-FR] و [en-US]. من ناحية أخرى، قد نتذكر أن العرض [Action13Get] كان يستخدم البرنامج النصي Javascript [myscripts.js] التالي:
// عند تحميل المستند
$(document).ready(function () {
var culture = 'fr-FR';
Globalize.culture(culture);
});
الآن، لم تعد الثقافة هي [fr-FR] فقط، بل أصبحت متنوعة. لذلك، يتم الآن إنشاء هذه الأسطر بواسطة العرض [Action14Get] نفسه في الأسطر 21-26. سيتم تضمين هذه الأسطر الست في الصفحة HTML المرسلة إلى العميل.
- السطر 23: يتم تهيئة متغير جافا سكريبت [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] يتم تنفيذها عند انتهاء المتصفح من تحميل الصفحة. سيؤدي تنفيذها إلى تحديد لغة إطار عمل التحقق من صحة البيانات من جانب العميل. مع الإعداد [en-US]، ستكون رسائل الخطأ الخاصة بالإطار باللغة الإنجليزية وستأتي من ملف الموارد [MyResources.en-US.resx]. سنرى كيف.
الآن دعونا نلقي نظرة على الأسطر 57-65:
<!-- اختيار لغة -->
@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: يؤدي النقر على الروابط إلى تنفيذ وظيفة جافا سكريبت [postForm]. أين توجد هذه الوظيفة؟ في البرنامج النصي [myscripts2.js] المشار إليه في السطر 20 من العرض:
![]() |
محتواها هو التالي:
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) {
//استخدم المكون الإضافي Globalization لتحليل القيمة
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: يتم إنشاء العلامة
حيث [xx-XX] هي قيمة المعلمة [lang] للدالة؛
- السطر 10: دائمًا عن طريق البرمجة، نضيف هذه العلامة إلى النموذج الثاني. في النهاية، يبدو الأمر كما لو أن هذه العلامة كانت موجودة منذ البداية في النموذج الثاني. وبالتالي، سيتم إرسال قيمتها. وهذا ما كنا نريده؛
- الأسطر 11-17: نكرر نفس الآلية لعلامة
حيث [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);
}
// اللغة
[HttpPost]
public RedirectResult Lang(string url)
{
// إعادة توجيه العميل إلى url
return new RedirectResult(url);
}
}
- السطر 18: لا تستجيب الإجراء إلا لـ [POST]؛
- السطر 19: لا تسترد سوى المعلمة المسماة [url]؛
- السطر 22: تستجيب للعميل بإعادة توجيهه إلى هذا 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]. وبالتالي، سيتم العثور عليها في السطر 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()
{
// استرداد سياق الطلب الحالي
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] في الجلسة في السطر 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] وتقويمها:
![]() |
نلاحظ أن التقويم هو تقويم فرنسي في حين أن ثقافة الصفحة هي [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]، لا يزال يُطلب إدخال التاريخ بالتنسيق الفرنسي يوم/شهر/سنة (20/11/2013) في حين أن التنسيق الأمريكي هو شهر/يوم/سنة (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>
ملاحظة: السطر 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; }
// المنشئ
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;
}
// حقول خارج نموذج الإجراء
public string Culture { get; set; }
public string Regionale { get; set; }
public string StrDate1 { get; set; }
public string FormatDate { get; set; }
// بيانات محلية
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)
{
// طريقة 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);
}
- السطر 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. Conclusion
من الواضح أن موضوع تدويل التطبيق هو موضوع معقد...
































