ایجاد چند زبانه در ASP.Net MVC

اگر کاربران وب سایت شما از نقاط مختلف جهان میباشند ، این کاربران علاقه مند هستند که محتوای وب سایت شما را با زبان خودشان مشاهده کنند . ایجاد یک وب سایت چند زبانه کاره زیاد آسانی نیست ، اما این امر باعث میشود تا مخاطبین بیشتری به وب سایت شما مراجعه کنند .خوشبختانه ، Net Framework. دارای مولفه ای برای پشتیبانی از زبان ها و Culture های مختلف میباشد .

ایجاد چند زبانه در ASP.Net MVC

Globalization و Localization در ASP.Net MVC : 

Internationalization (بین المللی کردن) شامل Globalization و Localization میباشد .  Globalization فرآیندی میباشد که در طی آن ظاهر و طراحی برنامه شما از Culture ها و فرهنگ های مختلف پشتیبانی میکند .  Localization سفارشی کردن یک برنامه برای یک زبان خاص میباشد . 


فرمت برای نام Culture عبارت است از : "<languagecode2>-<country/regioncode2>" که در آن 
<languagecode2> جزو کدهای زبان میباشد و <country/regioncode2> جزو کدهای Subculture میباشد . مثال ما شامل es-CL برای (Spanish(chile و en-US برای (English(United State میباشد . 

Internationalization معمولا با  "I18N" مخفف میشود . که در این کلمه مخفف حرف های ابتدایی و انتهایی Internationalization گرفته شده است و عدد 18 نشان دهنده تعداد حرف های ما بین این دو است . همین روش نیز برای Globalization و Localization نیز در نظر گرفته میشود و مخفف های "G11N" و "L10N" بکار گرفته میشود . 

ASP.Net دو مقدار از Culture را نگهداری میکند ، Culture و UICulture . مقدار Culture ، نتیجه توابع وابسته به Culture را تخمین میزند ، همانند Date , Number و فرمت پولی مربوط به آن را . اما UICulture اینکه کدام resource باید برای صفحه بارگذاری شود را بوسیله ResourceManager ، تخمین میزند . ResourceManager به دنبال Culture-specific Resource میگردد که بوسیله CurrentUICulture تخمین زده میشود . هر thread در Net. دارای اشیا CurrentCulture و CurrentUICulture میباشد . بنابراین ASP.Net مقادیر اینها را در زبان Render کردن توابع وابسته به Culture بررسی میکند . برای مثال ، اگر فرهنگ Thread جاری (CurrentCulture) روی en-US تنظیم شده باشد ()DateTime.Now.ToLongDateString مقدار "Saturday, January 08, 2011" را نمایش خواهد داد  ، اما اگر CurrentCulture بر روی en-CL تنظیم شده باشد نتیجه "sábado, 08 de enero de 2011" خواهد بود . 

حال مروری بر مطالبی که تا الان شرح دادیم را خواهیم داشت :

• Globalization - G11N : پردازشی که درآن برنامه را قادر به پشتیبانی از چندین زبان میکند . 
•Localization - L10N : پردازشی که در طی آن یک برنامه را برای یک زبان خاص سفارشی سازی میکند . 
• Internationalization - I18N : دو تعریف بالا یعنی Globalization و Localization را شرح میدهد . 
• Culture : این یک زبان است ، که بصورت اختیاری نیز میتواند یک region هم داشته باشد . 
• Local : همانند همان Culture میباشد . 
• Neutral culture : فرهنگی میباشد که دارای یک زبان مشخص و خاص است اما Region آن مشخص نیست . برای مثال : "en" , "es" 
• specific culture : فرهنگی میباشد که Culture و region آن مشخص است برای مثال : "en-US"

ممکن است این سوال در ذهن شما ایجاد شود که چرا ما به Region احتیاج داریم و Culture به تنهایی برای ما کفایت نمیکند ؟ 

شما ممکن است که اصلا به region احتیاجی پیدا نکنید . این درست است English در United State با English در United Kingdom یکسان نیست ، اما اگر برنامه شما فقط متن های خوانای انگلیسی برای کاربران این کشورها  ارائه کند ، شما به region اصلا احتیاجی نخواهید داشت . مشکل زمانی نمایان میشود که شما قصد استفاده از number , dates و واحد پولی این کشورها را داشته باشید . برای مثال ، خروجی زیر را برای دو Region مختلف از Spanish یعنی Chile و Mexico مقایسه کنید . 

int value = 5600;
 
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("es-CL");
Console.WriteLine(DateTime.Now.ToShortDateString());
Console.WriteLine(value.ToString("c"));
 
Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo("es-MX");
Console.WriteLine(DateTime.Now.ToShortDateString());
Console.WriteLine(value.ToString("c"));
 
// Output
26-07-2011 // Date in es-CL, Spanish (Chile)
$5.600,00 // Currency in es-CL, Spanish (Chile)
 
26/07/2011 // Date in es-MX, Spanish (Mexico)
$5,600.00 // Currency in es-MX, Spanish (Mexico)

توجه داشته باشید که تاریخ و واحد پولی آنها با هم متفاوت میباشد . جدا کنندگان اعداد هم در دو کشور متفاوت میباشد و اگر region را رعایت نکنید باعث سردرگمی کاربران خواهید شد . ما به Region برای این دست از مشکلات نیازمند هستیم  .

حال به سراغ بررسی چگونگی پشتیبانی از زبان های مختلف در ASP.Net MVC میرویم :

در اینجا دو روش اصلی برای ثبت Language و Culture مختلف در برنامه ASP.Net MVC معرفی خواهیم کرد :

1.  استفاده از Resource String در تمام View سایت خود . 
2. استفاده از View های مختلف برای هر زبان 
3. استفاده از ترکیب دو روش بالا 


#سوال -- کدام روش بهتر است ؟
این امر بسیار مهم است که کدام کارآمد تر است و سهولت بیشتری در استفاده دارد . برخی از مردم ترجیح میدهند که برای تمام زبان ها از یک View استفاده کنند و آن به این دلیل است که Maintainabilty ( نگهداری و توسعه کدها ) آسان تر است . در حالی که برخی دیگر از مردم معتقدند که استفاده از Resource  برای جابجا کردن محتوای سایت و تغییر آن به زبان دیگر ممکن است درهم ریختگی و بی نطمی بوجود بیاورد و ناخوانا باشد . بعضی از پروژه ها توسعه دهنده را مجبور میکنند که از Viewهای مختلف برای هر زبان استفاده کنند . اما گاهی اوقاتم شما مجبور به استفاده از View مختلف برای زبان های دیگر میباشید چون بعضی از آنها rtl و بعضی دیگر ltr میباشند . اگر شما "dir="ltr تنظیم کنید نیز در بعضی مواقع کفایت نمیکند و آن بهم ریختگی ها باز ایجاد خواهد شد . شاید ترکیبی از دو روش بالا بهترین روش باشد ، در این مثال ، برای استفاده از زبان های Spanish , English و Farsi ما مشکلی به استفاده از Resource ها در Layout نخواهیم خورد . 

چگونه ASP.Net زبان کاربر را حدس میزند؟!

در هر درخواست HTTP ،  یک فیلد در Header وجود دارد که Accept-Language نامیده میشود که زبان مرورگر کاربر را تخمین میزند  : 

Accept-Language: en-us,en;q=0.5

این بدان معناست که مرورگر ترجیح میدهد که از زبان en-us استفاده کند . اما این نوع های دیگر زبان انگلیسی را نیز میپذیرد . پارامتر "q" میزان تخمین زده شده برای علاقه مندی کاربر در استفاده از این زبان را نشان میدهد . شما میتوانید لیست زبان های مورد استفاده را توسط مرورگر خود مشخص کنید . 

Internet Explorer :

FireFox : 




روند Globalizing سایت : 
ما یک برنامه جدید ASP.Net MVC ایجاد میکنیم  و فرآیند Globalizing آن را مرحله به مرحله بررسی خواهیم کرد . 

یک پروژه جدید ASP.Net Web Application ایجاد کنید :





ایجاد Model :

public class Person
{
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public int Age { get; set; }
        public string Email { get; set; }
        public string Biography { get; set; }
}

پیام های اعتبارسنجی Internationalization :

Model ما هیچ اعتبارسنجی ای ندارد ، که این امر در برنامه های امروزی عُرف نمی باشد . ما میتوانیم برای اعتبارسنجی از صفت های Data Annotation استفاده کنیم . برای Globize کردن پیام های اعتبارسنجی ، ما به تعریف یکسری پارامترهای دیگر نیاز داریم . "ErrorMessageResourceType" نوع Resource را برای جستجوی پیام خطا ، مشخص میکند . "ErrorMessageResourceName" نام Resource را برای  جستجوی پیام خطا ، مشخص میکند . ResourceManager فایل مناسب Resource را بر مبنای CurrentCulture انتخاب میکند . 

حال کلاس Person را ویرایش میکنیم و صفت های زیر را به آن اضافه میکنیم :

public class Person
{
    [Display(Name = "FirstName", ResourceType = typeof(Resources.Resources))]    
    [Required(ErrorMessageResourceType = typeof(Resources.Resources),
              ErrorMessageResourceName = "FirstNameRequired")]
    [StringLength(50, ErrorMessageResourceType = typeof(Resources.Resources),
                      ErrorMessageResourceName = "FirstNameLong")]
    public string FirstName { get; set; }
    [Display(Name = "LastName", ResourceType = typeof(Resources.Resources))]    
    [Required(ErrorMessageResourceType = typeof(Resources.Resources),
              ErrorMessageResourceName = "LastNameRequired")]
    [StringLength(50, ErrorMessageResourceType = typeof(Resources.Resources),
                      ErrorMessageResourceName = "LastNameLong")]
    public string LastName { get; set; }
    [Display(Name = "Age", ResourceType = typeof(Resources.Resources))]    
    [Required(ErrorMessageResourceType = typeof(Resources.Resources),
              ErrorMessageResourceName = "AgeRequired")]
    [Range(0, 130, ErrorMessageResourceType = typeof(Resources.Resources),
                   ErrorMessageResourceName = "AgeRange")]
    public int Age { get; set; }
    [Display(Name = "Email", ResourceType = typeof(Resources.Resources))]    
    [Required(ErrorMessageResourceType = typeof(Resources.Resources),
              ErrorMessageResourceName = "EmailRequired")]
    [RegularExpression(".+@.+\\..+", ErrorMessageResourceType = typeof(Resources.Resources),
                                     ErrorMessageResourceName = "EmailInvalid")]
    public string Email { get; set; }
    [Display(Name = "Biography", ResourceType = typeof(Resources.Resources))]    
    public string Biography { get; set; }
}

پیامهای اعتبارسنجی Localizing Data Annotation :

به این دلیل که برای اعتبارسنجی در Model ما نیاز به استفاده از Data Annotation داریم ، ما مجبور خواهیم بود که resource string ترجمه شده را برای هر Culture ای که سایت ما پشتیبانی میکند ، اضافه کنیم . در مثال ما ، English , Spanish  و Farsi خواهد بود . 

ما فایل های Resource را در Assembly جداگاه ذخیره خواهیم کرد ، بنابراین ما میتوانیم از آنها در پروژه های بعدی نیز استفاده بنمائیم . 

روی Solution راست کلیک کرده و New Project را انتخاب کنید  . در پنجره باز شده Class Library را انتخاب کنید و نام آن را Resources بگذارید . 

حال روی پروژه Resources راست کلیک کرده و New Item را بزنید . در پنجره باز شده Resource File را انتخاب کرده و نام آن را Resources.resx بگذارید . این Culture پیش فرض ما خواهد بود (en-US) 





توجه داشته باشید که سطح دسترسی آن را Public بگذارید ، در این صورت این فایل در پروژه های دیگر نیز در دسترس خواهد بود . 

حال یک Resource جدید با نام Resources.es.resx ایجاد کنید ، مقادیر این فایل همانند زیر میباشد :



حال ، همین کار را برای نسخه فارسی نیز انجام دهید و نام آن را Resources.fa.resx بگذارید .

ما باید از درون برنامه به این Resource ها ارجاع داشته باشیم ، به این صورت ما میتوانیم فایل های Resource را در وب سایت خود داشته باشیم . روی Refrences کلیک راست کرده و Resources را انتخاب کنید 


Viewها : 
ما باید متن انگلیسی را از تمام View ها Extract کرده و آن را به فایل Resource منتقل کنیم . در اینجا یک ترفند وجود دارد ، بجای تایپ کردن هرباره ی نام NameSpace ، ما میتوانیم آن را به Web.Config اضافه کنیم ، به تصویر دقت کنید :



تخمین Culture : 

همانطور که پیش تر گفتیم ، در header یک فیلد با نام Accept-Language وجود دارد که در هربار درخواست مرورگر آن را می فرستد . این فیلد شامل یک لیستی از نام Culture ها میباشد (language-culture) که کاربر بر روی مرورگر خود تنظیم کرده است . مشکل این است که Culture های مشخص شده ، هیچ کدام آن Culture مد نظر کاربر نباشند . پس به همین دلیل ما این امکان را به کاربر میدهیم که بصورت صریح زبان برنامه را انتخاب کند . ما باید گزینه انتخابی کاربر را در Cookie ذخیره کنیم . ما یک Controller با نام Base Controller ایجاد میکنیم که ابتدا محتوای کوکی کاربر را بررسی میکند ، ما از فیلد Accept-language که توسط مرورگر کاربر فرستاده میشود ، استفاده میکنیم .یک Controller ایجاد کرده و همانند زیر نام آن را BaseController بگذارید :

public class BaseController : Controller
{
    protected override IAsyncResult BeginExecuteCore(AsyncCallback callback, object state)
    {
        string cultureName = null;
         
        // Attempt to read the culture cookie from Request
        HttpCookie cultureCookie = Request.Cookies["_culture"];
        if (cultureCookie != null)
            cultureName = cultureCookie.Value;
        else
            cultureName = Request.UserLanguages != null && Request.UserLanguages.Length > 0 ? 
                    Request.UserLanguages[0] :  // obtain it from HTTP header AcceptLanguages
                    null;
        // Validate culture name
        cultureName = CultureHelper.GetImplementedCulture(cultureName); // This is safe
         
        // Modify current thread's cultures            
        Thread.CurrentThread.CurrentCulture = new System.Globalization.CultureInfo(cultureName);
        Thread.CurrentThread.CurrentUICulture = Thread.CurrentThread.CurrentCulture;
         
        return base.BeginExecuteCore(callback, state);
    }
}


توجه داشته باشید که تمامی Controller های پروژه شما از BaseController ارث بری داشته باشند . Base Controller کوکی را بررسی میکند و اگر مقداری در آن موجود بود آن را در Current Thread Culture قرار میدهد . البته چون این مقدار در سمت کاربر ذخیره میشود  در هر بار استفاده باید آن را اعتبارسنجی کنیم . که برای این کار از کلاس helperای به نام CultureHelper استفاده میکنیم . اگر نام Culture معتبر نباشد ، این کلاس یک مقدار پیش فرض برای Culture قرار میدهد . 

کلاس CultureHelper :
CultureHelper اساس یک مزیت است که این امکان را به می دهد که نام Culture ای را که ما در سایت خود پیاده سازی کرده ایم را ذخیره کنیم :

public static class CultureHelper
{
    // Valid cultures
    private static readonly List<string> _validCultures = new List<string> { "af", "af-ZA", "sq", "sq-AL", "gsw-FR", "am-ET", "ar", "ar-DZ", "ar-BH", "ar-EG", "ar-IQ", "ar-JO", "ar-KW", "ar-LB", "ar-LY", "ar-MA", "ar-OM", "ar-QA", "ar-SA", "ar-SY", "ar-TN", "ar-AE", "ar-YE", "hy", "hy-AM", "as-IN", "az", "az-Cyrl-AZ", "az-Latn-AZ", "ba-RU", "eu", "eu-ES", "be", "be-BY", "bn-BD", "bn-IN", "bs-Cyrl-BA", "bs-Latn-BA", "br-FR", "bg", "bg-BG", "ca", "ca-ES", "zh-HK", "zh-MO", "zh-CN", "zh-Hans", "zh-SG", "zh-TW", "zh-Hant", "co-FR", "hr", "hr-HR", "hr-BA", "cs", "cs-CZ", "da", "da-DK", "prs-AF", "div", "div-MV", "nl", "nl-BE", "nl-NL", "en", "en-AU", "en-BZ", "en-CA", "en-029", "en-IN", "en-IE", "en-JM", "en-MY", "en-NZ", "en-PH", "en-SG", "en-ZA", "en-TT", "en-GB", "en-US", "en-ZW", "et", "et-EE", "fo", "fo-FO", "fil-PH", "fi", "fi-FI", "fr", "fr-BE", "fr-CA", "fr-FR", "fr-LU", "fr-MC", "fr-CH", "fy-NL", "gl", "gl-ES", "ka", "ka-GE", "de", "de-AT", "de-DE", "de-LI", "de-LU", "de-CH", "el", "el-GR", "kl-GL", "gu", "gu-IN", "ha-Latn-NG", "he", "he-IL", "hi", "hi-IN", "hu", "hu-HU", "is", "is-IS", "ig-NG", "id", "id-ID", "iu-Latn-CA", "iu-Cans-CA", "ga-IE", "xh-ZA", "zu-ZA", "it", "it-IT", "it-CH", "ja", "ja-JP", "kn", "kn-IN", "kk", "kk-KZ", "km-KH", "qut-GT", "rw-RW", "sw", "sw-KE", "kok", "kok-IN", "ko", "ko-KR", "ky", "ky-KG", "lo-LA", "lv", "lv-LV", "lt", "lt-LT", "wee-DE", "lb-LU", "mk", "mk-MK", "ms", "ms-BN", "ms-MY", "ml-IN", "mt-MT", "mi-NZ", "arn-CL", "mr", "mr-IN", "moh-CA", "mn", "mn-MN", "mn-Mong-CN", "ne-NP", "no", "nb-NO", "nn-NO", "oc-FR", "or-IN", "ps-AF", "fa", "fa-IR", "pl", "pl-PL", "pt", "pt-BR", "pt-PT", "pa", "pa-IN", "quz-BO", "quz-EC", "quz-PE", "ro", "ro-RO", "rm-CH", "ru", "ru-RU", "smn-FI", "smj-NO", "smj-SE", "se-FI", "se-NO", "se-SE", "sms-FI", "sma-NO", "sma-SE", "sa", "sa-IN", "sr", "sr-Cyrl-BA", "sr-Cyrl-SP", "sr-Latn-BA", "sr-Latn-SP", "nso-ZA", "tn-ZA", "si-LK", "sk", "sk-SK", "sl", "sl-SI", "es", "es-AR", "es-BO", "es-CL", "es-CO", "es-CR", "es-DO", "es-EC", "es-SV", "es-GT", "es-HN", "es-MX", "es-NI", "es-PA", "es-PY", "es-PE", "es-PR", "es-ES", "es-US", "es-UY", "es-VE", "sv", "sv-FI", "sv-SE", "syr", "syr-SY", "tg-Cyrl-TJ", "tzm-Latn-DZ", "ta", "ta-IN", "tt", "tt-RU", "te", "te-IN", "th", "th-TH", "bo-CN", "tr", "tr-TR", "tk-TM", "ug-CN", "uk", "uk-UA", "wen-DE", "ur", "ur-PK", "uz", "uz-Cyrl-UZ", "uz-Latn-UZ", "vi", "vi-VN", "cy-GB", "wo-SN", "sah-RU", "ii-CN", "yo-NG" };
    // Include ONLY cultures you are implementing
    private static readonly List<string> _cultures = new List<string> {
        "en-US",  // first culture is the DEFAULT
        "es", // Spanish NEUTRAL culture
        "ar"  // Arabic NEUTRAL culture
        
    };
    /// <summary>
    /// Returns true if the language is a right-to-left language. Otherwise, false.
    /// </summary>
    public static bool IsRighToLeft()
    {
        return System.Threading.Thread.CurrentThread.CurrentCulture.TextInfo.IsRightToLeft;
 
    }
    /// <summary>
    /// Returns a valid culture name based on "name" parameter. If "name" is not valid, it returns the default culture "en-US"
    /// </summary>
    /// <param name="name" />Culture's name (e.g. en-US)</param>
    public static string GetImplementedCulture(string name)
    {
        // make sure it's not null
        if (string.IsNullOrEmpty(name))
            return GetDefaultCulture(); // return Default culture
        // make sure it is a valid culture first
        if (_validCultures.Where(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Count() == 0)
            return GetDefaultCulture(); // return Default culture if it is invalid
        // if it is implemented, accept it
        if (_cultures.Where(c => c.Equals(name, StringComparison.InvariantCultureIgnoreCase)).Count() > 0)
            return name; // accept it
        // Find a close match. For example, if you have "en-US" defined and the user requests "en-GB", 
        // the function will return closes match that is "en-US" because at least the language is the same (ie English)  
        var n = GetNeutralCulture(name);
        foreach (var c in _cultures)
            if (c.StartsWith(n))
                return c;
        // else 
        // It is not implemented
        return GetDefaultCulture(); // return Default culture as no match found
    }
    /// <summary>
    /// Returns default culture name which is the first name decalared (e.g. en-US)
    /// </summary>
    /// <returns></returns>
    public static string GetDefaultCulture()
    {
        return _cultures[0]; // return Default culture
    }
    public static string GetCurrentCulture()
    {
        return Thread.CurrentThread.CurrentCulture.Name;
    }
    public static string GetCurrentNeutralCulture()
    {
        return GetNeutralCulture(Thread.CurrentThread.CurrentCulture.Name);
    }
    public static string GetNeutralCulture(string name)
    {
        if (!name.Contains("-")) return name;
             
        return name.Split('-')[0]; // Read first part only. E.g. "en", "es"
    }
}

ما باید "Culture_" را بصورت دستی پر کنیم . دیکشنری "Culture_" لیستی از نام Culture هایی را که توسط سایت ما پشتیبانی میشود را نگهداری میکند . تمام زیبایی این کلاس مفید در آن است که طریقه ی استفاده از آن همانند Culture میباشد . برای مثال ، اگر کاربری از united Kingdam سایت ما را مشاهده میکندو اگر در سایت ما Culture آن پیاده سازی نشده باشد ، کاربر سایت را با زبان انگلیسی دیگری مثل en-US مشاهده خواهد کرد . برای همین اگر واحد پولی و تاریخ برای شما مهم نمیباشد ، پیاده سازی تمام Culture ها ضرورتی ندارد . ResourceManager زمانی که Culture مشخص را پیدا نمیکند ، یک Neutral Culture را جایگزین آن میکنیم ، این مکانیزم خودکار fallback نامیده میشود . 

Controllerها :

Visual Studio بصورت پیش فرض یک Controller با نام Home برای ما ایجاد میکند ، خب ما هم برای سادگی از آن استفاده میکنیم . برای کارامد بودن این Controller آن را همانند زیر ویرایش میکنیم :

public class HomeController : BaseController
{
    [HttpGet]
    public ActionResult Index()
    {
        return View();
    }
    [HttpPost]
    public ActionResult Index(Person per)
    {
        return View();
    }
    public ActionResult SetCulture(string culture)
    {
        // Validate input
        culture = CultureHelper.GetImplementedCulture(culture);
        // Save culture in a cookie
        HttpCookie cookie = Request.Cookies["_culture"];
        if (cookie != null)
            cookie.Value = culture;   // update cookie value
        else
        {
            cookie = new HttpCookie("_culture");                
            cookie.Value = culture;
            cookie.Expires = DateTime.Now.AddYears(1);
        }
        Response.Cookies.Add(cookie);
        return RedirectToAction("Index");
    }                
 
}

"SetCulture" این امکان را برای کاربران فراهم می آورد که Current Culture خود را عوض کرده و این را در یک کوکی با نام Culture_ ذخیره کنند . ما فقط محدود به Cookie نیستیم و ما میتوانیم به جای آن نام Culture را در Session یا هر جای دیگر ذخیره سازی کنیم ، اما به این دلیل که Cookie ها هیچگونه حجمی در سمت سرور را اشغال نمیکنند ، هنوزم به عنوان سبک ترین و یکی از بهترین راه ها هستند . 

ایجاد یک View Template :

حال یک View برای متد Index از HomeController پیاده سازی میکینم .ابتدا View Index موجود را حذف میکنیم . حال با راست کلیک بر روی نام action Methode یک View جدید برای آن ایجاد میکنیم :

بعد از ساخته شدن View ، کدهای زیر را در آن قرار دهید :

@model MvcInternationalization.Models.Person
@{
    ViewBag.Title = Resources.AddPerson;
    var culture = System.Threading.Thread.CurrentThread.CurrentUICulture.Name.ToLowerInvariant();
}
@helper selected(string c, string culture)
{
    if (c == culture)
    {
        @:checked="checked"
    }
}
<h2>@Resources.AddPerson</h2>
@using(Html.BeginForm("SetCulture", "Home"))
{
    <fieldset>
        <legend>@Resources.ChooseYourLanguage</legend>
        <div class="control-group">
            <div class="controls">
                <label for="en-us">
                    <input name="culture" id="en-us" value="en-us" type="radio" @selected("en-us", culture) /> English
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="es">
                    <input name="culture" id="es" value="es" type="radio" @selected("es", culture) /> Español
                </label>
            </div>
        </div>
        <div class="control-group">
            <div class="controls">
                <label for="ar">
                    <input name="culture" id="ar" value="ar" type="radio" @selected("ar", culture) /> العربية
                </label>
            </div>
        </div>
       
    </fieldset>
        
     
     
}
@using (Html.BeginForm()) 
{
    @Html.AntiForgeryToken()
     
    <div class="form-horizontal">        
        <hr />
        @Html.ValidationSummary(true)
        <div class="form-group">
            @Html.LabelFor(model => model.FirstName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.FirstName)
                @Html.ValidationMessageFor(model => model.FirstName)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.LastName, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.LastName)
                @Html.ValidationMessageFor(model => model.LastName)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Age, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Age)
                @Html.ValidationMessageFor(model => model.Age)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Email, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Email)
                @Html.ValidationMessageFor(model => model.Email)
            </div>
        </div>
        <div class="form-group">
            @Html.LabelFor(model => model.Biography, new { @class = "control-label col-md-2" })
            <div class="col-md-10">
                @Html.EditorFor(model => model.Biography)
                @Html.ValidationMessageFor(model => model.Biography)
            </div>
        </div>
        <div class="form-group">
            <div class="col-md-offset-2 col-md-10">
                <input type="submit" value="@Resources.Create" class="btn btn-default" />
            </div>
        </div>
    </div>
}
@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
    <script type="text/javascript">
        (function ($) {
            $("input[type = 'radio']").click(function () {
                $(this).parents("form").submit(); // post form
            });
             
        })(jQuery);
    </script>
}

JavaScript بصورت خیلی ساده برای تنظیم Culture مقادیر فرم را Post میکند . البته Partial View ها را نباید فراموش کنیم :

@using Microsoft.AspNet.Identity
@if (Request.IsAuthenticated)
{
    using (Html.BeginForm("LogOff", "Account", FormMethod.Post, new { id = "logoutForm", @class = "navbar-right" }))
    {
    @Html.AntiForgeryToken()
    <ul class="nav navbar-nav navbar-right">
        <li>
            @Html.ActionLink(User.Identity.GetUserName(), "Manage", "Account", routeValues: null, htmlAttributes: new { title = "Manage" })
        </li>
        <li><a href="javascript:document.getElementById('logoutForm').submit()">@Resources.LogOff</a></li>
    </ul>
    }
}
else
{
    <ul class="nav navbar-nav navbar-right">
        <li>@Html.ActionLink(Resources.Register, "Register", "Account", routeValues: null, htmlAttributes: new { id = "registerLink" })</li>
        <li>@Html.ActionLink(Resources.LogOn, "Login", "Account", routeValues: null, htmlAttributes: new { id = "loginLink" })</li>
    </ul>
}


چپ به راست یا راست به چپ :

Html از زبان های rtl نیز پشتیبانی میکند . Layout.cshtml_ را بصورت زیر ویرایش کنید :

<!DOCTYPE html>
<html lang="@CultureHelper.GetCurrentNeutralCulture()" dir="@(CultureHelper.IsRighToLeft() ? "rtl" : "ltr")">
<head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>@ViewBag.Title - ASP.NET MVC Internationalization</title>
    @Styles.Render("~/Content/css" + (CultureHelper.IsRighToLeft() ? "-rtl" : ""))
    @Scripts.Render("~/bundles/modernizr")
</head>
<body>
    <div class="navbar navbar-inverse navbar-fixed-top">
        <div class="container">
            <div class="navbar-header">
                <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                    <span class="icon-bar"></span>
                </button>
                @Html.ActionLink("ASP.NET MVC Internationalization", "Index", "Home", null, new { @class = "navbar-brand" })
            </div>
            <div class="navbar-collapse collapse">
                <ul class="nav navbar-nav">                    
                </ul>
                @Html.Partial("_LoginPartial")
            </div>
        </div>
    </div>
    <div class="container body-content">
        @RenderBody()
        <hr />
        <footer>
            <p>@DateTime.Now</p>
        </footer>
    </div>
    @Scripts.Render("~/bundles/jquery")
    @Scripts.Render("~/bundles/bootstrap" + (CultureHelper.IsRighToLeft() ? "-rtl" : ""))
    @RenderSection("scripts", required: false)
</body>
</html>



حال بصورت اساسی ما نیاز به دو مجموعه فایل های CSS و JS داریم . یکی برای زبان های چپ به راست و دیگری برای زبان های راست به چپ . به این دلیل که قالب پیش فرض MVC از Bootstrap استفاده میکند ، این امر نیاز است که ما قالب rtl آن را نیز نصب کنیم . برای این از طریق Package Manager Console اقدام کرده و کد زیر را در آن تایپ کنید :

Install-Package Twitter.Bootstrap.RTL
 
توجه داشته باشید که فایل های bootstrap-rtl.js و bootstrap-rtl.css را داشته باشید . 

ما باید دو تا bundle ایجاد کنیم ، یکی برای rtl دیگری برای ltr . فایل BundleConfig.cs را همانند زیر ویرایش کنید . 

bundles.Add(new ScriptBundle("~/bundles/bootstrap-rtl").Include(
           "~/Scripts/bootstrap-rtl.js",
           "~/Scripts/respond.js"));
 
 bundles.Add(new StyleBundle("~/Content/css-rtl").Include(
           "~/Content/bootstrap-rtl.css",
           "~/Content/site.css"));


حال برنامه را اجرا کنید :

توجه داشته باشید که اعتبارسنجی سمت کاربر به زیبایی هر چه تمام تر کار میکند . بر تغییر Culture روی RadioButton های دلخواه خود کلیک کنید و توجه داشته باشید که قالب شما چگونه چپ به راست و راست به چپ میشود . استفاده از Viewهای جداگانه این امکان را به ما میدهد که موقعیت مکانی مولفه ها را کنترل کنیم و با تغییر Culture بی نظمی در قالب خود نداشته باشیم . 

دانلود فایل های ضمیمه مخصوص اعضای سایت می باشد !
کاربر مهمان! جهت دانلود و استفاده از امکانات سایت لطفا وارد سایت شوید و یا ثبت نام کنید
دانلود نسخه ی PDF این مطلب