前言
這段時間在研究多語言的實(shí)現(xiàn),就找了NopCommerce這個開源項目來研究了一下,并把自己對這個項目的粗淺認(rèn)識與大家分享一下。
挺碰巧的是昨天收到了NopCommerce 3.90 發(fā)布測試版的郵件:
不啰嗦了,開始正題了!
其實(shí)對于Nop的多語言,最主要的元素有下面兩個:
WebWorkContext(IWorkContext的實(shí)現(xiàn)類)
LocalizationService(ILocalizationService的實(shí)現(xiàn)類)
其他相關(guān)的元素可以說都是在這兩個的基礎(chǔ)上體現(xiàn)價值的。
下面先來介紹一下WebWorkContext的WorkingLanguage屬性,這個是貫穿整個應(yīng)用的,所以必須要先從這個講起。
WorkingLanguage
WebWorkContext中對多語言來說最為重要的一個屬性就是WorkingLanguage,它決定了我們當(dāng)前瀏覽頁面所采用的是那種語言。
每次打開一個頁面,包括切換語言時,都是讀取這個WorkingLanguage的值。當(dāng)然在讀的時候,也做了不少操作:
從當(dāng)前上下文中的_cachedLanguage變量是否有值,有就直接讀取了這個值。
從GenericAttribute表中查詢當(dāng)前用戶的語言ID,這張表中的字段Key對應(yīng)的值是LanguageId時,就表明是某個用戶當(dāng)前正在使用的語言ID。
從Language表中查詢出語言信息(當(dāng)前店鋪->當(dāng)前店鋪默認(rèn)->當(dāng)前店鋪的第一個->所有語言的第一個)
查詢語言表時,首先查出店鋪支持的所有語言,然后找到當(dāng)前用戶正在使用的語言ID,根據(jù)這兩個條件組合得到的Language實(shí)體就是當(dāng)前的WorkingLanguage。
如果說這兩個條件的組合拿不到相應(yīng)的語言實(shí)體,就會根據(jù)當(dāng)前Store的默認(rèn)語言ID(如下圖所示)去找。
如果根據(jù)Store的默認(rèn)語言還是不能找到,就會取這個Store語言列表的第一個。
如果還是沒有查找到相應(yīng)的語言,那就不會根據(jù)Store去找語言,而是直接取所有發(fā)布語言中的第一個,這就要確保在數(shù)據(jù)庫中必須存在一個初始化的語言。
初始化對任何一個系統(tǒng)都是必不可少的??!
下面是這個屬性get具體的實(shí)現(xiàn)片段:
if (_cachedLanguage != null) return _cachedLanguage; Language detectedLanguage = null;if (_localizationSettings.SeoFriendlyUrlsForLanguagesEnabled) { //get language from URL detectedLanguage = GetLanguageFromUrl(); }if (detectedLanguage == null && _localizationSettings.AutomaticallyDetectLanguage) { //get language from browser settings //but we do it only once if (!this.CurrentCustomer.GetAttribute<bool>(SystemCustomerAttributeNames.LanguageAutomaticallyDetected, _genericAttributeService, _storeContext.CurrentStore.Id)) { detectedLanguage = GetLanguageFromBrowserSettings(); if (detectedLanguage != null) { _genericAttributeService.SaveAttribute(this.CurrentCustomer, SystemCustomerAttributeNames.LanguageAutomaticallyDetected, true, _storeContext.CurrentStore.Id); } } }if (detectedLanguage != null) { //the language is detected. now we need to save it if (this.CurrentCustomer.GetAttribute<int>(SystemCustomerAttributeNames.LanguageId, _genericAttributeService, _storeContext.CurrentStore.Id) != detectedLanguage.Id) { _genericAttributeService.SaveAttribute(this.CurrentCustomer, SystemCustomerAttributeNames.LanguageId, detectedLanguage.Id, _storeContext.CurrentStore.Id); } }var allLanguages = _languageService.GetAllLanguages(storeId: _storeContext.CurrentStore.Id);//find current customer languagevar languageId = this.CurrentCustomer.GetAttribute<int>(SystemCustomerAttributeNames.LanguageId, _genericAttributeService, _storeContext.CurrentStore.Id);var language = allLanguages.FirstOrDefault(x => x.Id == languageId);if (language == null) { //it not found, then let's load the default currency for the current language (if specified) languageId = _storeContext.CurrentStore.DefaultLanguageId; language = allLanguages.FirstOrDefault(x => x.Id == languageId); }if (language == null) { //it not specified, then return the first (filtered by current store) found one language = allLanguages.FirstOrDefault(); }if (language == null) { //it not specified, then return the first found one language = _languageService.GetAllLanguages().FirstOrDefault(); }//cache_cachedLanguage = language;return _cachedLanguage;
因?yàn)檫@里目前不涉及對這個屬性的set操作,只有在切換語言的時候會涉及,所以set的內(nèi)容會放到切換語言的小節(jié)說明。并且在大部分情況下,用到的都是get操作。
視圖中常規(guī)的用法
來看看Nop中比較常規(guī)的用法:
我拿了BlogMonths.cshtml中的一小段代碼做演示:
在視圖中,可以看到很多這樣的寫法,幾乎每個cshtml文件都會有!
這里的T其實(shí)是一個delegate。這個delegate有2個輸入?yún)?shù),并最終返回一個LocalizedString對象。
比較經(jīng)常的都是只用到了第一個參數(shù)。第一個參數(shù)就是對應(yīng) LocaleStringResource表中的ResourceName字段
可以把這個對應(yīng)關(guān)系理解為一個key-value,就像用網(wǎng)上不少資料用資源文件處理多語言那樣。
下圖是在LocaleStringResource表中用Blog做模糊查詢的示例結(jié)果:
至于第二個參數(shù)怎么用,想想我們string.Format的用法就知道個所以然了。只要在ResourcesValue中存儲一個帶有占位符的字符串即可!
上圖中也有部分ResourcesValue用到了這個占位符的寫法。
其實(shí)我們看了它的實(shí)現(xiàn)會更加清晰的理解:
public Localizer T { get { if (_localizer == null) { //null localizer //_localizer = (format, args) => new LocalizedString((args == null || args.Length == 0) ? format : string.Format(format, args)); //default localizer _localizer = (format, args) => { var resFormat = _localizationService.GetResource(format); if (string.IsNullOrEmpty(resFormat)) { return new LocalizedString(format); } return new LocalizedString((args == null || args.Length == 0) ? resFormat : string.Format(resFormat, args)); }; } return _localizer; } }
此時可能大家會有個疑問,這里返回的是一個LocalizedString對象,并不是一個字符串,那么,它是怎么輸出到頁面并呈現(xiàn)到我們面前的呢??
最開始的時候我也遲疑了一下,因?yàn)樵创a在手,所以查看了一下類的定義:
public class LocalizedString : MarshalByRefObject, IHtmlString{}
看到這個類繼承了IHtmlString接口,應(yīng)該就知道個七七八八了!這個接口的ToHtmlString方法就是問題的本質(zhì)所在!
當(dāng)斷點(diǎn)在LocalizedString實(shí)現(xiàn)的ToHtmlString方法時會發(fā)現(xiàn),大部分都是走的這個方法,返回的內(nèi)容也就是所謂鍵值對中的值。
其中還有部分是顯式調(diào)用Text等其他屬性的。
有興趣深入了解這個接口的內(nèi)容,可以去看看msdn上面相關(guān)的內(nèi)容。
視圖中強(qiáng)類型的使用
說起強(qiáng)類型,大家應(yīng)該也不會陌生,畢竟大部分的MVC教程都會涉及。
在System.Web.Mvc.Html這個命名空間下,有不少靜態(tài)類(如InputExtensions,SelectExtensions等)和靜態(tài)方法(如TextBoxFor,PasswordFor等)。
其中這些靜態(tài)方法中,以For結(jié)尾的都是歸屬于強(qiáng)類型。
看看它們的方法簽名就知道了為什么叫強(qiáng)類型了。
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression);
下面就來看看,Nop在多語言這一塊是怎么個強(qiáng)類型法。
Nop在強(qiáng)類型這一塊的就一個擴(kuò)展:NopLabelFor
Nop只在Nop.Admin這個項目中用到這個擴(kuò)展的,在Nop.Web是沒有用到的。
在我個人看來,這一塊的實(shí)現(xiàn)可以說是挺妙的!下面來看看它是怎么個妙法:
先來看看它的用法,既然是強(qiáng)類型的,就必然有兩個方面,一個是View,一個是Model
View中的用法
@Html.NopLabelFor(model => model.Name)
Model的定義
[NopResourceDisplayName("Admin.Configuration.Languages.Fields.Name")] [AllowHtml]public string Name { get; set; }
在View中的用法和其他強(qiáng)類型的寫法并沒有什么太大的區(qū)別!只是在Model定義的時候要加上一個Attribute做為標(biāo)識
下面來看看它的實(shí)現(xiàn),其實(shí)這個的實(shí)現(xiàn)主要涉及的相關(guān)類就只有兩個:
一個是視圖的擴(kuò)展-HtmlExtensions
一個是模型相關(guān)的Attribute-NopResourceDisplayName
先來看一下NopResourceDisplayName的實(shí)現(xiàn)
public class NopResourceDisplayName : System.ComponentModel.DisplayNameAttribute, IModelAttribute { private string _resourceValue = string.Empty; //private bool _resourceValueRetrived; public NopResourceDisplayName(string resourceKey) : base(resourceKey) { ResourceKey = resourceKey; } public string ResourceKey { get; set; } public override string DisplayName { get { //do not cache resources because it causes issues when you have multiple languages //if (!_resourceValueRetrived) //{ var langId = EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Id; _resourceValue = EngineContext.Current .Resolve<ILocalizationService>() .GetResource(ResourceKey, langId, true, ResourceKey); // _resourceValueRetrived = true; //} return _resourceValue; } } public string Name { get { return "NopResourceDisplayName"; } } }
重寫了DisplayNameAttribute的DisplayName ,這樣在界面中展示的時候就會顯示這個值 , 實(shí)現(xiàn)了IModelAttribute的Name。
其中DisplayName中是根據(jù)ResourcesKey去數(shù)據(jù)庫中找到要顯示的文字。Name是在HtmlExtensions中用于拿到對應(yīng)的NopResourceDisplayName對象。
然后是擴(kuò)展的具體寫法:
public static MvcHtmlString NopLabelFor<TModel, TValue>(this HtmlHelper<TModel> helper, Expression<Func<TModel, TValue>> expression, bool displayHint = true) { var result = new StringBuilder(); var metadata = ModelMetadata.FromLambdaExpression(expression, helper.ViewData); var hintResource = string.Empty; object value; if (metadata.AdditionalValues.TryGetValue("NopResourceDisplayName", out value)) { var resourceDisplayName = value as NopResourceDisplayName; if (resourceDisplayName != null && displayHint) { var langId = EngineContext.Current.Resolve<IWorkContext>().WorkingLanguage.Id; hintResource = EngineContext.Current.Resolve<ILocalizationService>() .GetResource(resourceDisplayName.ResourceKey + ".Hint", langId); result.Append(helper.Hint(hintResource).ToHtmlString()); } } result.Append(helper.LabelFor(expression, new { title = hintResource })); return MvcHtmlString.Create(result.ToString()); }
這個擴(kuò)展做的事其實(shí)也很簡單,根據(jù)模型的NopResourceDisplayName這個Attribute去顯示對應(yīng)的信息。
不過要注意的是在這里還做了一個額外的操作:在文字的前面添加了一個小圖標(biāo)!
可以看到這句代碼helper.Hint(hintResource).ToHtmlString()
,它調(diào)用了另一個Html的擴(kuò)展,這個擴(kuò)展就只是創(chuàng)建了一個img標(biāo)簽。
最后的效果如下:
這里還有一個關(guān)于驗(yàn)證相關(guān)的實(shí)現(xiàn),這里的多語言實(shí)現(xiàn)與強(qiáng)類型的實(shí)現(xiàn)相類似,就不重復(fù)了,它的實(shí)現(xiàn)依賴于FluentValidation。
模型Property的用法
上面提到的基本都是在頁面上的操作的多語言,Nop中還有不少是直接在controller等地方將多語言的結(jié)果查出來賦值給對應(yīng)的視圖模型再呈現(xiàn)到界面上的!這一點(diǎn)十分感謝 Spraus 前輩的評論提醒!
下面以首頁的Featured products為例補(bǔ)充說明一下這種用法。
foreach (var product in products) { var model = new ProductOverviewModel { Id = product.Id, Name = product.GetLocalized(x => x.Name), ShortDescription = product.GetLocalized(x => x.ShortDescription), FullDescription = product.GetLocalized(x => x.FullDescription), //... }; //other code}
通過上面的代碼片段,可以看出,它也是用了一個泛型的擴(kuò)展方法來實(shí)現(xiàn)的。這個擴(kuò)展方法就是GetLocalized。
大家應(yīng)該已經(jīng)發(fā)現(xiàn)這里的寫法與我們前面提到的強(qiáng)類型寫法有那么一點(diǎn)類似~~都是我們熟悉的lambda表達(dá)式。
有那么一點(diǎn)不同的是,這里的實(shí)現(xiàn)是借助了Linq的Expression。
var member = keySelector.Body as MemberExpression;var propInfo = member.Member as PropertyInfo; TPropType result = default(TPropType);string resultStr = string.Empty;string localeKeyGroup = typeof(T).Name;string localeKey = propInfo.Name;if (languageId > 0) { //localized value if (loadLocalizedValue) { var leService = EngineContext.Current.Resolve<ILocalizedEntityService>(); resultStr = leService.GetLocalizedValue(languageId, entity.Id, localeKeyGroup, localeKey); if (!String.IsNullOrEmpty(resultStr)) result = CommonHelper.To<TPropType>(resultStr); }
來源:http://catcher1994.cnblogs.com/
聲明: 本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責(zé)任的權(quán)利。如果您發(fā)現(xiàn)博客中出現(xiàn)了錯誤,或者有更好的建議、想法,請及時與我聯(lián)系?。∪绻胝椅宜较陆涣?,可以私信或者加我QQ。
http://www.cnblogs.com/catcher1994/p/6445871.html