/// <summary> /// Register language tag /// </summary> /// <param name="languageTag"></param> /// <exception cref="ArgumentNullException"></exception> public void RegisterTag(LanguageTag languageTag) { if (languageTag == null) { throw new ArgumentNullException(nameof(languageTag)); } var hashcode = languageTag.GetHashCode(); // ReSharper disable once InconsistentlySynchronizedField if (_items.Any(x => x.HashCodeCached == hashcode)) { return; } lock (_cacheLockObj) { if (_items.Any(x => x.HashCodeCached == hashcode)) { return; } //this will update all item's priority _items.Add(new FallbackItem(languageTag)); } }
/// <summary> /// 获得 LanguageTag 的完整度级别,该级别用于 Add 方法对同语言标记做优先级自动优化排列时的参考 /// 级别从 0 到 4,数字越小,优先级越高 /// LanguageTag 完整性越高,优先级越高——优先命中高优先级的,最低的 Language only 作为兜底候选项 /// l 0 /// l-r 1 /// l-s 2 /// l-s-r 3 /// l-s-r-p 4 /// </summary> /// <param name="languageTag"></param> /// <returns></returns> private static int GetFullLevel(LanguageTag languageTag) { if (languageTag == null) { return(-1); } if (!languageTag.Language.IsSet()) { return(-1); } if (languageTag.Language.IsSet() && languageTag.Script.IsSet() && languageTag.Region.IsSet() && languageTag.PrivateUse.IsSet()) { return(4); } if (languageTag.Language.IsSet() && languageTag.Script.IsSet() && languageTag.Region.IsSet()) { return(3); } if (languageTag.Language.IsSet() && languageTag.Script.IsSet()) { return(2); } if (languageTag.Language.IsSet() && languageTag.Region.IsSet()) { return(1); } return(0); }
/// <summary> /// Get suitable tags /// </summary> /// <param name="languageTag"></param> /// <returns></returns> public IEnumerable <LanguageTag> GetSuitableTags(LanguageTag languageTag) { if (_cachedResults.TryGetValue(languageTag.GetHashCode(), out var results)) { return(results.Select(s => s.Value)); } if (!_items.Any()) { return(Enumerable.Empty <LanguageTag>()); } results = new List <FallbackItem>(); var orderedItems = _items.OrderByDescending(o => o.Priority).ToList(); var skipHashCodes = new List <int>(); //step.1 filter all results by this order: ExactMatch, DefaultRegion, ScriptMatch and LanguageMatch foreach (var level in _matchLevels) { results.AddRange(Filter(orderedItems, languageTag, level, skipHashCodes)); } //step.2 add all results which priority less than the min of the result that got from step.1 results.AddRange(Filter(orderedItems, results.Min(m => m.Priority), skipHashCodes)); //step.3 add default language tag (in this case, the most less priority) if need. AddMostLessPriorityIfNeed(results, skipHashCodes); //step.4 clear skipped hashcode cache skipHashCodes.Clear(); return(Remember(languageTag, results)); }
/// <summary> /// Has cached result /// </summary> /// <param name="languageTag"></param> /// <returns></returns> public bool HashCachedResult(LanguageTag languageTag) { if (languageTag == null) { return(false); } return(_cachedResults.ContainsKey(languageTag.GetHashCode())); }
/// <summary> /// Contains /// </summary> /// <param name="languageTag"></param> /// <returns></returns> public bool Contains(LanguageTag languageTag) { if (languageTag == null) { return(false); } return(_items.ContainsGlobalKey(languageTag.GlobalKey)); }
internal LanguageTag(string originalLanguageTag, string language, string script, string region, string privateUse, LanguageTag parent, CultureInfo cultureInfo) { OriginalLanguageTag = originalLanguageTag; LowerCaseLanguageTag = originalLanguageTag.ToLowerInvariant(); Language = language; Script = script; Region = region; PrivateUse = privateUse; Parent = parent; CultureInfo = cultureInfo; NativeNameTitleCase = CultureInfo != null?CultureInfo.TextInfo.ToTitleCase(CultureInfo.NativeName) : "OriginalLangTag"; GlobalKey = $"po:{OriginalLanguageTag}".ToLowerInvariant(); Alias = OriginalLanguageTag; }
private IEnumerable <LanguageTag> Remember(LanguageTag left, List <FallbackItem> filteredItems) { lock (_cacheLockObj) { var hashcode = left.GetHashCode(); if (_cachedResults.ContainsKey(hashcode)) { _cachedResults[hashcode] = filteredItems; } else { _cachedResults.Add(hashcode, filteredItems); } } return(filteredItems.Select(s => s.Value)); }
private IEnumerable <FallbackItem> Filter(List <FallbackItem> orderedItems, LanguageTag languageTag, MatchLevel matchLevel, List <int> skipHashCodes) { foreach (var item in orderedItems) { if (skipHashCodes.Contains(item.HashCodeCached)) { continue; } var score = LanguageTagMatcher.Match(languageTag, item.Value, matchLevel); if (score > 0) { skipHashCodes.Add(item.HashCodeCached); yield return(item); } } }
public LanguageTag Build() { //Load any parent UpdateParent(); //Update culture info UpdateCultureInfo(); var tag = new LanguageTag(OriginalLanguageTag, Language, Script, Region, PrivateUse, Parent, CultureInfo); //Update alias if (!string.IsNullOrWhiteSpace(Alias)) { tag.UpdateAlias(Alias); } return(tag); }
/// <summary> /// Convert <see cref="LanguageTag"/> to <see cref="Locale"/>. /// </summary> /// <param name="languageTag"></param> /// <returns></returns> public static Locale AsLocale(this LanguageTag languageTag) { if (languageTag == null) { languageTag = LanguageTag.Current; } if (languageTag.Region.IsSet()) { if (EnumsNET.Enums.TryParse( $"{languageTag.Language}_{languageTag.Region.ToUpperInvariant()}", true, out Locale locale1)) { return(locale1); } } return(EnumsNET.Enums.TryParse(languageTag.Language, true, out Locale locale2) ? locale2 : LanguageTag.DefaultLocale); }
/// <summary> /// Looks up in the passed collection of supported AppLanguages the language that is best matched /// to this langtag. I.e. the written AppLanguage that a user understanding this langtag /// will most-likely understand. /// </summary> /// <param name="leftLanguageTag"></param> /// <param name="rightLanguageTags"></param> /// <param name="matchedTag"></param> /// <param name="matchLevel"></param> /// <returns>Selected CultureInfoEx instance from the AppLanguages collection or null if there was no match.</returns> public static int Match(LanguageTag leftLanguageTag, LanguageTag[] rightLanguageTags, out LanguageTag matchedTag, MatchLevel matchLevel = MatchLevel.LanguageMatch) { int matchedScore = 0; matchedTag = null; foreach (LanguageTag rightLanguageTag in rightLanguageTags) { int score = Match(leftLanguageTag, rightLanguageTag, matchLevel); if (score > matchedScore) { matchedScore = score; matchedTag = rightLanguageTag; if (matchedScore == 100) { // Can't beat an exact match. break; } } } return(matchedScore); }
/// <summary> /// Performs 'language matching' between lang described by this (A) /// and language decibed by i_rhs (B). Essentially, returns an assessment of /// how well a speaker of A will understand B. /// The key points are as follows: /// · The Script is almost as relevant as the language itself; that is, if /// you speak a language but do not understand the script, you cannot /// read that language. Thus a mismatch in Script should score low. /// · The Region is less relevant than Script to understanding of language. /// The one exception to this is where the Region has traditionally been /// used to also indicate the Script. E.g. /// zh-CH -> Chinese (Simplified) i.e. zh-Hans /// zh-TW -> Chinese (Traditional) i.e. zh-Hant /// In these cases we normalize all legacy langtags to their new values /// before matching. E.g. zh-CH is normalized to zh-Hans. /// «LX113» /// </summary> /// <param name="leftLanguageTag"></param> /// <param name="rightLanguageTag"></param> /// <param name="matchLevel"></param> /// <returns> /// Returns a score on to what extent the two languages match. The value ranges from /// 100 (exact match) down to 0 (fundamental language tag mismatch), with values /// in between which may be used to compare quality of a match, larger the value /// meaning better quality. /// </returns> /// <remarks> /// Matching values: /// RHS /// this lang lang+script lang+region lang+script+region /// ---------------------------------------------------------------------------------- /// lang | A D C D /// lang+script | D A D B /// lang+region | C D A D /// lang+script+region | D B D A /// /// NB: For the purposes of the logic above, lang incorporates Language + PrivateUse subtags. /// /// A. Exact match (100) /// All three subtags match. /// B. Unbalanced Region Mismatch (99) [zh, zh-HK] [zh-Hans, zh-Hans-HK] /// Language and Script match; /// one side has Region set while the other doesn't. /// Here there is the possibility that due to defaults Region matches. /// C. Balanced Region Mismatch (98) [zh-IK, zh-HK] [zh-Hans-IK, zh-Hans-HK] /// Language and Script match; /// both sides have Region set but to different values. /// Here there is NO possibility that Region matches. /// D. Unbalanced Script Mismatch (97) [zh-HK, zh-Hant-HK] /// Language matches, Region may match; /// one side has Script set while the other doesn't. /// Here there is the possibility that due to defaults Script matches. /// E. Balanced Script Mismatch (96) /// Language matches, Region may match; /// both sides have Script set but to different values. /// Here there is NO possibility that Script matches. /// F. Language Mismatch (0) /// Language doesn't match. /// </remarks> /// <seealso href="http://msdn.microsoft.com/en-us/library/windows/apps/jj673578.aspx"/> public static int Match(LanguageTag leftLanguageTag, LanguageTag rightLanguageTag, MatchLevel matchLevel = MatchLevel.LanguageMatch) { if (leftLanguageTag == null) { throw new ArgumentNullException(nameof(leftLanguageTag)); } if (rightLanguageTag == null) { throw new ArgumentNullException(nameof(rightLanguageTag)); } if (leftLanguageTag.Language.IsNullOrEmpty() || rightLanguageTag.Language.IsNullOrEmpty()) { return(0); } //Init bool[] init(string l, string r) => new[] { 0 == string.Compare(l, r, StringComparison.OrdinalIgnoreCase), l.IsNotNullNorEmpty(), r.IsNotNullNorEmpty() }; var L = init(leftLanguageTag.Language, rightLanguageTag.Language); var S = init(leftLanguageTag.Script, rightLanguageTag.Script); var R = init(leftLanguageTag.Region, rightLanguageTag.Region); var P = init(leftLanguageTag.PrivateUse, rightLanguageTag.PrivateUse); // Language incorporates Language + PrivateUse subtags for our logic here. L[0] = L[0] && P[0]; L[1] = L[1] || P[1]; L[2] = L[2] || P[2]; // Logic. int score = 100; // F. if (!L[0]) { return(0); } // A. if (S[0] && R[0] && P[0]) { return(score); } --score; if (matchLevel != MatchLevel.ExactMatch) { // B. if (S[0] && !R[0] && R[1] != R[2]) { return(score); } --score; if (matchLevel != MatchLevel.DefaultRegion) { // C. if (S[0] && !R[0] && R[1] == R[2]) { return(score); } --score; if (matchLevel != MatchLevel.ScriptMatch) { // D. if (!S[0] && S[1] != S[2]) { return(score); } --score; // E. if (!S[0] && S[1] == S[2]) { return(score); } } //--score; } } // F. return(0); }
/// <summary> /// Match /// </summary> /// <param name="appLanguages"></param> /// <param name="matchedTag"></param> /// <param name="matchLevel"></param> /// <returns></returns> public int Match(LanguageTag[] appLanguages, out LanguageTag matchedTag, MatchLevel matchLevel = MatchLevel.LanguageMatch) { return(LanguageTagMatcher.Match(this, appLanguages, out matchedTag, matchLevel)); }
// [IEquatable<LanguageTag>] /// <summary> /// Equals /// </summary> /// <param name="other"></param> /// <returns></returns> public bool Equals(LanguageTag other) { return(0 == string.CompareOrdinal(LowerCaseLanguageTag, other.LowerCaseLanguageTag)); }
// [IComparable<LanguageTag>] /// <summary> /// CompareTo /// </summary> /// <param name="other"></param> /// <returns></returns> public int CompareTo(LanguageTag other) { return(string.CompareOrdinal(LowerCaseLanguageTag, other.LowerCaseLanguageTag)); }
/// <summary> /// Match /// </summary> /// <param name="appLanguage"></param> /// <param name="matchLevel"></param> /// <returns></returns> public int Match(LanguageTag appLanguage, MatchLevel matchLevel = MatchLevel.LanguageMatch) { return(LanguageTagMatcher.Match(this, appLanguage, matchLevel)); }
/// <summary> /// Create a new instance for <see cref="FallbackItem"/> /// </summary> /// <param name="languageTag"></param> public FallbackItem(LanguageTag languageTag) { Value = languageTag ?? throw new ArgumentNullException(nameof(languageTag)); HashCodeCached = Value.GetHashCode(); FullLevel = GetFullLevel(Value); }