public ActionResult SetLanguage(string langtag, string returnUrl) { // If valid 'langtag' passed. i18n.LanguageTag lt = i18n.LanguageTag.GetCachedInstance(langtag); if (lt.IsValid()) { // Set persistent cookie in the client to remember the language choice. Response.Cookies.Add(new System.Web.HttpCookie("i18n.langtag") { Value = lt.ToString(), HttpOnly = true, Expires = DateTime.UtcNow.AddYears(1) }); } // Owise...delete any 'language' cookie in the client. else { var cookie = Response.Cookies["i18n.langtag"]; if (cookie != null) { cookie.Value = null; cookie.Expires = DateTime.UtcNow.AddMonths(-1); } } // Update PAL setting so that new language is reflected in any URL patched in the // response (Late URL Localization). HttpContext.SetPrincipalAppLanguageForRequest(lt); // Patch in the new langtag into any return URL. if (returnUrl.IsSet()) { returnUrl = LocalizedApplication.Current.UrlLocalizerForApp.SetLangTagInUrlPath(HttpContext, returnUrl, UriKind.RelativeOrAbsolute, lt == null ? null : lt.ToString()).ToString(); } //Redirect user agent as approp. return(this.Redirect(returnUrl)); }
private void Application_SetAppLangulage(object sender, EventArgs e) { var langCookie = HttpContext.Current.Request.Cookies["Mcd_AM.LangTag"]; if (langCookie != null) { var langtag = langCookie.Value; i18n.LanguageTag lt = i18n.LanguageTag.GetCachedInstance(langtag); if (lt.IsValid()) { // Set persistent cookie in the client to remember the language choice. HttpContext.Current.Response.Cookies.Add(new HttpCookie("Mcd_AM.LangTag") { Value = lt.ToString(), HttpOnly = true, Expires = DateTime.UtcNow.AddYears(1) }); } // Owise...delete any 'language' cookie in the client. else { var cookie = HttpContext.Current.Response.Cookies["Mcd_AM.LangTag"]; if (cookie != null) { cookie.Value = null; cookie.Expires = DateTime.UtcNow.AddMonths(-1); } } // Update PAL setting so that new language is reflected in any URL patched in the // response (Late URL Localization). HttpContext.Current.SetPrincipalAppLanguageForRequest(lt); } }
public static LanguageTag GetMatchingAppLanguage(LanguageItem[] languages, int maxPasses = -1) { LanguageTag lt = null; LocalizedApplication.Current.TextLocalizerForApp.GetText(null, null, languages, out lt, maxPasses); return(lt); }
public string SetLangTagInUrlPath(System.Web.HttpContextBase context, string url, UriKind uriKind, string langtag) { switch (UrlLocalizationScheme) { case UrlLocalizationScheme.Scheme2: { if (DetermineDefaultLanguageFromRequest(context).Equals(langtag)) { return(url); } break; } case UrlLocalizationScheme.Scheme3: { throw new InvalidOperationException(); } } string siteRootPath = ExtractAnySiteRootPathFromUrl(ref url, uriKind); url = LanguageTag.SetLangTagInUrlPath(url, uriKind, langtag); // If site root path was trimmed from the URL above, add it back on now. if (siteRootPath != null) { PatchSiteRootPathIntoUrl(siteRootPath, ref url, uriKind); } return(url); }
protected void Application_BeginRequest(object source, EventArgs e) { i18n.LanguageTag lt = i18n.LanguageTag.GetCachedInstance("en"); if (lt.IsValid()) { Response.Cookies.Add(new HttpCookie("i18n.langtag") { Value = lt.ToString(), HttpOnly = true, Expires = DateTime.UtcNow.AddYears(1) }); } else { var cookie = Response.Cookies["i18n.langtag"]; if (cookie != null) { cookie.Value = null; cookie.Expires = DateTime.UtcNow.AddMonths(-1); } } if (HttpContext.Current.Request.RawUrl.ToLower().Contains("content") || HttpContext.Current.Request.RawUrl.ToLower().Contains("style")) { HttpContext.Current.Response.AddHeader("Access-Control-Allow-Origin", "*"); } HttpContext.Current.SetPrincipalAppLanguageForRequest(lt); }
/// <summary> /// Helper for instantiating a LanguageItem array from a compact string form /// previously returned by <see cref="DehydrateLanguageItemsToString"/>. /// </summary> /// <param name="strLanguageItems"> /// Compact string representation of a language item array. /// May be null/empty string in which case a a single-item language item array /// representing a null PAL is returned. /// Example values: /// "fr-CA;q=1,fr;q=0.5" /// "en-CA;q=2,de;q=0.5,en;q=1,fr-FR;q=0,ga;q=0.5" /// "en-CA;q=1,de;q=0.5,en;q=1,fr-FR;q=0,ga;q=0.5" /// "en-CA;q=1" /// "?;q=2" /// "?;q=2,de;q=0.5,en;q=1,fr-FR;q=0,ga;q=0.5" /// "" /// </param> /// <returns>A language item array e.g. a UserLanguages value.</returns> /// <exception cref="ArgumentException">strLanguageItems is invalid</exception> public static LanguageItem[] HydrateLanguageItemsFromString(string strLanguageItems) { int pos; LanguageTag pal = null; if (strLanguageItems.IsSet()) { // Read PAL from the front of the compact string. pos = strLanguageItems.IndexOf(';'); if (pos == -1) { throw new ArgumentException("strLanguageItems is invalid"); } string strPal = strLanguageItems.Substring(0, pos); if (strPal != "?") { pal = new LanguageTag(strPal); } // Strip off the PAL from the front of the compact string. pos = strLanguageItems.IndexOf(','); if (pos != -1) { strLanguageItems = strLanguageItems.Substring(pos + 1); } else { strLanguageItems = ""; } } // return(ParseHttpLanguageHeader(strLanguageItems, pal)); }
public ActionResult SetLanguage(string langtag, string returnUrl) { //if valid "langtag" passed i18n.LanguageTag lt = i18n.LanguageTag.GetCachedInstance(langtag); if (lt.IsValid()) { //Set persistent coockie in the client to remember the language choice. Response.Cookies.Add( new HttpCookie("i18n.langtag") { Value = lt.ToString(), HttpOnly = true, Expires = DateTime.UtcNow.AddYears(1) }); } //delete any language coockie in the client. else { var coockie = Response.Cookies["i18n.langtag"]; if (coockie != null) { coockie.Value = null; coockie.Expires = DateTime.UtcNow.AddMonths(-1); } } System.Web.HttpContext.Current.SetPrincipalAppLanguageForRequest(lt); //if(returnUrl.IsSet()) //{ // returnUrl = LocalizedApplication.Current.UrlLocalizerForApp.SetLangTagInUrlPath(HttpContext, returnUrl, UriKind.RelativeOrAbsolute, lt == null ? null : lt.ToString()).ToString(); //} return(Redirect(returnUrl)); }
public ActionResult SetLanguage(string langtag, string returnUrl) { try { // If valid 'langtag' passed. i18n.LanguageTag lt = i18n.LanguageTag.GetCachedInstance(langtag); if (lt.IsValid()) { WebsiteLanguage = langtag; // Set persistent cookie in the client to remember the language choice. Response.Cookies.Add(new HttpCookie(CommonsConst.Const.i18nlangtag) { Value = lt.ToString(), HttpOnly = true, Expires = DateTime.UtcNow.AddYears(1) }); if (User.Identity.IsAuthenticated) { _userService.UpdateLanguageUser(langtag, User.Identity.Name); UserSession LoggedUser = UserSession; LoggedUser.LanguageTag = langtag; UserSession = LoggedUser; } } // Owise...delete any 'language' cookie in the client. else { var cookie = Response.Cookies[CommonsConst.Const.i18nlangtag]; if (cookie != null) { cookie.Value = null; cookie.Expires = DateTime.UtcNow.AddMonths(-1); } } // Update PAL setting so that new language is reflected in any URL patched in the // response (Late URL Localization). System.Web.HttpContext.Current.SetPrincipalAppLanguageForRequest(lt); // Patch in the new langtag into any return URL. if (!string.IsNullOrEmpty(returnUrl)) { returnUrl = LocalizedApplication.Current.UrlLocalizerForApp.SetLangTagInUrlPath(HttpContext, returnUrl, UriKind.RelativeOrAbsolute, lt == null ? null : lt.ToString()).ToString(); returnUrl = returnUrl.Replace(lt.ToString() + "?", lt.ToString() + "/Home?"); } // Redirect user agent as approp. } catch (Exception e) { Commons.Logger.GenerateError(e, System.Reflection.MethodBase.GetCurrentMethod().DeclaringType, "Lanuguage = " + langtag + " and url = " + returnUrl); } if (returnUrl != "") { return(this.Redirect(returnUrl)); } else { return(null); } }
private static string GetCacheKey(string langtag) { //return string.Format("po:{0}", langtag).ToLowerInvariant(); // The above will cause a new string to be allocated. // So subsituted with the following code. // Obtain the cache key without allocating a new string (except for first time). // As this method is a high-frequency method, the overhead in the (lock-free) dictionary // lookup is thought to outweigh the potentially large number of temporary string allocations // and the consequently hastened garbage collections. return(LanguageTag.GetCachedInstance(langtag).GlobalKey); }
public EmployeeSession afterLoginInitial(string access_token, dynamic access_result) { EmployeeSession empSession = EmployeeSession.LoadByJsonString(HttpContext.Current.Session["empSession"].ToString()); empSession.accessToken = access_token; if (access_result.CompanyId != null) { empSession.companyId = access_result.CompanyId; } empSession.photoURL = access_result.PhotoURL; empSession.id = access_result.Id; empSession.firstName = access_result.FirstName; empSession.lastName = access_result.LastName; empSession.email = access_result.Email; if (access_result.AdminFlag != null) { empSession.adminFlag = bool.Parse((string)access_result.AdminFlag); } else { empSession.adminFlag = false; } empSession.issued = access_result.issued; empSession.expires = access_result.expires; empSession.employeeNumber = access_result.EmployeeNumber; if (access_result.Lang != null) { empSession.Lang = access_result.Lang; } else { empSession.Lang = "en"; } i18n.LanguageTag langTag = i18n.LanguageTag.GetCachedInstance(empSession.Lang); System.Web.HttpContext.Current.SetPrincipalAppLanguageForRequest(langTag); HttpContext.Current.Session["empSession"] = empSession.Serialize(); StringBuilder logMessage = new StringBuilder(); logMessage.AppendLine("audit: User Login Successful."); logMessage.AppendLine("email:" + empSession.email); Global._sfAuditLogger.Audit(logMessage); return(empSession); }
public virtual ConcurrentDictionary <string, LanguageTag> GetAppLanguages() { ConcurrentDictionary <string, LanguageTag> AppLanguages = (ConcurrentDictionary <string, LanguageTag>)System.Web.HttpRuntime.Cache["i18n.AppLanguages"]; if (AppLanguages != null) { return(AppLanguages); } lock (Sync) { AppLanguages = (ConcurrentDictionary <string, LanguageTag>)System.Web.HttpRuntime.Cache["i18n.AppLanguages"]; if (AppLanguages != null) { return(AppLanguages); } AppLanguages = new ConcurrentDictionary <string, LanguageTag>(); // Insert into cache. // NB: we do this before actually populating the collection. This is so that any changes to the // folders before we finish populating the collection will cause the cache item to be invalidated // and hence reloaded on next request, and so will not be missed. System.Web.HttpRuntime.Cache.Insert("i18n.AppLanguages", AppLanguages, _translationRepository.GetCacheDependencyForAllLanguages()); // Populate the collection. List <string> languages = _translationRepository.GetAvailableLanguages().Select(x => x.LanguageShortTag).ToList(); // Ensure default language is included in AppLanguages where appropriate. if (LocalizedApplication.Current.MessageKeyIsValueInDefaultLanguage && !languages.Any(x => LocalizedApplication.Current.DefaultLanguageTag.Equals(x))) { languages.Add(LocalizedApplication.Current.DefaultLanguageTag.ToString()); } foreach (var langtag in languages) { if (IsLanguageValid(langtag)) { AppLanguages[langtag] = LanguageTag.GetCachedInstance(langtag); } } // Done. return(AppLanguages); } }
public string ExtractLangTagFromUrl(System.Web.HttpContextBase context, string url, UriKind uriKind, bool incomingUrl, out string urlPatched) { string siteRootPath = ExtractAnySiteRootPathFromUrl(ref url, uriKind); string result = LanguageTag.ExtractLangTagFromUrl(url, uriKind, out urlPatched); switch (UrlLocalizationScheme) { case UrlLocalizationScheme.Scheme1: { break; } case UrlLocalizationScheme.Scheme2: { // If the URL is nonlocalized incoming URL, this implies default language. if (result == null && incomingUrl) { result = DetermineDefaultLanguageFromRequest(context).ToString(); urlPatched = url; } break; } case UrlLocalizationScheme.Scheme3: default: { throw new InvalidOperationException(); } } // If site root path was trimmed from the URL above, add it back on now. if (siteRootPath != null && urlPatched != null) { PatchSiteRootPathIntoUrl(siteRootPath, ref urlPatched, uriKind); } return(result); }
public static LanguageTag GetInferredLanguage(this HttpContextBase context) { // langtag = best match between // 1. Inferred user languages (cookie and Accept-Language header) // 2. App Languages. LanguageTag lt = null; HttpCookie cookie_langtag = context.Request.Cookies.Get("i18n.langtag"); if (cookie_langtag != null) { lt = LanguageHelpers.GetMatchingAppLanguage(cookie_langtag.Value); } if (lt == null) { lt = LanguageHelpers.GetMatchingAppLanguage(context.GetRequestUserLanguages()); } if (lt == null) { throw new InvalidOperationException("Expected GetRequestUserLanguages to fall back to default language."); } return(lt); }
/// <summary> /// Implements the Early Url Localization logic. /// <see href="https://docs.google.com/drawings/d/1cH3_PRAFHDz7N41l8Uz7hOIRGpmgaIlJe0fYSIOSZ_Y/edit?usp=sharing"/> /// </summary> public void ProcessIncoming( System.Web.HttpContextBase context) { // Is URL explicitly excluded from localization? if (!m_urlLocalizer.FilterIncoming(context.Request.Url)) { return; } // YES. Continue handling request. bool allowRedirect = context.Request.HttpMethod.Equals("GET", StringComparison.OrdinalIgnoreCase) || context.Request.HttpMethod.Equals("HEAD", StringComparison.OrdinalIgnoreCase); // NO. Is request URL localized? string urlNonlocalized; string langtag = m_urlLocalizer.ExtractLangTagFromUrl(context, context.Request.RawUrl, UriKind.Relative, true, out urlNonlocalized); if (langtag == null) { // NO. // langtag = best match between // 1. Inferred user languages (cookie and Accept-Language header) // 2. App Languages. LanguageTag lt = context.GetInferredLanguage(); // If redirection allowed...redirect user agent (browser) to localized URL. // The principle purpose of this redirection is to ensure the browser is showing the correct URL // in its address field. if (allowRedirect) { RedirectWithLanguage(context, context.Request.RawUrl, lt.ToString(), m_urlLocalizer); return; } // Otherwise, handle the request under the language infered above but without doing the redirect. // NB: this will mean that the user agent (browser) won't have the correct URL displayed; // however, this typically won't be an issue because we are talking about POST, PUT and DELETE methods // here which are typically not shown to the user. else { context.SetPrincipalAppLanguageForRequest(lt); return; // Continue handling request. } } // YES. Does langtag EXACTLY match an App Language? LanguageTag appLangTag = LanguageHelpers.GetMatchingAppLanguage(langtag); if (appLangTag.IsValid() && appLangTag.Equals(langtag)) { // YES. Establish langtag as the PAL for the request. context.SetPrincipalAppLanguageForRequest(appLangTag); // Rewrite URL for this request. context.RewritePath(urlNonlocalized); // Continue handling request. return; } // NO. Does langtag LOOSELY match an App Language? else if (appLangTag.IsValid() && !appLangTag.Equals(langtag)) { // YES. Localize URL with matching App Language. // Conditionally redirect user agent to localized URL. if (allowRedirect) { RedirectWithLanguage(context, urlNonlocalized, appLangTag.ToString(), m_urlLocalizer); return; } } // NO. Do nothing to URL; expect a 404 which corresponds to language not supported. // Continue handling request. }
/// <summary> /// Sessions the start. /// </summary> /// <param name="sender">Sender.</param> /// <param name="e">E.</param> private void Session_Start(object sender, EventArgs e) { string languageBrowser = null; try { if (Request.IsAuthenticated) { UserService _userService = new UserService(); var ConnectedUser = _userService.GetUserByUserName(User.Identity.Name); if (ConnectedUser != null) { languageBrowser = ConnectedUser.Language?.Code; } } else { Session[CommonsConst.Const.UserSession] = null; } if (String.IsNullOrEmpty(languageBrowser)) { string[] languages = Request?.UserLanguages; if (languages != null && languages.Length > 0) { string Favoritelanguage = languages[0]; languageBrowser = CommonsConst.Const.DefaultCulture; CategoryService _categoryService = new CategoryService(); var ListLanguages = _categoryService.GetSelectionList(CommonsConst.CategoryTypes.Language); if (ListLanguages != null && ListLanguages.Count > 0) { foreach (var Language in ListLanguages) { if (Language.Code == Favoritelanguage || Favoritelanguage.IndexOf(Language.Code + "-") > -1) { languageBrowser = Language.Code; break; } } } } } if (!String.IsNullOrEmpty(languageBrowser)) { Session[CommonsConst.Const.WebsiteLanguageSession] = languageBrowser; i18n.LanguageTag lt = i18n.LanguageTag.GetCachedInstance(languageBrowser); Response.Cookies.Add(new HttpCookie(CommonsConst.Const.i18nlangtag) { Value = lt.ToString(), HttpOnly = true, Expires = DateTime.UtcNow.AddYears(1) }); System.Web.HttpContext.Current.SetPrincipalAppLanguageForRequest(lt); } } catch (Exception ex) { Commons.Logger.GenerateError(ex, System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); } }
public virtual string GetText(string msgid, string msgcomment, LanguageItem[] languages, out LanguageTag o_langtag, int maxPasses = -1) { // Validate arguments. if (maxPasses > (int)LanguageTag.MatchGrade._MaxMatch + 1) { maxPasses = (int)LanguageTag.MatchGrade._MaxMatch + 1; } // Init. bool fallbackOnDefault = maxPasses == (int)LanguageTag.MatchGrade._MaxMatch + 1 || maxPasses == -1; // Determine the key for the msg lookup. This may be either msgid or msgid+msgcomment, depending on the prevalent // MessageContextEnabledFromComment setting. string msgkey = msgid == null ? msgid: TemplateItem.KeyFromMsgidAndComment(msgid, msgcomment, _settings.MessageContextEnabledFromComment); // Perform language matching based on UserLanguages, AppLanguages, and presence of // resource under msgid for any particular AppLanguage. string text; o_langtag = LanguageMatching.MatchLists( languages, GetAppLanguages().Values, msgkey, TryGetTextFor, out text, Math.Min(maxPasses, (int)LanguageTag.MatchGrade._MaxMatch)); // If match was successfull if (text != null) { // If the msgkey was returned...don't output that but rather the msgid as the msgkey // may be msgid+msgcomment. if (text == msgkey) { return(msgid); } return(text); } // Optionally try default language. if (fallbackOnDefault) { o_langtag = LocalizedApplication.Current.DefaultLanguageTag; return(msgid); } // return(null); }
/// <summary> /// Given a list of user-preferred languages (in order of precedence) and the list of languages /// in which an arbitrary resource is available (AppLanguages), returns the AppLanguage which /// the user is most likely able to understand. /// </summary> /// <param name="UserLanguages"> /// A list of user-preferred languages (in order of precedence). /// </param> /// <param name="AppLanguages"> /// The list of languages in which an arbitrary resource is available. /// </param> /// <param name="key"> /// Optionally specifies the key or a message to be looked up in order to validate /// a language selection. Only if the language passes the validation will it be selected. /// Set in conjunction with TryGetTextFor. /// May be null (while TryGetTextFor is non-null) which specifies that one or more messages /// must exists for a language for it to be considered valid (PO-valid). /// </param> /// <param name="TryGetTextFor"> /// Optional delegate to be called in order to validate a language for selection. /// See TextLocalizer.TryGetTextFor for more details. /// </param> /// <param name="o_text"> /// When language validation is enabled (TryGetTextFor is non-null) outputs the translated /// text that was returned by TryGetTextFor when the language was validated. /// If key == null then this will be set to "". /// </param> /// <param name="maxPasses"> /// 0 - allow exact match only /// 1 - allow exact match or default-region match only /// 2 - allow exact match or default-region match or script match only /// 3 - allow exact match or default-region match or script match or language match only /// -1 to set to most tolerant (i.e. 4). /// </param> /// <param name="relatedTo"> /// Optionally applies a filter to the user languages considered for a match. /// When set, then only user languages that have a matching language to that of relatedTo /// are considered. /// </param> /// <param name="palPrioritization"> /// Indicates whether PAL Prioritization is enabled. /// </param> /// <returns> /// LanguageTag instance selected from AppLanguages with the best match, or null if there is no match /// at all (or UserLanguages and/or AppLanguages is empty). /// It is possible for there to be no match at all if no language subtag in the UserLanguages tags /// matches the same of any of the tags in AppLanguages list. /// </returns> /// <exception cref="System.ArgumentNullException">Thrown if UserLanguages or AppLanguages is null.</exception> /// <remarks> /// This method called many times per request. Every effort taken to avoid it making any heap allocations.<br/> /// <br/> /// Principle Application Language (PAL) Prioritization:<br/> /// User has selected an explicit language in the webapp e.g. fr-CH (i.e. PAL is set to fr-CH). /// Their browser is set to languages en-US, zh-Hans. /// Therefore, UserLanguages[] equals fr-CH, en-US, zh-Hans. /// We don't have a particular message in fr-CH, but have it in fr and fr-CA. /// We also have message in en-US and zh-Hans. /// We presume the message from fr or fr-CA is better match than en-US or zh-Hans. /// However, without PAL prioritization, en-US is returned and failing that, zh-Hans. /// Therefore, for the 1st entry in UserLanguages (i.e. explicit user selection in app) /// we try all match grades first. Only if there is no match whatsoever for the PAL /// do we move no to the other (browser) languages, where return to prioritizing match grade /// i.e. loop through all the languages first at the strictest match grade before loosening /// to the next match grade, and so on. /// Refinement to PAL Prioritization:<br/> /// UserLanguages (UL) = de-ch,de-at (PAL = de-ch)<br/> /// AppLanguages (AL) = de,de-at,en<br/> /// There is no exact match for PAL in AppLanguages.<br/> /// However:<br/> /// 1. the second UL (de-at) has an exact match with an AL<br/> /// 2. the parent of the PAL (de) has an exact match with an AL.<br/> /// Normally, PAL Prioritization means that 2. takes precedence. /// However, that means choosing de over de-at, when the user /// has said they understand de-at (it being preferable to be /// more specific, esp. in the case of different scripts under /// the same language).<br/> /// Therefore, as a refinement to PAL Prioritization, before selecting /// 'de' we run the full algorithm again (without PAL Prioritization) /// but only considering langtags related to the PAL. /// </remarks> public static LanguageTag MatchLists( LanguageItem[] UserLanguages, IEnumerable <LanguageTag> AppLanguages, string key, Func <string, string, string> TryGetTextFor, out string o_text, int maxPasses = -1, LanguageTag relatedTo = null, bool palPrioritization = true) { int idxUserLang = 0; LanguageTag ltUser; // Validate arguments. if (UserLanguages == null) { throw new ArgumentNullException("UserLanguages"); } if (AppLanguages == null) { throw new ArgumentNullException("AppLanguages"); } if (maxPasses > (int)LanguageTag.MatchGrade._MaxMatch) { maxPasses = (int)LanguageTag.MatchGrade._MaxMatch; } //#78 //if (key != null && key.Equals("Sign In", StringComparison.InvariantCultureIgnoreCase)) { // key = key; } // If one or more UserLanguages determined for the current request if (UserLanguages.Length != 0) { // First, find any match for the PAL (see PAL Prioritization notes above). // If a PAL has been determined for the request if (palPrioritization && (ltUser = (LanguageTag)UserLanguages[0].LanguageTag) != null && (relatedTo == null || ltUser.Match(relatedTo, LanguageTag.MatchGrade.LanguageMatch) != 0)) // Apply any filter on eligible user languages. // Wiz through all match grades for the Principle Application Language. { for (int pass = 0; pass <= (int)LanguageTag.MatchGrade._MaxMatch; ++pass) { LanguageTag.MatchGrade matchGrade = (LanguageTag.MatchGrade)pass; foreach (LanguageTag langApp in AppLanguages) { // If languages do not match at the current grade...goto next. if (ltUser.Match(langApp, matchGrade) == 0) { continue; } // Optionally test for a resource of the given key in the matching language. if (TryGetTextFor != null) { o_text = TryGetTextFor(langApp.ToString(), key); if (o_text == null) { continue; } } else { o_text = null; } // We have a match between PAL and an AL that is NOT an exact match, // there may be a UL that is related to the PAL but has a closer (more specific) // match to an AL. See "Refinement to PAL Prioritization" notes above for more details. if (matchGrade != LanguageTag.MatchGrade.ExactMatch) { LanguageTag lt = MatchLists( UserLanguages, AppLanguages, key, TryGetTextFor, out o_text, maxPasses, langApp, false); // false = disable PAL Prioritization. if (lt != null) { return(lt); } } // Match. ++UserLanguages[idxUserLang].UseCount; return(langApp); } } } // PAL didn't match so skip over that now. ++idxUserLang; // No match for PAL, so now try for the browser languages, this time prioritizing the // match grade. for (int pass = 0; pass <= (int)LanguageTag.MatchGrade._MaxMatch; ++pass) { LanguageTag.MatchGrade matchGrade = (LanguageTag.MatchGrade)pass; for (int i = idxUserLang; i < UserLanguages.Length; ++i) { ltUser = (LanguageTag)UserLanguages[i].LanguageTag; if (ltUser == null) { continue; } // TODO: move the Match functionality to this class, and make it operate on ILanguageTag. // Or consider making the Match logic more abstract, e.g. requesting number of passes from // the object, and passing a pass value through to Match. // Apply any filter on eligible user languages. if (relatedTo != null) { if (ltUser.Match(relatedTo, LanguageTag.MatchGrade.LanguageMatch) == 0) { continue; } } foreach (LanguageTag langApp in AppLanguages) { // If languages do not match at the current grade...goto next. if (ltUser.Match(langApp, matchGrade) == 0) { continue; } // Optionally test for a resource of the given key in the matching language. if (TryGetTextFor != null) { o_text = TryGetTextFor(langApp.ToString(), key); if (o_text == null) { continue; } } else { o_text = null; } // Match. ++UserLanguages[i].UseCount; return(langApp); } } } } // No match at all. o_text = null; return(null); }
// Static helpers /// <summary> /// Parses an HTTP Accept-Language or Content-Language header value, returning /// a representative ordered array of LanguageItem instances, sorted in order of /// language preference. /// E.g. "de;q=0.5, en;q=1, fr-FR;q=0,ga;q=0.5". /// Notably, is able to re-order elements based on quality. /// </summary> /// <remarks> /// The first element position in the returned array is reserved for an item that /// describes the Principal Application Language (PAL) for the request. If/when the PAL /// is not set, that element will be a null item (LanguageItem.LanguageTag == null). /// /// This method is designed to be as efficient as possible, typically requiring /// only a single heap alloc, for the returned array object itself. /// </remarks> /// <param name="headerval"> /// HTTP Accept-Language header value. /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html. /// May be null or empty string for zero languages. /// </param> /// <param name="pal"> /// Optional language to store at the first element position in the array, which is reserved /// for the Principal Application Language (PAL). Any such LanguageItem stored that has a quality /// value of 2 (LanguageItem.PalQualitySetting). Null if no such language to be stored there and the /// item to be set as null (LanguageItem.LanguageTag == null). /// </param> /// <returns> /// Array of languages items (with possibly null LanguageTag members) sorted in order or language preference. /// </returns> public static LanguageItem[] ParseHttpLanguageHeader(string headerval, ILanguageTag pal = null) { // This method is designed to be as efficient as possible (avoiding string allocations where possible). // int begin, end, pos1; int len = headerval != null ? headerval.Length : 0; int ordinal = 0; // Init array with enough elements for each language entry in the header. var LanguageItems = new LanguageItem[(len > 0 ? headerval.CountOfChar(',') + 1 : 0) + 1]; // First element position is reserved for any PAL. LanguageItems[ordinal] = new LanguageItem(pal, PalQualitySetting, ordinal); ++ordinal; // For each language component of the header (delimited by comma) for (begin = 0; begin < len; begin = end + 1) { end = headerval.IndexOf(',', begin); if (-1 == end) { end = len; } float qvalue = 1; pos1 = headerval.IndexOf(';', begin); if (-1 != pos1 && pos1 < end) { // pos1 -> ";q=n" if (pos1 - begin < 2 || // room for valid langtag pos1 + 3 >= headerval.Length || headerval[pos1 + 1] != 'q' || headerval[pos1 + 2] != '=') { continue; } if (!ParseHelpers.TryParseDecimal(headerval, pos1 + 3, -1, out qvalue)) { continue; } if (qvalue < 0f || qvalue > 1.0f) { continue; } } else { pos1 = end; } // Skip over any whitespace. We expect this to make the following Trim redundant, // thus saving on an alloc. while (headerval[begin] == ' ') { ++begin; } // Extract language subtag e.g. "fr-FR". // NB: we expect this to be efficient and not allocate a new string as // a string matching the trimmed value is most likely already Intern (held by // the LanguageTag cache as a key value). Only first time here for a particular // value will a new string possibly be allocated. string langtag = headerval.Substring(begin, pos1 - begin).Trim(); // Wrap langtag. LanguageTag lt = i18n.LanguageTag.GetCachedInstance(langtag); if (lt == null || !lt.Language.IsSet()) { continue; } // Ignore the langtag if already added. //if (pal.IsValid() && pal.Equals(lt)) { // continue; } // NB: the above check disabled as it can cause the first lang in the header, // where it matches the PAL intially, to be lost if/when the PAL is later changed // to something else. // Store a new representative item. // NB: LanguageItem is a value type so no alloc done here. LanguageItems[ordinal] = new LanguageItem(lt, qvalue, ordinal); ++ordinal; } // Truncate any extra elements from end of array. if (ordinal != LanguageItems.Length) { LanguageItems = LanguageItems.Where(x => x.LanguageTag.IsValid()).ToArray(); } // If there was no PAL, and the header value was invalid then we will have no language items, so add the default if (LanguageItems.Length == 0) { LanguageItems = new LanguageItem[] { new LanguageItem(LocalizedApplication.Current.DefaultLanguageTag, PalQualitySetting, 0) } } ; // Rearrange items into order of precedence. This is facilitated by LanguageItem's // impl. of IComparable. Array.Sort(LanguageItems); // Done. return(LanguageItems); } }