/// <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));
 }
Example #6
0
        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);
        }
Example #10
0
        /// <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);
        }
Example #13
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));
 }
Example #14
0
 // [IEquatable<LanguageTag>]
 /// <summary>
 /// Equals
 /// </summary>
 /// <param name="other"></param>
 /// <returns></returns>
 public bool Equals(LanguageTag other)
 {
     return(0 == string.CompareOrdinal(LowerCaseLanguageTag, other.LowerCaseLanguageTag));
 }
Example #15
0
 // [IComparable<LanguageTag>]
 /// <summary>
 /// CompareTo
 /// </summary>
 /// <param name="other"></param>
 /// <returns></returns>
 public int CompareTo(LanguageTag other)
 {
     return(string.CompareOrdinal(LowerCaseLanguageTag, other.LowerCaseLanguageTag));
 }
Example #16
0
 /// <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);
 }