/// <summary> /// Helper for converting the C printf-style %0, %1 ... style identifiers in a formatted nugget msgid string /// to the .NET-style format items: {0}, {1} ... /// </summary> /// <remarks> /// A formatted msgid may be in the form: /// <para> /// Enter between %1 and %0 characters /// </para> /// <para> /// For which we return: /// </para> /// <para> /// Enter between {1} and {0} characters /// </para> /// </remarks> public static string ConvertIdentifiersInMsgId(string msgid) { // Convert %n style identifiers to {n} style. return(m_regexPrintfIdentifiers.Replace(msgid, delegate(Match match) { string s = match.Groups[1].Value; double id; if (ParseHelpers.TryParseDecimal(s, 1, s.Length - 1 + 1, out id)) { s = string.Format("{{{0}}}", id); } return s; })); }
// Static helpers /// <summary> /// Parses an HTTP Accept-Language or Content-Language header value, returning /// a representative ordered array of LanguageItem instances, sorted in order of /// language preference. /// E.g. "de;q=0.5, en;q=1, fr-FR;q=0,ga;q=0.5". /// Notably, is able to re-order elements based on quality. /// </summary> /// <remarks> /// The first element position in the returned array is reserved for an item that /// describes the Principal Application Language (PAL) for the request. If/when the PAL /// is not set, that element will be a null item (LanguageItem.LanguageTag == null). /// /// This method is designed to be as efficient as possible, typically requiring /// only a single heap alloc, for the returned array object itself. /// </remarks> /// <param name="headerval"> /// HTTP Accept-Language header value. /// http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html. /// May be null or empty string for zero languages. /// </param> /// <param name="pal"> /// Optional language to store at the first element position in the array, which is reserved /// for the Principal Application Language (PAL). Any such LanguageItem stored that has a quality /// value of 2 (LanguageItem.PalQualitySetting). Null if no such language to be stored there and the /// item to be set as null (LanguageItem.LanguageTag == null). /// </param> /// <returns> /// Array of languages items (with possibly null LanguageTag members) sorted in order or language preference. /// </returns> public static LanguageItem[] ParseHttpLanguageHeader(string headerval, ILanguageTag pal = null) { // This method is designed to be as efficient as possible (avoiding string allocations where possible). // int begin, end, pos1; int len = headerval != null ? headerval.Length : 0; int ordinal = 0; // Init array with enough elements for each language entry in the header. var LanguageItems = new LanguageItem[(len > 0 ? headerval.CountOfChar(',') + 1 : 0) + 1]; // First element position is reserved for any PAL. LanguageItems[ordinal] = new LanguageItem(pal, PalQualitySetting, ordinal); ++ordinal; // For each language component of the header (delimited by comma) for (begin = 0; begin < len; begin = end + 1) { end = headerval.IndexOf(',', begin); if (-1 == end) { end = len; } float qvalue = 1; pos1 = headerval.IndexOf(';', begin); if (-1 != pos1 && pos1 < end) { // pos1 -> ";q=n" if (pos1 - begin < 2 || // room for valid langtag pos1 + 3 >= headerval.Length || headerval[pos1 + 1] != 'q' || headerval[pos1 + 2] != '=') { continue; } if (!ParseHelpers.TryParseDecimal(headerval, pos1 + 3, -1, out qvalue)) { continue; } if (qvalue < 0f || qvalue > 1.0f) { continue; } } else { pos1 = end; } // Skip over any whitespace. We expect this to make the following Trim redundant, // thus saving on an alloc. while (headerval[begin] == ' ') { ++begin; } // Extract language subtag e.g. "fr-FR". // NB: we expect this to be efficient and not allocate a new string as // a string matching the trimmed value is most likely already Intern (held by // the LanguageTag cache as a key value). Only first time here for a particular // value will a new string possibly be allocated. string langtag = headerval.Substring(begin, pos1 - begin).Trim(); // Wrap langtag. LanguageTag lt = i18n.LanguageTag.GetCachedInstance(langtag); if (lt == null || !lt.Language.IsSet()) { continue; } // Ignore the langtag if already added. //if (pal.IsValid() && pal.Equals(lt)) { // continue; } // NB: the above check disabled as it can cause the first lang in the header, // where it matches the PAL intially, to be lost if/when the PAL is later changed // to something else. // Store a new representative item. // NB: LanguageItem is a value type so no alloc done here. LanguageItems[ordinal] = new LanguageItem(lt, qvalue, ordinal); ++ordinal; } // Truncate any extra elements from end of array. if (ordinal != LanguageItems.Length) { LanguageItems = LanguageItems.Where(x => x.LanguageTag.IsValid()).ToArray(); } // If there was no PAL, and the header value was invalid then we will have no language items, so add the default if (LanguageItems.Length == 0) { LanguageItems = new LanguageItem[] { new LanguageItem(LocalizedApplication.Current.DefaultLanguageTag, PalQualitySetting, 0) } } ; // Rearrange items into order of precedence. This is facilitated by LanguageItem's // impl. of IComparable. Array.Sort(LanguageItems); // Done. return(LanguageItems); } }