public static void AddLanguage(string code, string name, bool isPrivateUse, string iso3Code) { var languageTag = new LanguageSubtag(code, name, isPrivateUse, iso3Code); RegisteredLanguages.Add(languageTag); Iso3Languages = RegisteredLanguages.Where(l => !string.IsNullOrEmpty(l.Iso3Code)).ToDictionary(l => l.Iso3Code, StringComparer.InvariantCultureIgnoreCase); }
public static bool TryGetLanguageFromIso3Code(string iso3Code, out LanguageSubtag languageSubtag) { return Iso3Languages.TryGetValue(iso3Code, out languageSubtag); }
/// <summary> /// Initializes a new instance of the <see cref="T:LanguageSubtag"/> class. /// </summary> /// <param name="subtag">The subtag.</param> /// <param name="name">The name.</param> public LanguageSubtag(LanguageSubtag subtag, string name) : this(subtag.Code, name, subtag.IsPrivateUse, subtag._iso3Code) { Names = new List <string>(); }
private static string GetImplicitScriptCode(LanguageSubtag languageSubtag, RegionSubtag regionSubtag) { if (languageSubtag == null || languageSubtag.IsPrivateUse) return null; string regionCode = null; if (regionSubtag != null && !regionSubtag.IsPrivateUse) regionCode = regionSubtag; return GetImplicitScriptCode(languageSubtag.Code, regionCode); }
/// <summary> /// Gets the subtags of the specified language tag. /// </summary> /// <param name="langTag">The language tag.</param> /// <param name="languageSubtag">The language subtag.</param> /// <param name="scriptSubtag">The script subtag.</param> /// <param name="regionSubtag">The region subtag.</param> /// <param name="variantSubtags">The variant and private-use subtags.</param> /// <returns></returns> public static bool TryGetSubtags(string langTag, out LanguageSubtag languageSubtag, out ScriptSubtag scriptSubtag, out RegionSubtag regionSubtag, out IEnumerable<VariantSubtag> variantSubtags) { if (langTag == null) throw new ArgumentNullException("langTag"); languageSubtag = null; scriptSubtag = null; regionSubtag = null; variantSubtags = null; Match match = LangTagPattern.Match(langTag); if (!match.Success) return false; var privateUseCodes = new List<string>(); Group privateUseGroup = match.Groups["privateuse"]; if (privateUseGroup.Success) privateUseCodes.AddRange(privateUseGroup.Value.Split(new[] {'-'}, StringSplitOptions.RemoveEmptyEntries).Skip(1)); Group languageGroup = match.Groups["language"]; if (languageGroup.Success) { string languageCode = languageGroup.Value; if (languageCode.Equals(WellKnownSubtags.UnlistedLanguage, StringComparison.OrdinalIgnoreCase)) { // In our own WS dialog, we don't allow no language, but if it isn't a standard one, a language like xkal // produces an identifier like qaa-x-kal, and we interepret the first thing after the x as a private // language code (not allowed as the first three characters according to the standard). // If it's NOT a valid language code (e.g., too many characters), probably came from some other // program. Treating it as a language code will fail if we try to create such a writing system, // since we will detect the invalid language code. So only interpret the first element // after the x as a language code if it is a valid one. Otherwise, we just let qaa be the language. if (privateUseCodes.Count > 0 && LangPattern.IsMatch(privateUseCodes[0]) && !StandardSubtags.CommonPrivateUseVariants.Contains(privateUseCodes[0])) { languageSubtag = new LanguageSubtag(privateUseCodes[0]); privateUseCodes.RemoveAt(0); } else { languageSubtag = WellKnownSubtags.UnlistedLanguage; // We do allow just plain qaa. } } else { if (!StandardSubtags.IsValidIso639LanguageCode(languageCode)) return false; languageSubtag = languageCode; } } Group scriptGroup = match.Groups["script"]; if (scriptGroup.Success) { string scriptCode = scriptGroup.Value; if (scriptCode.Equals("Qaaa", StringComparison.OrdinalIgnoreCase) && privateUseCodes.Count > 0 && ScriptPattern.IsMatch(privateUseCodes[0])) { scriptSubtag = new ScriptSubtag(privateUseCodes[0]); privateUseCodes.RemoveAt(0); } else { if (!StandardSubtags.IsValidIso15924ScriptCode(scriptCode)) return false; scriptSubtag = scriptCode; } } Group regionGroup = match.Groups["region"]; if (regionGroup.Success) { string regionCode = regionGroup.Value; if (regionCode.Equals("QM", StringComparison.OrdinalIgnoreCase) && privateUseCodes.Count > 0 && RegionPattern.IsMatch(privateUseCodes[0])) { regionSubtag = new RegionSubtag(privateUseCodes[0]); privateUseCodes.RemoveAt(0); } else { if (!StandardSubtags.IsValidIso3166RegionCode(regionCode)) return false; regionSubtag = regionCode; } } if (scriptSubtag == null) scriptSubtag = GetImplicitScriptCode(languageSubtag, regionSubtag); var variants = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); var variantSubtagsList = new List<VariantSubtag>(); Group variantGroup = match.Groups["variant"]; if (variantGroup.Success) { foreach (string variantCode in variantGroup.Value.Split(new[] {'-'}, StringSplitOptions.RemoveEmptyEntries)) { if (variants.Contains(variantCode)) return false; VariantSubtag variantSubtag; if (!StandardSubtags.RegisteredVariants.TryGet(variantCode, out variantSubtag)) return false; variantSubtagsList.Add(variantSubtag); variants.Add(variantCode); } } variants.Clear(); foreach (string privateUseCode in privateUseCodes) { if (variants.Contains(privateUseCode) || privateUseCode.Equals("x", StringComparison.InvariantCultureIgnoreCase)) return false; VariantSubtag variantSubtag; if (!StandardSubtags.CommonPrivateUseVariants.TryGet(privateUseCode, out variantSubtag)) variantSubtag = new VariantSubtag(privateUseCode); variantSubtagsList.Add(variantSubtag); variants.Add(privateUseCode); } variantSubtags = variantSubtagsList; return true; }
private static bool TryCreate(LanguageSubtag languageSubtag, ScriptSubtag scriptSubtag, RegionSubtag regionSubtag, IEnumerable<VariantSubtag> variantSubtags, out string langTag, out string message, out string paramName) { message = null; paramName = null; VariantSubtag[] variantSubtagsArray = variantSubtags.ToArray(); if (languageSubtag == null && (scriptSubtag != null || regionSubtag != null || variantSubtagsArray.Length == 0 || variantSubtagsArray.Any(v => !v.IsPrivateUse))) { message = "A language subtag is required."; paramName = "languageSubtag"; } var sb = new StringBuilder(); bool isCustomLanguage = false; if (languageSubtag != null) { // Insert non-custom language, script, region into main part of code. if (languageSubtag.IsPrivateUse && languageSubtag.Code != WellKnownSubtags.UnlistedLanguage) { if (!LangPattern.IsMatch(languageSubtag.Code)) { message = "The private use language code is invalid."; paramName = "languageSubtag"; } sb.Append("qaa"); isCustomLanguage = true; } else { sb.Append(languageSubtag.Code); } } bool isCustomScript = false; if (scriptSubtag != null && GetImplicitScriptCode(languageSubtag, regionSubtag) != scriptSubtag.Code) { if (sb.Length > 0) sb.Append("-"); // Qaaa is our flag to expect a script in private-use. If the actual value is Qaaa, we need to treat it as custom, // so we don't confuse some other private-use tag with a custom script. if (scriptSubtag.IsPrivateUse && !StandardSubtags.IsPrivateUseScriptCode(scriptSubtag.Code)) { if (message == null && !ScriptPattern.IsMatch(scriptSubtag.Code)) { message = "The private use script code is invalid."; paramName = "scriptSubtag"; } sb.Append("Qaaa"); isCustomScript = true; } else { sb.Append(scriptSubtag.Code); } } bool isCustomRegion = false; if (regionSubtag != null) { if (sb.Length > 0) sb.Append("-"); // QM is our flag to expect a region in private-use. If the actual value is QM, we need to treat it as custom, // so we don't confuse some other private-use tag with a custom region. if (regionSubtag.IsPrivateUse && !StandardSubtags.IsPrivateUseRegionCode(regionSubtag.Code)) { if (message == null && !RegionPattern.IsMatch(regionSubtag.Code)) { message = "The private use region code is invalid."; paramName = "regionSubtag"; } sb.Append("QM"); isCustomRegion = true; } else { sb.Append(regionSubtag.Code); } } var variants = new HashSet<string>(StringComparer.InvariantCultureIgnoreCase); foreach (VariantSubtag variantSubtag in variantSubtagsArray.Where(vs => !vs.IsPrivateUse)) { if (message == null && variants.Contains(variantSubtag.Code)) { message = "Duplicate variants are not allowed."; paramName = "variantSubtags"; } if (sb.Length > 0) sb.Append("-"); sb.Append(variantSubtag.Code); variants.Add(variantSubtag.Code); } // Insert custom language, script, or variant into private=use. bool inPrivateUse = false; if (isCustomLanguage) { inPrivateUse = true; if (sb.Length > 0) sb.Append("-"); sb.Append("x-"); sb.Append(languageSubtag.Code); } if (isCustomScript) { if (sb.Length > 0) sb.Append("-"); if (!inPrivateUse) { inPrivateUse = true; sb.Append("x-"); } sb.Append(scriptSubtag.Code); } if (isCustomRegion) { if (sb.Length > 0) sb.Append("-"); if (!inPrivateUse) { inPrivateUse = true; sb.Append("x-"); } sb.Append(regionSubtag.Code); } variants.Clear(); foreach (VariantSubtag variantSubtag in variantSubtagsArray.Where(vs => vs.IsPrivateUse)) { if (message == null && !PrivateUsePattern.IsMatch(variantSubtag.Code)) { message = "The private use subtags contains an invalid subtag."; paramName = "variantSubtags"; } if (message == null && variants.Contains(variantSubtag.Code)) { message = "Duplicate private use subtags are not allowed."; paramName = "variantSubtags"; } if (sb.Length > 0) sb.Append("-"); if (!inPrivateUse) { inPrivateUse = true; sb.Append("x-"); } sb.Append(variantSubtag.Code); variants.Add(variantSubtag.Code); } langTag = sb.ToString(); return message == null; }
/// <summary> /// Validates the creation of an IETF language tag. /// </summary> public static bool Validate(LanguageSubtag languageSubtag, ScriptSubtag scriptSubtag, RegionSubtag regionSubtag, IEnumerable<VariantSubtag> variantSubtags, out string message) { string langTag, paramName; return TryCreate(languageSubtag, scriptSubtag, regionSubtag, variantSubtags, out langTag, out message, out paramName); }
/// <summary> /// Creates a language tag from the specified subtags. /// </summary> public static string Create(LanguageSubtag languageSubtag, ScriptSubtag scriptSubtag, RegionSubtag regionSubtag, IEnumerable<VariantSubtag> variantSubtags, bool validate = true) { string langTag, message, paramName; if (!TryCreate(languageSubtag, scriptSubtag, regionSubtag, variantSubtags, out langTag, out message, out paramName)) { if (validate) throw new ArgumentException(message, paramName); } return langTag; }
/// <summary> /// Tries to create a language tag from the specified subtags. /// </summary> public static bool TryCreate(LanguageSubtag languageSubtag, ScriptSubtag scriptSubtag, RegionSubtag regionSubtag, IEnumerable<VariantSubtag> variantSubtags, out string langTag) { string message, paramName; if (!TryCreate(languageSubtag, scriptSubtag, regionSubtag, variantSubtags, out langTag, out message, out paramName)) { langTag = null; return false; } return true; }
/// <summary> /// Converts the specified ICU locale to a language tag. If the ICU locale is already a valid /// language tag, it will return it. /// </summary> /// <param name="icuLocale">The ICU locale.</param> /// <returns></returns> public static string ToLanguageTag(string icuLocale) { if (string.IsNullOrEmpty(icuLocale)) throw new ArgumentNullException("icuLocale"); if (icuLocale.Contains("-")) { Match match = IcuTagPattern.Match(icuLocale); if (match.Success) { // We need to check for mixed case in the language code portion. This has been // observed in user data, and causes crashes later on. See LT-11288. var rgs = icuLocale.Split('-'); if (rgs[0].ToLowerInvariant() == rgs[0]) return icuLocale; var bldr = new StringBuilder(); bldr.Append(rgs[0].ToLowerInvariant()); for (var i = 1; i < rgs.Length; ++i) { bldr.Append("-"); bldr.Append(rgs[i].ToLowerInvariant()); } icuLocale = bldr.ToString(); } } var locale = new Locale(icuLocale); string icuLanguageCode = locale.Language; string languageCode; if (icuLanguageCode.Length == 4 && icuLanguageCode.StartsWith("x")) languageCode = icuLanguageCode.Substring(1); else languageCode = icuLanguageCode; // Some very old projects may have codes with over-long identifiers. In desperation we truncate these. // 4-letter codes starting with 'e' are a special case. if (languageCode.Length > 3 && !(languageCode.Length == 4 && languageCode.StartsWith("e"))) languageCode = languageCode.Substring(0, 3); // The ICU locale strings in FW 6.0 allowed numbers in the language tag. The // standard doesn't allow this. Map numbers to letters deterministically, even // though the resulting code may have no relation to reality. (It may be a valid // ISO 639-3 language code that is assigned to a totally unrelated language.) if (languageCode.Contains('0')) languageCode = languageCode.Replace('0', 'a'); if (languageCode.Contains('1')) languageCode = languageCode.Replace('1', 'b'); if (languageCode.Contains('2')) languageCode = languageCode.Replace('2', 'c'); if (languageCode.Contains('3')) languageCode = languageCode.Replace('3', 'd'); if (languageCode.Contains('4')) languageCode = languageCode.Replace('4', 'e'); if (languageCode.Contains('5')) languageCode = languageCode.Replace('5', 'f'); if (languageCode.Contains('6')) languageCode = languageCode.Replace('6', 'g'); if (languageCode.Contains('7')) languageCode = languageCode.Replace('7', 'h'); if (languageCode.Contains('8')) languageCode = languageCode.Replace('8', 'i'); if (languageCode.Contains('9')) languageCode = languageCode.Replace('9', 'j'); LanguageSubtag languageSubtag; if (languageCode == icuLanguageCode) { languageSubtag = (languageCode.Length == 4 && languageCode.StartsWith("e")) ? languageCode.Substring(1) : languageCode; } else { languageSubtag = new LanguageSubtag(languageCode); } if (icuLanguageCode == icuLocale) return Create(languageSubtag, null, null, Enumerable.Empty<VariantSubtag>()); return Create(languageSubtag, locale.Script, locale.Country, TranslateVariantCode(locale.Variant)); }
public static bool TryGetLanguageFromIso3Code(string iso3Code, out LanguageSubtag languageSubtag) { return(Iso3Languages.TryGetValue(iso3Code, out languageSubtag)); }