# برنامه نویسی با محصولات مایکروسافت > برنامه نویسی مبتنی بر Microsoft .Net Framework > ASP.NET MVC > آموزش: نحوه ساخت برنامه چند زبانه با ASP.Net MVC4

## micro_bhk

سلام
توی یکی از پروژه هام نیاز به چند زبانه سازی با MVC داشتم، توی سایت برنامه نویس هم مطلبی در این رابطه پیدا نکردم، یه مقاله پیدا کردم که پیاده سازیش رو توضیح داده بود، به همین خاطر اون رو به صورت فارسی گذاشتم که دوستانی که نیاز دارن استفاده کنن.
لازم به ذکره که دقیقا همون مقاله رو ترجمه نکردم، اون چیزهایی که نیاز بود رو تو قالب یه پروژه گذاشتم.
برای توضیحات بیشتر میتونید به منبع اصلی مراجعه کنید:
http://geekswithblogs.net/shaunxu/ar...ion-1-day.aspx

تا جایی که لازم بوده نحوه کار توضیح داده شده.

طبق مثال موجود، برای این کار ما نیاز به 2 پروژه داریم

1. GlobalizationMvc
2. AVAResource


مراحل انجام کار
1. یک پروژه از نوع ASP .NET Mvc 4 Web Application ایجاد میکنیم و اسمش رو GlobalizationMVC4 قرار میدیم. 

2. حالا باید پروژه AVAResource رو ایجاد کنیم، برای اینکار از مسیر Files -> Add-> Visual C#‎ -> Windows یه پروژه از نوع Class Library ایجاد میکنیم و فایل Class1.cs رو که نیازی بهش نداریم، پاک میکنیم. این پروژه شامل فایل های Resource مربرط به هر زبان هست. 

3. برای اضافه کردن فایل Resource هر زبان، روی پروژه AVAResource راست کلیک و گزینه Add   و بعد New Item رو انتخاب میکنیم، از قسمت Visual C#‎ Item -> General گزینه Resource File رو انتخاب میکنیم و اسمش رو   Resource  قرار میدیم (این فایل رو به عنوان Resource پیش فرض در نظر میگیریم). برای اضافه کردن هر زبان، مراحل قسمت 4 رو انجام میدیم (یا از فایل ایجاد شده یک کپی میگیریم و اسمش رو تغییر میدیم)، یک فایل دیگه برای زبان فارسی اضافه میکنیم
استاندارد نامگذاری زبان ها به اینصورت هست که نام اختصاری زبانهایی رو که میخواید به عنوان پسوند فایل ایجاد شده قرار میدید مثلا برای فارسی Default.aspx.fa.resx  یا  Default.aspx.fa-IR.resx استفاده کنید. 

4. مقدار Name/Value مربوط به هر زبان رو اضافه کنید. ما تو این مثال 3 تا مقدار message، title و rtl (برای تعیین چپ به راست و راست به چپ ) استفاده کردیم

5. برای هر فایل Resource اگر بخوایم به روش این مثال فایل های Resource رو تو یه پروژه جدا قرار بدیم، لازمه که مقدار Access Modifier هر زبان رو برابر Public و تو قسمت Properties مقدار گزینه Bulid Action رو برابر با Embedded Resource قرار بدید.

6. AVAResource رو توی پروژه GlobalizationMVC4 رفرنس میدیم (برای این کار در پروژه GlobalizationMVC4 روی Refrence راست کلیک کرده، گزینه Add Refrence رو انتخاب میکنیم، از لیت پروژه های موجود تو تب Project، AVAResource رو انتخاب میکنیم)

7. توی پوشه App_Start، فایل RouteConfig.cs تیکه کد زیر رو به تابع RegisterRoutes اضافه می کنیم:

  routes.MapRoute(
                "Localization", // Route name
                "{lang}/{controller}/{action}/{id}", // URL with parameters
                new {controller = "Home", action = "Index", id = UrlParameter.Optional} // Parameter defaults
                );

همونطور که پیداست {lang} اضافه شده، که نشون دهنده این هستش که اولین مقداری که بعد از URL قرار میگیره، برای زبان هست.

8. یک Controller به اسم BaseController ایجاد میکنیم و نوع کلاس رو Abstract قرار میدیم، برای اینکار روی پوشه Controllers راست کلیک کرده، گزینه Add و سپس Controller رو انتخاب میکنیم.

9. توی کنترلر ایجاد شده (BaseController) کدهای زیر رو وارد میکنیم:

protected override bool DisableAsyncSupport
        {
            get
            {
                return true;
            }
        }

        protected override void ExecuteCore()
        {
            if (RouteData.Values["lang"] != null &&
                !string.IsNullOrWhiteSpace(RouteData.Values["lang"].ToString()))
            {
                // set the culture from the route data (url)
                var lang = RouteData.Values["lang"].ToString();
                Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(lang);
            }
            else
            {
                // load the culture info from the cookie
                var cookie = HttpContext.Request.Cookies["MvcLocalization.CurrentUICulture"];
                var langHeader = string.Empty;
                if (cookie != null)
                {
                    // set the culture by the cookie content
                    langHeader = cookie.Value;
                    Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(langHeader);
                }
                else
                {
                    // set the culture by the location if not speicified
                    langHeader = HttpContext.Request.UserLanguages[0];
                    Thread.CurrentThread.CurrentUICulture = CultureInfo.CreateSpecificCulture(langHeader);
                }
                // set the lang value into route data
                RouteData.Values["lang"] = langHeader;
            }

            // save the location into cookie
            HttpCookie _cookie = new HttpCookie("MvcLocalization.CurrentUICulture", Thread.CurrentThread.CurrentUICulture.Name);
            _cookie.Expires = DateTime.Now.AddYears(1);
            HttpContext.Response.SetCookie(_cookie);

            base.ExecuteCore();
        }


نکته: 
توی MVC 4 برای اینکه بخوایم متد ExecuteCore() رو Override کنیم، لازمه که مقدارد فلاگ DisableAsyncSupport برابر true قرار بدیم برای اینکار DisableAsyncSupport رو Override میکنیم.
تابع ExecuteCore هر بار که Controller ی رو اجرا می کنیم، فراخونی میشه، توی این تابع ما عمل چک کردن URL رو انجام میدیم، اگر که lang وجود داشته باشه، اونرو میخونیم و و ست میکنیم و در نهایت توی Cookie ذخیره میکنیم.

10. هر کنترلی که ایجاد میکنیم باید از کنترلر BaseController ارث برای کنه، مثلا تو این مثال کنترلر Home ما از این کلاس ارث بری کرده:

public class HomeController : BaseController

11. یک کلاس static درست میکنیم به اسم SwitchLanguageHelper، از این کلاس برای ساخت html helper link اختصاصی برای تغییر زبان استفاده می کنیم
کد های زیر رو توی این کلاس کپی می کنیم:

public class Language
        {
            public string Url { get; set; }
            public string ActionName { get; set; }
            public string ControllerName { get; set; }
            public RouteValueDictionary RouteValues { get; set; }
            public bool IsSelected { get; set; }

            public MvcHtmlString HtmlSafeUrl
            {
                get
                {
                    return MvcHtmlString.Create(Url);
                }
            }
        }

        public static Language LanguageUrl(this HtmlHelper helper, string cultureName,
            string languageRouteName = "lang", bool strictSelected = false)
        {
            // set the input language to lower
            cultureName = cultureName.ToLower();
            // retrieve the route values from the view context
            var routeValues = new RouteValueDictionary(helper.ViewContext.RouteData.  Values);
            // copy the query strings into the route values to generate the link
            var queryString = helper.ViewContext.HttpContext.Request.QueryString  ;
            foreach (string key in queryString)
            {
                if (queryString[key] != null && !string.IsNullOrWhiteSpace(key))
                {
                    if (routeValues.ContainsKey(key))
                    {
                        routeValues[key] = queryString[key];
                    }
                    else
                    {
                        routeValues.Add(key, queryString[key]);
                    }
                }
            }
            var actionName = routeValues["action"].ToString();
            var controllerName = routeValues["controller"].ToString();
            // set the language into route values
            routeValues[languageRouteName] = cultureName;
            // generate the language specify url
            var urlHelper = new UrlHelper(helper.ViewContext.RequestContext, helper.RouteCollection);
            var url = urlHelper.RouteUrl("Localization", routeValues);
            // check whether the current thread ui culture is this language
            var current_lang_name = Thread.CurrentThread.CurrentUICulture.Name.ToLower  ();
            var isSelected = strictSelected ?
                current_lang_name == cultureName :
                current_lang_name.StartsWith(cultureName);
            return new Language()
            {
                Url = url,
                ActionName = actionName,
                ControllerName = controllerName,
                RouteValues = routeValues,
                IsSelected = isSelected
            };
        }

        public static MvcHtmlString LanguageSelectorLink(this HtmlHelper helper,
            string cultureName, string selectedText, string unselectedText,
            IDictionary<string, object> htmlAttributes, string languageRouteName = "lang", bool strictSelected = false)
        {
            var language = helper.LanguageUrl(cultureName, languageRouteName, strictSelected);
            var link = helper.RouteLink(language.IsSelected ? selectedText : unselectedText,
                "Localization", language.RouteValues, htmlAttributes);
            return link;
        }


12. تقریبا کار تموم شده، توی هر پیجی که می خوایم تغییر زبان انجام بشه (معمولا در Master) تکه کد زیر رو توی کدهای html صفحه قرار میدیم:

@Html.LanguageSelectorLink("en", "[English]", "English", null)
@Html.LanguageSelectorLink("fa", "[فارسی]", "فارسی", null) 

به ازای هر زبان، یک LanguageSelectorLink مثل بالا قرار می دیم.


13. برای استفاده از Resource ها طبق حالت های زیر عمل میکنم:
استفاده در View: 
@AVAResource.Resource.message

استفاده در کلاس:
AVAResource.Resource.title;
مثلا:
ViewBag.Message = AVAResource.Resource.title;

14. برای راست به چپ یا بالعکس تگ Body رو بصورت زیر تغییر میدیم:
<body dir="@AVAResource.Resource.dir">


کار تمومه.

حجم فایل زیاد بود اون رو هم پیوست کردم. بخاطر محدویدیت در تعداد فایل های پیوست، تو 2تا پست فایل ها رو گذاشتم.
همه فایل های پیوست این پست و پست بعدی رو دانلود کنید و توی یک پوشه قرار بدید، و بعد فایل اول رو Extract کنید.

----------


## micro_bhk

ادامه فایل پیوست مثال

----------


## micro_bhk

با سلام
مثال قبل که در مورد چند زبانه سازی زده شد، برای سئو مناسب نیست.
برای رفع این مشکل میتونید از این طریق اقدام کنید:

در این روش ما نیازی به محتوای کلاس SwitchLanguageHelper نداریم، جای کدهای موجود، قطعه کد زیر رو قرار میدیم

public static MvcHtmlString MyActionLinkWithUrl(this HtmlHelper helper, string url, string htmlAttributes, string spanText)
        {
            string langHeader = CultureInfo.CurrentCulture.TextInfo.ToTitleCase(Th  read.CurrentThread.CurrentUICulture.ToString().Sub  string(0, 2).ToLower()); 
            string leftUrl = HttpContext.Current.Request.Url.GetLeftPart(UriPar  tial.Authority)+"/";
            var result = new StringBuilder();
            result.Append("<a href=\"");
            result.Append(leftUrl + langHeader + HttpUtility.HtmlAttributeEncode(url) + "\" " + htmlAttributes + ">");
            result.Append(spanText);
            result.Append("</a>");

            return new MvcHtmlString(result.ToString());
        }

این یک HtmlHelperهستش که میشه بهتر از این هم نوشت.

خوب ما لینک های تغییر زبان رو به این صورت تغییر میدیم:

<a href="@Request.Url.GetLeftPart(UriPartial.Authorit  y)/En">English</a>
    <a href="@Request.Url.GetLeftPart(UriPartial.Authorit  y)/Fa">فارسی</a> 


و هر جا که نیاز به استفاده از لینک داشتیم، از htmlHelper که ایجاد کردیم به اینصورت استفاده می کنیم:
@Html.MyActionLinkWithUrl("/عنوان لینک", "خصوصیات تگ", "آدرس")
مثل
@Html.MyActionLinkWithUrl("/Page/AboutUs", "class=\"icon_users\"", "درباره ما")

که مثلا در اینجا Page کنترلر ماست و AboutUs اکشن مورد نظر.
در قسمت خصوصیات تگ a میتونید از Id, style و مواردی که در تگ a بود استفاده ببرید
مثلا:
@Html.MyActionLinkWithUrl("/Page/AboutUs", "id=\"test\" class=\"icon_users\" style=\"color: red\" onclick=\"alert('ddd')\"" , "درباره ما")

و حتی جای عنوان لینک هم میتونید از عکس هم استفاده کنید مثلا:
@Html.MyActionLinkWithUrl("/Page/AboutUs", "", "<img src=\"pic.gif\" alt=\"pic\" />")


و در نهایت باید Route هارو بصورت زیر اصلاح کنید:

routes.MapRoute(
               "Localization1", // Route name
               "{lang}", // URL with parameters
               new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );


            routes.MapRoute(
                "Default", // Route name
                "{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

            routes.MapRoute(
                "Localization2", // Route name
                "{lang}/{controller}/{action}/{id}", // URL with parameters
                new { controller = "Home", action = "Index", id = UrlParameter.Optional }
            );

----------


## Wily_Fox

با سلام

متشکرم از پست خوبی که گذاشتید.
سوالی داشتم اگه امکانش هست پاسخ بدید:

آیا با این روش نیازی نیست در دیتابیس تغییری ایجاد کنیم؟

مثلا: در دیتابیس ما Menuها و Footerها قرار دارند. و از طرفی محتوای سایت رو هم در دیتابیس ذخیره کردیم. 

حالا من میخوام هم منوی فارسی داشته باشم هم منوی انگلیسی و... بجز محتوای page آیا با این روش میشه این حالت رو پوشش داد؟

یا باید جدولی در دیتابیس با عنوان Language ذخیره کنیم و Menu و Footerها رو باهاش در ارتباط کنیم؟

----------


## sdbkhsh

متشکرم از پست خوبی که گذاشتید. توضیحات بسیار کامل و مفید بود.
موفق و پیروز باشید.

----------


## micro_bhk

> با سلام
> 
> متشکرم از پست خوبی که گذاشتید.
> سوالی داشتم اگه امکانش هست پاسخ بدید:
> 
> آیا با این روش نیازی نیست در دیتابیس تغییری ایجاد کنیم؟
> 
> مثلا: در دیتابیس ما Menuها و Footerها قرار دارند. و از طرفی محتوای سایت رو هم در دیتابیس ذخیره کردیم. 
> 
> ...


سلام
این حالت فقط برای نمایش متن ها و عنوان های ثابت هستش
شما اگر منوهاتون استاتیک هست و نیازی ندارید که مدیر سایت روش تغییری اعمال کنه می تونید از این حالت استفاده کنید.

در واقع برای محتوای پویای سایت تو سیستم های چند زبانه روش های مختلفی هست. که با کمی جستجو میتونید اون روشی که به نسبت کار و حجم پروژتون نیاز هست رو انتخاب کنید

----------


## razi.66

خیلی خوب بود واقعا استفاده کردم ممنون

----------


## hasty0087

سلام.
من کدهای فوق رو استفاده کردم ولی MVCHtmlString و StringBuilder رو برا من نمی شناسه. چیکار باید بکنم؟

----------


## jaykob

> سلام.
> من کدهای فوق رو استفاده کردم ولی MVCHtmlString و StringBuilder رو برا من نمی شناسه. چیکار باید بکنم؟


سلام 

باید دو فضای نام زیر را اضافه کنید:

*System.Web.Mvc**System.Text*

----------


## hasty0087

> سلام 
> 
> باید دو فضای نام زیر را اضافه کنید:
> 
> *System.Web.Mvc*
> 
> *System.Text*


این دو فضای نام موجود هس،اما بازم خطا می ده

----------


## hasty0087

اون مشکل برطرف شد، اما الان که از دستور :
@Html.MyActionLinkWithUrl("/Page/AboutUs", "class=\"icon_users\"", "درباره ما")
استفاده می کنم، خطا می ده که /Fa/Home/AboutUs موجود نیست. چیکار باید انجام بدم؟

----------


## sniperb

سلام دوستان
برنامه ای من در basecontroller خطای زیر رو میده
کسی میتونه کمک کنه
*Server Error in '/' Application.**The current request for action 'Index' on controller type 'HomeController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Index() on type DB_Lab.Controllers.HomeController
System.Web.Mvc.ActionResult Index() on type DB_Lab.Controllers.BaseController**Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code. 

Exception Details: System.Reflection.AmbiguousMatchException: The current request for action 'Index' on controller type 'HomeController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Index() on type DB_Lab.Controllers.HomeController
System.Web.Mvc.ActionResult Index() on type DB_Lab.Controllers.BaseController

Source Error: 

Line 63:             HttpContext.Response.SetCookie(_cookie);
Line 64: 
Line 65:             base.ExecuteCore();
Line 66:         }
Line 67:



Source File: C:\Users\Behzad\Documents\Visual Studio 2015\Projects\DB Lab\DB Lab\Controllers\BaseController.cs    Line: 65 

Stack Trace: 

[AmbiguousMatchException: The current request for action 'Index' on controller type 'HomeController' is ambiguous between the following action methods:
System.Web.Mvc.ActionResult Index() on type DB_Lab.Controllers.HomeController
System.Web.Mvc.ActionResult Index() on type DB_Lab.Controllers.BaseController]
   System.Web.Mvc.Async.AsyncActionMethodSelector.Fin  dAction(ControllerContext controllerContext, String actionName) +163
   System.Web.Mvc.Async.ReflectedAsyncControllerDescr  iptor.FindAction(ControllerContext controllerContext, String actionName) +59
   System.Web.Mvc.ControllerActionInvoker.FindAction(  ControllerContext controllerContext, ControllerDescriptor controllerDescriptor, String actionName) +38
   System.Web.Mvc.ControllerActionInvoker.InvokeActio  n(ControllerContext controllerContext, String actionName) +88
   System.Web.Mvc.Controller.ExecuteCore() +89
   DB_Lab.Controllers.BaseController.ExecuteCore() in C:\Users\Behzad\Documents\Visual Studio 2015\Projects\DB Lab\DB Lab\Controllers\BaseController.cs:65
   System.Web.Mvc.ControllerBase.Execute(RequestConte  xt requestContext) +87
   System.Web.Mvc.<>c__DisplayClass15.<BeginExecute>b  __13() +32
   System.Web.Mvc.Async.<>c__DisplayClass1.<MakeVoidD  elegate>b__0() +35
   System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSy  nchronous>b__7(IAsyncResult _) +27
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsync  Result asyncResult, Object tag) +30
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsync  Result asyncResult, Object tag) +21
   System.Web.Mvc.Controller.EndExecute(IAsyncResult asyncResult) +29
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAs  yncController.EndExecute(IAsyncResult asyncResult) +24
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessReq  uest>b__3(IAsyncResult asyncResult) +31
   System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidD  elegate>b__3(IAsyncResult ar) +35
   System.Web.Mvc.Async.WrappedAsyncResult`1.End() +59
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsync  Result asyncResult, Object tag) +30
   System.Web.Mvc.Async.AsyncResultWrapper.End(IAsync  Result asyncResult, Object tag) +21
   System.Web.Mvc.MvcHandler.EndProcessRequest(IAsync  Result asyncResult) +29
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHan  dler.EndProcessRequest(IAsyncResult result) +23
   System.Web.CallHandlerExecutionStep.System.Web.Htt  pApplication.IExecutionStep.Execute() +9742689
   System.Web.HttpApplication.ExecuteStep(IExecutionS  tep step, Boolean& completedSynchronously) +155


*

----------


## sniperb

دوستان همچنین resource  رو رفرنس کردم ولی  تو mvc نمیشناسه!
چکار کنم؟

----------

