Example #1
0
        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);
        }
Example #2
0
		public static bool TryGetLanguageFromIso3Code(string iso3Code, out LanguageSubtag languageSubtag)
		{
			return Iso3Languages.TryGetValue(iso3Code, out languageSubtag);
		}
Example #3
0
 /// <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>();
 }
Example #4
0
		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);
		}
Example #5
0
		/// <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;
		}
Example #6
0
		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;
		}
Example #7
0
		/// <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);
		}
Example #8
0
		/// <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;
		}
Example #9
0
		/// <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;
		}
Example #10
0
		/// <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));
		}
Example #11
0
 public static bool TryGetLanguageFromIso3Code(string iso3Code, out LanguageSubtag languageSubtag)
 {
     return(Iso3Languages.TryGetValue(iso3Code, out languageSubtag));
 }