private void UpdateParent() { //load any parent: //l-s-r-p -> l-s-r //l-s-r -> l-s //l-r -> l //l-s -> l //l -> no parent if (Region.IsSet() && Script.IsSet() && PrivateUse.IsSet()) { Parent = GetParentLanguageTag($"{Language}-{Script}-{Region}"); } else if (Region.IsSet() && Script.IsSet()) { Parent = GetParentLanguageTag($"{Language}-{Script}"); } else if (Script.IsSet() || Region.IsSet()) { Parent = GetParentLanguageTag(Language); } else { Parent = default; } }
private void UpdateCultureInfo() { try { if (PrivateUse.IsSet()) { CultureInfo = new CultureInfo(OriginalLanguageTag.Replace($"-x-{PrivateUse}", string.Empty)); } else { CultureInfo = new CultureInfo(OriginalLanguageTag); } } catch (ArgumentException) { } }
// Operations /// <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="i_rhs"></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 int Match(LanguageTag i_rhs, MatchGrade matchGrade = MatchGrade.LanguageMatch) { if (i_rhs == null) { throw new ArgumentNullException("i_rhs"); } // Either langtag being null fails the match. if (!Language.IsSet() || !i_rhs.Language.IsSet()) { return(0); } // Init. bool[] L = { 0 == string.Compare(Language, i_rhs.Language, true), Language.IsSet(), i_rhs.Language.IsSet() }; bool[] S = { 0 == string.Compare(Script, i_rhs.Script, true), Script.IsSet(), i_rhs.Script.IsSet() }; bool[] R = { 0 == string.Compare(Region, i_rhs.Region, true), Region.IsSet(), i_rhs.Region.IsSet() }; bool[] P = { 0 == string.Compare(PrivateUse, i_rhs.PrivateUse, true), PrivateUse.IsSet(), i_rhs.PrivateUse.IsSet() }; // 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 (matchGrade != MatchGrade.ExactMatch) { // B. if (S[0] && !R[0] && R[1] != R[2]) { return(score); } --score; if (matchGrade != MatchGrade.DefaultRegion) { // C. if (S[0] && !R[0] && R[1] == R[2]) { return(score); } --score; if (matchGrade != MatchGrade.ScriptMatch) { // D. if (!S[0] && S[1] != S[2]) { return(score); } --score; // E. if (!S[0] && S[1] == S[2]) { return(score); } } //--score; //DebugHelpers.WriteLine("LanguageTag.Match -- fallen through: {0}, {1}", ToString(), i_rhs.ToString()); //Debug.Assert(false); } } // F. return(0); }
/// <summary> /// Constructs a new instance based on a language tag string. /// If successful, then the Language property is set to a valid language subtag. /// </summary> /// <param name="langtag"> /// Supports a subset of BCP 47 language tag spec corresponding to the Windows /// support for language names, namely the following subtags: /// language (mandatory, 2 alphachars) /// script (optional, 4 alphachars) /// region (optional, 2 alphachars | 3 decdigits) /// privateuse (optional, 4+ alphanumericchars) /// Example tags supported: /// "en" [language] /// "en-US" [language + region] /// "zh" [language] /// "zh-HK" [language + region] /// "zh-123" [language + region] /// "zh-Hant" [language + script] /// "zh-Hant-HK" [language + script + region] /// "en-GB-x-ACMECorp" [language + region + privateuse] /// </param> /// <seealso href="http://www.microsoft.com/resources/msdn/goglobal/default.mspx"/> public LanguageTag(string langtag) { m_langtag = langtag.Trim(); // Normalize certain langtags: // «LX113» http://www.w3.org/International/articles/language-tags/#script for (int i = 0; i < NormalizedLangTags.GetLength(0); ++i) { if (0 == string.Compare(m_langtag, NormalizedLangTags[i, 0], true)) { m_langtag = NormalizedLangTags[i, 1]; break; } } m_langtagLC = m_langtag.ToLowerInvariant(); // Parse the langtag. Match match = s_regex_parseLangtag.Match(m_langtag); if (match.Success && match.Groups.Count == 5) { Language = match.Groups[1].Value; Script = match.Groups[2].Value; Region = match.Groups[3].Value; PrivateUse = match.Groups[4].Value; } // Load any parent: // l-s-r-p -> l-s-r // l-s-r -> l-s // l-r -> l // l-s -> l // l -> no parent if (Region.IsSet() && Script.IsSet() && PrivateUse.IsSet()) { m_parent = GetCachedInstance(string.Format("{0}-{1}-{2}", Language, Script, Region)); } else if (Region.IsSet() && Script.IsSet()) { m_parent = GetCachedInstance(string.Format("{0}-{1}", Language, Script)); } else if (Script.IsSet() || Region.IsSet()) { m_parent = GetCachedInstance(Language); } else { m_parent = null; } // GlobalKey = string.Format("po:{0}", m_langtag).ToLowerInvariant(); // try { if (PrivateUse.IsSet()) { // Strip out the private use subtag to allow CultureInfo to be set, based on the rest of the language tag CultureInfo = new CultureInfo(langtag.Replace("-x-" + PrivateUse, string.Empty)); } else { CultureInfo = new CultureInfo(langtag); } } catch (System.ArgumentException) {} // NativeNameTitleCase = CultureInfo != null?CultureInfo.TextInfo.ToTitleCase(CultureInfo.NativeName) : "m_langtag"; //Debug.Assert(ToString() == langtag); }