PDA

View Full Version : آموزش: نحوه ساخت برنامه چند زبانه با ASP.Net MVC4



micro_bhk
دوشنبه 11 دی 1391, 21:16 عصر
سلام
توی یکی از پروژه هام نیاز به چند زبانه سازی با MVC داشتم، توی سایت برنامه نویس هم مطلبی در این رابطه پیدا نکردم، یه مقاله پیدا کردم که پیاده سازیش رو توضیح داده بود، به همین خاطر اون رو به صورت فارسی گذاشتم که دوستانی که نیاز دارن استفاده کنن.
لازم به ذکره که دقیقا همون مقاله رو ترجمه نکردم، اون چیزهایی که نیاز بود رو تو قالب یه پروژه گذاشتم.
برای توضیحات بیشتر میتونید به منبع اصلی مراجعه کنید:

http://geekswithblogs.net/shaunxu/archive/2010/05/06/localization-in-asp.net-mvc-ndash-3-days-investigation-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
دوشنبه 11 دی 1391, 21:18 عصر
ادامه فایل پیوست مثال

micro_bhk
سه شنبه 26 دی 1391, 23:15 عصر
با سلام
مثال قبل که در مورد چند زبانه سازی زده شد، برای سئو مناسب نیست.
برای رفع این مشکل میتونید از این طریق اقدام کنید:

در این روش ما نیازی به محتوای کلاس 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.Authority)/En">English</a>
<a href="@Request.Url.GetLeftPart(UriPartial.Authority)/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
پنج شنبه 23 خرداد 1392, 10:03 صبح
با سلام

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

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

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

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

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

sdbkhsh
سه شنبه 20 اسفند 1392, 00:46 صبح
متشکرم از پست خوبی که گذاشتید. توضیحات بسیار کامل و مفید بود.
موفق و پیروز باشید.

micro_bhk
سه شنبه 20 اسفند 1392, 11:26 صبح
با سلام

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

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

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

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

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

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

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

razi.66
سه شنبه 13 آبان 1393, 13:33 عصر
خیلی خوب بود واقعا استفاده کردم ممنون

hasty0087
دوشنبه 10 آذر 1393, 09:45 صبح
سلام.
من کدهای فوق رو استفاده کردم ولی MVCHtmlString و StringBuilder رو برا من نمی شناسه. چیکار باید بکنم؟

jaykob
دوشنبه 10 آذر 1393, 10:44 صبح
سلام.
من کدهای فوق رو استفاده کردم ولی MVCHtmlString و StringBuilder رو برا من نمی شناسه. چیکار باید بکنم؟

سلام

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

System.Web.Mvc (http://msdn.microsoft.com/en-us/library/system.web.mvc.mvchtmlstring%28v=vs.118%29.aspx)Sy stem.Text (http://msdn.microsoft.com/en-us/library/system.text.stringbuilder%28v=vs.110%29.aspx)

hasty0087
دوشنبه 10 آذر 1393, 14:30 عصر
سلام

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

System.Web.Mvc (http://msdn.microsoft.com/en-us/library/system.web.mvc.mvchtmlstring%28v=vs.118%29.aspx)

System.Text (http://msdn.microsoft.com/en-us/library/system.text.stringbuilder%28v=vs.110%29.aspx)


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

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

sniperb
پنج شنبه 08 بهمن 1394, 12:44 عصر
سلام دوستان
برنامه ای من در 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.BaseControllerDescription: 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.<MakeVoidDelegate>b__0() +35
System.Web.Mvc.Async.<>c__DisplayClass8`1.<BeginSynchronous>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.<BeginProcessRequest>b__3(IAsyncResult asyncResult) +31
System.Web.Mvc.Async.<>c__DisplayClass4.<MakeVoidDelegate>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
پنج شنبه 08 بهمن 1394, 12:58 عصر
دوستان همچنین resource رو رفرنس کردم ولی تو mvc نمیشناسه!
چکار کنم؟