Objective
In fact, for NOP's multi-lingual, the main elements are the following two:
Other relevant elements can be said to reflect the value on the basis of these two.
Let's first introduce the Workinglanguage property of Webworkcontext, which runs through the whole application, so we must start with this.
Workinglanguage
One of the most important attributes in Webworkcontext for multiple languages is workinglanguage, which determines the language in which we are currently viewing the page.
Each time you open a page, including switching languages, the value of this workinglanguage is read. Of course, at the time of reading, also did a lot of operations:
If the _cachedlanguage variable in the current context has a value, the value is read directly.
Querying the current user's language ID from the genericattribute table, the value of the field Key in this table is LanguageID, which indicates the language ID that a user is currently using.
Language information is queried from the Language table (current store--current store--the first of all languages, the first of the current store)
When querying a language table, first identify all the languages supported by the store, and then find the language ID that the current user is using, and the language entity based on the combination of these two conditions is the current workinglanguage.
If the combination of these two conditions does not reach the corresponding language entity, it will be found based on the default language ID of the current store, as shown in.
If the default language of the store is still not available, the first one in the store language list will be taken.
If you do not find the appropriate language, you will not find the language based on the store, but instead take the first one in all the publishing languages, ensuring that an initialized language must exist in the database.
Initialization is essential for any system!!
Here is a specific implementation fragment of this Property Get:
if (_cachedlanguage! =Nullreturn _cachedlanguage; Language Detectedlanguage =Nullif (_localizationsettings.seofriendlyurlsforlanguagesenabled) {Get language from URL detectedlanguage = Getlanguagefromurl ();}if (Detectedlanguage = =Null && _localizationsettings.automaticallydetectlanguage) {Get language from browser settingsBut we did it only onceif (!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 itif (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.currentstor E.defaultlanguageid; Language = Alllanguages.firstordefault (x = x.id = = LanguageID);} if (language = null) {//it not Specified, then return to 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;
Because the set operation for this property is not currently involved, the contents of the set will be placed in the section description of the switching language only when the language is switched. And in most cases, a get operation is used.
General usage in the view
Take a look at the more general uses of NOP:
I took a small piece of code from blogmonths.cshtml to do a demo:
In the view, you can see a lot of such writing, almost every cshtml file will have!
The t here is actually a delegate. The delegate has 2 input parameters and eventually returns a LocalizedString object.
More often than not, only the first parameter is used. The first parameter is the ResourceName field in the corresponding localestringresource table
This correspondence can be understood as a key-value, as with a lot of information on the Web using resource files to deal with multiple languages.
Is the example result of using a blog to make a fuzzy query in the Localestringresource table:
As for the second parameter, think of us as a string. The use of format will know why. Just store a string with placeholders in Resourcesvalue!
Some of the resourcesvalue used the placeholder notation.
In fact, we see that its implementation will be more clearly understood:
Public Localizer t{Get {if (_localizer = =NULL) {//Null localizer_localizer =new localizedstring ((args = = null | | Args. Length = = 0)? Format:string. Format (format, args)); //default localizer _localizer = Span class= "Hljs-params" (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;}}
At this point, you may have a question, here is the return of a LocalizedString object, not a string, then, how it is output to the page and presented to us before it??
I hesitated at the beginning because the source was in hand, so I looked at the definition of the class:
public class LocalizedString : MarshalByRefObject, IHtmlString{}
See this class inherits the Ihtmlstring interface, should know a 7788! The tohtmlstring method of this interface is the essence of the problem!
When the breakpoint is implemented in the LocalizedString tohtmlstring method, it will be found that most of it is the way to go, the content returned is the so-called key value pairs in the value.
Some of the other properties, such as text, are explicitly called.
If you are interested in learning more about this interface, you can go to the relevant content on MSDN.
Use of strongly typed in views
Speaking of strong type, we should also not unfamiliar, after all, most of the MVC tutorial will be involved.
In the System.Web.Mvc.Html namespace, there are many static classes (such as inputextensions,selectextensions, etc.) and static methods (such as textboxfor,passwordfor, etc.).
Among these static methods, the for end is attributed to the strongly typed.
Look at their method signature and know why it's called a strong type.
public static MvcHtmlString TextBoxFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper, Expression<Func<TModel, TProperty>> expression);
Here's a look at how NOP is a strongly typed method in the multi-lingual piece.
NOP in the strong type this piece of an extension:noplabelfor
NOP only uses this extension in the Nop.admin project, which is not used in Nop.web.
In my personal view, this piece of implementation can be said to be very good! Let's take a look at how it looks:
First look at its usage, since it is a strong type, there must be two aspects, one is View, one is Model
Usage in view
@Html.NopLabelFor(model => model.Name)
Definition of model
[NopResourceDisplayName("Admin.Configuration.Languages.Fields.Name")]public string Name { get; set; }
There's no big difference between usage in view and other strong typing! Just add a attribute when the model is defined.
The following is a look at its implementation, in fact, the implementation of the main involved in the relevant class is only two:
Let's take a look at the implementation of Nopresourcedisplayname
PublicClass NopResourceDisplayName:System.ComponentModel.DisplayNameAttribute, imodelattribute{PrivateString _resourcevalue =String. Empty;private bool _resourcevalueretrived;Public Nopresourcedisplayname (String ResourceKey): Base (resourceKey) {resourceKey = ResourceKey;}PublicString ResourceKey {GetSet }public overrideString DisplayName {get {//do not cache resources because it causes issues when you hav E 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";}}
Rewrite the Displaynameattribute displayname so that it displays this value when displayed in the interface, implementing the Imodelattribute name.
One of the DisplayName is to find the text to display in the database according to Resourceskey. Name is used in htmlextensions to get the corresponding Nopresourcedisplayname object.
Then the specific wording of the extension:
PublicStatic 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;ObjectValueif (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 ());}
This extension is actually very simple, according to the model Nopresourcedisplayname this attribute to display the corresponding information.
Note, however, that there is an extra action in place: A small icon is added to the front of the text!
You can see this code helper.Hint(hintResource).ToHtmlString()
, which calls the extension of another HTML, which simply creates an IMG tag.
The final results are as follows:
There is also a validation-related implementation, where the multi-language implementation is similar to a strongly typed implementation, and is not duplicated, and its implementation relies on fluentvalidation.
Usage of model property
The above mentioned basic is on the page of the operation of the multi-language, NOP is also a lot of directly in the controller and other places to find the results of multiple languages to assign to the corresponding view model and then presented to the interface! This is very grateful to Spraus predecessors of the comments reminded!
The following is the first page of the featured products as an example to supplement this usage.
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}
As you can see from the code snippet above, it is also implemented using a generic extension method. The extension method is getlocalized.
We should have found that the wording here is similar to the strong typing we have mentioned earlier ~ ~ is a lambda expression we are familiar with.
One of the differences is that the implementation here is based on the expression of LINQ.
var member = Keyselector.bodyAs Memberexpression;var propinfo = member. MemberAs 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); }}//set default value if Requiredif (String.IsNullOrEmpty ( RESULTSTR) && returndefaultvalue) {var localizer = Keyselector.compile (); result = Localizer (entity);} return result;
The above is the core code snippet of this approach. Another data sheet is also covered here Localizedproperty
For the product, the meaning of this is to maintain multiple sets of different language products. Have someone to maintain this piece can do a better division of labor!
- EntityId, Entity ID (ex: Product ID)
- LanguageID-Language ID
- Localekeygroup Group (example: Commodity group, which is defined as a class name or table name)
- Localekey-Key (example: Product name, here is the property name of the class or the field name of the table)
- Localvalue-Value (example: Lumia 950XL, here is the property value of the class or the field value of the table)
Of course this way of doing this will cause the data volume of this table to soar! Especially when the commodity base is too large. At this point, the problem can be dealt with by dividing the library into a table.
Toggle Language
The toggle language in NOP is done by JS Jump after selecting it in a dropdown box.
window.location.href=/Common/SetLanguage/{langid}?returnUrl=xxx
As you can see, it is handled by the action of the setlanguage below Commoncontroller.
In the setlanguage processing, there are 4 major strides (the third step is to nop.web the project), the approximate process is as follows:
Which returns the Workinglanguage property of the current context (Workcontext) as the language entity found.
It also adds or updates records to the Genericattribute table, which is like a configuration table with a lot of configuration information. The basis for adding or updating here is Keygroup for Customer,key for LanguageID.
The snippet code for the specific settings is as follows:
var languageId = value != null ? value.Id : 0;_genericAttributeService.SaveAttribute(this.CurrentCustomer, SystemCustomerAttributeNames.LanguageId, languageId, _storeContext.CurrentStore.Id);//reset cache_cachedLanguage = null;
Summarize
There are many solutions for multiple languages, but the following are not the most common scenarios:
- External files such as resource files, XML files, etc.
- Database-based (field-level, table-level, etc.)
- Generate a separate page for each language
- Make a single site for each language
- Third-party translation API
NOP Multi-language is based on database implementation, I am also more inclined to this implementation!
Finally, a mind map is used to summarize the contents of this article.
A brief analysis of Nopcommerce's multi-language scheme