2 using System.Collections.Generic;
6 using System.Globalization;
7 using System.Web.Script.Serialization;
8 using System.Collections.Specialized;
9 using System.Collections;
10 using System.Text.RegularExpressions;
11 using System.Reflection;
12 using System.Diagnostics;
15 namespace Globalization {
16 public class GlobalizationInfo {
17 public string name = "";
18 public string englishName;
19 public string nativeName;
20 public string language;
22 public NumberFormatInfo numberFormat;
23 public Dictionary<String, DateFormatInfo> calendars;
24 private CultureInfo culture;
25 public static Dictionary<String, Object> BasisGlobInfo;
28 private static string[] _numberNegativePatterns = "(n)|-n|- n|n-|n -".Split('|');
29 private static string[] _currencyNegativePatterns = "($n)|-$n|$-n|$n-|(n$)|-n$|n-$|n$-|-n $|-$ n|n $-|$ n-|$ -n|n- $|($ n)|(n $)".Split('|');
30 private static string[] _percentNegativePatterns = "-n %|-n%|-%n|%-n|%n-|n-%|n%-|-% n|n %-|% n-|% -n|n- %".Split('|');
31 private static string[] _currencyPositivePatterns = "$n|n$|$ n|n $".Split('|');
32 private static string[] _percentPositivePatterns = "n %|n%|%n|% n".Split('|');
34 public static GlobalizationInfo GetGlobInfo(CultureInfo culture) {
35 var info = new GlobalizationInfo {
37 language = (culture == CultureInfo.InvariantCulture || culture.IsNeutralCulture) ? culture.Name : culture.Parent.Name,
38 name = String.IsNullOrEmpty(culture.Name) ? "invariant" : culture.Name,
39 englishName = String.IsNullOrEmpty(culture.Name) ? "invariant" : culture.EnglishName,
40 nativeName = String.IsNullOrEmpty(culture.Name) ? "invariant" : culture.NativeName,
41 isRTL = culture.TextInfo.IsRightToLeft,
42 numberFormat = GetNumberFormatInfo(culture),
43 calendars = GetCalendars(culture)
48 public static Dictionary<String, DateFormatInfo> GetCalendars(CultureInfo culture) {
49 var calendars = new Dictionary<String, DateFormatInfo>();
50 bool foundStandard = false;
51 var gregorianType = typeof(GregorianCalendar);
52 var defaultCalendar = culture.DateTimeFormat.Calendar;
53 var defaultCalendarType = defaultCalendar.GetType();
54 GregorianCalendarTypes? gregorianCalendarType = null;
55 if (defaultCalendarType == gregorianType) {
56 gregorianCalendarType = ((GregorianCalendar)defaultCalendar).CalendarType;
58 var optionalCalendars = culture.OptionalCalendars;
59 foreach (var calendar in optionalCalendars) {
60 var type = calendar.GetType();
62 bool isStandard = false;
63 if (type == gregorianType) {
64 var calendarType = ((GregorianCalendar)calendar).CalendarType;
65 if (calendarType == GregorianCalendarTypes.USEnglish) {
66 // we include the Gregorian_USEnglish culture as part of the built-in 'en' culture
67 // because it is so common -- it is an optional calendar of every single english
68 // speaking culture. So, skip it when found for any other culture.
71 else if (culture == CultureInfo.InvariantCulture) {
72 // invariant has one calendar, Gregorian_Localized, which is identical
73 // to Gregorian_USEnglish.
74 name = " Gregorian_USEnglish";
77 name = "Gregorian_" + calendarType.ToString();
79 if (!foundStandard && gregorianCalendarType.HasValue && gregorianCalendarType == gregorianCalendarType.Value) {
85 name = type.Name.Replace("Calendar", "");
95 if (culture != CultureInfo.InvariantCulture) {
96 culture.DateTimeFormat.Calendar = calendar;
98 var calendarInfo = GetDateTimeFormatInfo(culture, name);
99 calendars.Add(key, calendarInfo);
101 if (!foundStandard) {
102 throw new ApplicationException("Could not locate the standard calendar type for culture '" + culture.Name + "'.");
107 public static GlobalizationInfo.NumberFormatInfo GetNumberFormatInfo(CultureInfo culture) {
108 var nf = culture.NumberFormat;
109 return new GlobalizationInfo.NumberFormatInfo {
110 decimals = nf.NumberDecimalDigits,
111 decimalSeparator = nf.NumberDecimalSeparator,
112 groupSeparator = nf.NumberGroupSeparator,
113 groupSizes = nf.NumberGroupSizes,
115 negative = nf.NegativeSign,
116 negativeInfinity = nf.NegativeInfinitySymbol,
117 positive = nf.PositiveSign,
118 positiveInfinity = nf.PositiveInfinitySymbol,
119 pattern = new string[] { GetFromStringList(_numberNegativePatterns, nf.NumberNegativePattern) },
120 currency = new GlobalizationInfo.NumberFormatInfo.NumberClassFormatInfo {
121 decimals = nf.CurrencyDecimalDigits,
122 decimalSeparator = nf.CurrencyDecimalSeparator,
123 groupSeparator = nf.CurrencyGroupSeparator,
124 groupSizes = nf.CurrencyGroupSizes,
125 pattern = new string[] {
126 GetFromStringList(_currencyNegativePatterns, nf.CurrencyNegativePattern),
127 GetFromStringList(_currencyPositivePatterns, nf.CurrencyPositivePattern)
129 symbol = nf.CurrencySymbol
131 percent = new GlobalizationInfo.NumberFormatInfo.NumberClassFormatInfo {
132 decimals = nf.PercentDecimalDigits,
133 decimalSeparator = nf.PercentDecimalSeparator,
134 groupSeparator = nf.PercentGroupSeparator,
135 groupSizes = nf.PercentGroupSizes,
136 pattern = new string[] {
137 GetFromStringList(_percentNegativePatterns, nf.PercentNegativePattern),
138 GetFromStringList(_percentPositivePatterns, nf.PercentPositivePattern)
140 symbol = nf.PercentSymbol
145 public static string[] GetAMPMDesignators(CultureInfo culture, string ampm) {
146 return String.IsNullOrEmpty(ampm) ? null : new string[] { ampm, culture.TextInfo.ToLower(ampm), culture.TextInfo.ToUpper(ampm) };
149 public static GlobalizationInfo.DateFormatInfo GetDateTimeFormatInfo(CultureInfo culture, string calendarName) {
150 var df = culture.DateTimeFormat;
151 var info = new GlobalizationInfo.DateFormatInfo {
153 months = new DateFormatInfo.MonthInfo { names = df.MonthNames, namesAbbr = df.AbbreviatedMonthNames },
154 monthsGenitive = new DateFormatInfo.MonthInfo { names = df.MonthGenitiveNames, namesAbbr = df.AbbreviatedMonthGenitiveNames },
155 firstDay = (int) df.FirstDayOfWeek,
156 dateSeparator = df.DateSeparator,
157 timeSeparator = df.TimeSeparator,
158 days = new DateFormatInfo.DayInfo { names = df.DayNames, namesAbbr = df.AbbreviatedDayNames, namesShort = df.ShortestDayNames },
159 eras = GetEraInfo(culture),
160 twoDigitYearMax = df.Calendar.TwoDigitYearMax,
161 patterns = GetPatterns(df)
163 info.AM = GetAMPMDesignators(culture, df.AMDesignator);
164 info.PM = GetAMPMDesignators(culture, df.PMDesignator);
165 if (df.Calendar != null) {
166 var type = df.Calendar.GetType();
167 if (type == typeof(HijriCalendar)) {
169 using (var sr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Generator.HijriCalendar.js"))) {
170 convert = sr.ReadToEnd();
172 int adjustment = ((HijriCalendar)df.Calendar).HijriAdjustment;
173 convert = convert.Replace("%HIJRIADJUSTMENT%", adjustment.ToString(CultureInfo.InvariantCulture));
174 info.convertScriptBlock = convert;
176 else if (type == typeof(UmAlQuraCalendar)) {
177 using (var sr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Generator.UmAlQuraCalendar.js"))) {
178 info.convertScriptBlock = sr.ReadToEnd();
185 private static GlobalizationInfo.DateFormatInfo.EraInfo[] GetEraInfo(CultureInfo culture) {
186 Calendar cal = culture.DateTimeFormat.Calendar;
187 List<GlobalizationInfo.DateFormatInfo.EraInfo> eras = null;
189 eras = new List<GlobalizationInfo.DateFormatInfo.EraInfo>();
190 foreach (var eraNum in cal.Eras) {
191 eras.Add(GetEraInfo(culture, eraNum));
194 return eras == null ? null : eras.ToArray();
197 private static GlobalizationInfo.DateFormatInfo.EraInfo GetEraInfo(CultureInfo culture, int eraNum) {
198 var era = new GlobalizationInfo.DateFormatInfo.EraInfo {
199 name = culture.DateTimeFormat.GetEraName(eraNum),
203 var calendar = culture.DateTimeFormat.Calendar;
205 Type type = calendar.GetType();
206 if (type != typeof(GregorianCalendar)) {
207 if (type == typeof(TaiwanCalendar)) {
210 else if (type == typeof(KoreanCalendar)) {
213 else if (type == typeof(ThaiBuddhistCalendar)) {
216 else if (type == typeof(JapaneseCalendar)) {
219 era.start = 0xdf9984200L;
223 era.start = -1357603200000L;
227 era.start = -1812153600000L;
235 throw new InvalidOperationException("Invalid era number for JapaneseCalendar: " + eraNum.ToString());
242 private static Dictionary<String, String> GetPatterns(DateTimeFormatInfo df) {
243 var patterns = new Dictionary<String, String> {
244 { "d", df.ShortDatePattern },
245 { "D", df.LongDatePattern },
246 { "t", df.ShortTimePattern },
247 { "T", df.LongTimePattern },
248 { "f", df.LongDatePattern + " " + df.ShortTimePattern },
249 { "F", df.FullDateTimePattern },
250 { "M", df.MonthDayPattern },
251 { "S", df.SortableDateTimePattern },
252 { "Y", df.YearMonthPattern }
257 private static bool ArrayEquality(Array arr1, Array arr2) {
258 if (arr1.Length == arr2.Length) {
259 for (int i = 0; i < arr1.Length; i++) {
260 if (!arr1.GetValue(i).Equals(arr2.GetValue(i))) {
269 private static bool ListEquality(IList arr1, IList arr2) {
270 if (arr1.Count == arr2.Count) {
271 for (int i = 0; i < arr1.Count; i++) {
274 if (val1 == null || !val1.Equals(val2)) {
275 if (val1 is IList && val2 is IList) {
276 if (!ListEquality(val1 as IList, val2 as IList)) {
280 else if (val1 is Dictionary<String, Object> && val2 is Dictionary<String, Object>) {
281 var diff = DiffGlobInfos((Dictionary<String, Object>)val1, (Dictionary<String, Object>)val2);
282 if (diff.Count > 0) {
296 private static string GetFromStringList(string[] list, int value) {
297 return value < list.Length ? list[value] : null;
300 public Dictionary<String, Object> ToDictionary(bool diffCalendars) {
301 var jss = new JavaScriptSerializer();
302 var str = jss.Serialize(this);
303 var dictionary = jss.Deserialize<Dictionary<String, Object>>(str);
304 var cals = (Dictionary<String, Object>) dictionary["calendars"];
305 Dictionary<String, Object> basisStandardCal = null;
306 if (GlobalizationInfo.BasisGlobInfo != null) {
307 basisStandardCal = (Dictionary<String, Object>)((Dictionary<String, Object>)GlobalizationInfo.BasisGlobInfo["calendars"])["standard"];
308 foreach (var pair in this.calendars) {
309 var cal = (Dictionary<String, Object>)cals[pair.Key];
311 // make each calendar a diff from the standard basis calendar
312 cals[pair.Key] = cal = DiffGlobInfos(basisStandardCal, cal);
314 // apply convert script if it exists
315 if (!String.IsNullOrEmpty(pair.Value.convertScriptBlock)) {
316 cal["convert"] = pair.Value.convertScriptBlock;
318 // remove redundant monthsGenitive array if it is equivilent to months
319 Dictionary<String, Object> months = cal.ContainsKey("months") ? (Dictionary<String, Object>)cal["months"] : null;
320 Dictionary<String, Object> monthsGenitive = cal.ContainsKey("monthsGenitive") ? (Dictionary<String, Object>)cal["monthsGenitive"] : null;
321 Dictionary<String, Object> diff = (months != null && monthsGenitive != null) ? DiffGlobInfos(months, monthsGenitive) : null;
322 if (diff != null && diff.Count == 0) {
323 // the genitive months are the same as months, so remove it since it's optional
324 cal.Remove("monthsGenitive");
331 public static string GenerateJavaScript(string extend, string global, CultureInfo culture, string name, Dictionary<String, Object> dictionary, StringBuilder aggregateScript) {
332 string cultureFragment = ToJavaScript(extend, culture, dictionary, 1, false);
334 if (aggregateScript != null) {
335 aggregateScript.AppendFormat(CultureInfo.InvariantCulture, @"
336 Globalize.addCultureInfo( ""{0}"", ""default"", {{
339 ", name, cultureFragment, extend);
342 return string.Format(CultureInfo.InvariantCulture, @"/*
343 * Globalize Culture {0}
345 * http://github.com/jquery/globalize
347 * Copyright Software Freedom Conservancy, Inc.
348 * Dual licensed under the MIT or GPL Version 2 licenses.
349 * http://jquery.org/license
351 * This file was generated by the Globalize Culture Generator
352 * Translation: bugs found in this file need to be fixed in the generator
355 (function( window, undefined ) {{
359 if ( typeof require !== ""undefined""
360 && typeof exports !== ""undefined""
361 && typeof module !== ""undefined"" ) {{
363 Globalize = require( ""globalize"" );
366 Globalize = window.Globalize;
369 Globalize.addCultureInfo( ""{0}"", ""default"", {{
374 ", name, cultureFragment);
377 private static string Serialize(object value) {
378 // no need to escape single quotes
379 return _jss.Serialize(value).Replace("\\u0027", "'");
382 private static string ToJavaScript(string extend, CultureInfo culture, Dictionary<String, Object> dictionary, int level, bool isCalendars) {
383 StringBuilder sb = new StringBuilder();
384 string padding = _padding.Substring(0, level);
386 foreach (var pair in dictionary) {
391 if (pair.Value is Dictionary<String, Object>) {
392 sb.AppendFormat("{0}{1}: {{\n{2}\n{0}}}", padding, pair.Key, ToJavaScript(extend, culture, (Dictionary<String, Object>)pair.Value, level + 1, pair.Key.Equals("calendars")));
394 else if (pair.Key.Equals("convert")) {
395 sb.AppendFormat("{0}convert: {{\n{1}\n{0}}}", padding, pair.Value);
397 else if (pair.Key.Equals("groupSeparator")) {
398 sb.AppendFormat("{0}\",\": {1}", padding, Serialize(pair.Value));
400 else if (pair.Key.Equals("decimalSeparator")) {
401 sb.AppendFormat("{0}\".\": {1}", padding, Serialize(pair.Value));
403 else if (pair.Key.Equals("positive")) {
404 sb.AppendFormat("{0}\"+\": {1}", padding, Serialize(pair.Value));
406 else if (pair.Key.Equals("negative")) {
407 sb.AppendFormat("{0}\"-\": {1}", padding, Serialize(pair.Value));
409 else if (pair.Key.Equals("dateSeparator")) {
410 sb.AppendFormat("{0}\"/\": {1}", padding, Serialize(pair.Value));
412 else if (pair.Key.Equals("timeSeparator")) {
413 sb.AppendFormat("{0}\":\": {1}", padding, Serialize(pair.Value));
416 sb.AppendFormat("{0}{1}: {2}", padding, pair.Key, Serialize(pair.Value));
419 return sb.ToString();
422 private static JavaScriptSerializer _jss = new JavaScriptSerializer();
423 private static string _padding = " ";
425 private static Dictionary<String, Object> ToDictionary(IEnumerable<KeyValuePair<String, Object>> pairs) {
426 var d = new Dictionary<String, Object>();
427 foreach (var pair in pairs) {
428 d.Add(pair.Key, pair.Value);
433 public static Dictionary<String, Object> DiffGlobInfos(Dictionary<String, Object> glob1, Dictionary<String, Object> glob2) {
434 var unique = new Dictionary<String, Object>();
435 var comparer = new KeyValueComparer();
437 var diff = ToDictionary(glob2.Except(glob1, comparer));
438 foreach (var pair in glob2) {
439 if (diff.ContainsKey(pair.Key) && pair.Value != null) {
440 if (pair.Value is Dictionary<String, Object>) {
441 var subdiff = glob1.ContainsKey(pair.Key) ? DiffGlobInfos((Dictionary<String, Object>)glob1[pair.Key], (Dictionary<String, Object>)pair.Value) : (Dictionary<String, Object>)pair.Value;
442 if (subdiff.Count > 0) {
443 //Debug.WriteLine("Replacing\n {0}\nwith\n {1}", _jss.Serialize(diff[pair.Key]), _jss.Serialize(subdiff));
444 diff[pair.Key] = subdiff;
447 //Debug.WriteLine("\nRemoving {0}\n", _jss.Serialize(pair.Key));
448 diff.Remove(pair.Key);
451 else if (pair.Value is IList) {
452 if (glob1.ContainsKey(pair.Key) && ListEquality((IList)pair.Value, (IList)glob1[pair.Key])) {
453 diff.Remove(pair.Key);
462 public class KeyValueComparer : IEqualityComparer<KeyValuePair<String, Object>> {
463 public bool Equals(KeyValuePair<String, Object> x, KeyValuePair<String, Object> y) {
464 if (x.Key.Equals(y.Key)) {
465 if ((x.Value == null && y.Value == null) || (x.Value != null && x.Value.Equals(y.Value))) {
468 else if (x.Value is ArrayList && y.Value is ArrayList) {
469 return ListEquality((IList)x.Value, (IList)y.Value);
471 else if (x.Value is Array && y.Value is Array) {
472 return ArrayEquality(x.Value as Array, y.Value as Array);
474 else if (x.Value is Dictionary<String, Object> && y.Value is Dictionary<String, Object>) {
475 var diff = DiffGlobInfos((Dictionary<String, Object>)x.Value, (Dictionary<String, Object>)y.Value);
476 if (diff.Count == 0) {
480 // Debug.WriteLine(" Dictionaries diff:\n {0}\n {1}", _jss.Serialize(x.Value), _jss.Serialize(y.Value));
484 //Debug.WriteLine(" Diff found: {0}={1}, {2}={3}", x.Key, x.Value, y.Key, y.Value);
488 public int GetHashCode(KeyValuePair<String, Object> obj) {
489 return obj.GetHashCode();
493 public class NumberFormatInfo {
494 public string[] pattern;
496 public string groupSeparator;
497 public string decimalSeparator;
498 public int[] groupSizes;
500 public string negative;
501 public string negativeInfinity;
502 public string positive;
503 public string positiveInfinity;
504 public NumberClassFormatInfo percent;
505 public NumberClassFormatInfo currency;
507 public class NumberClassFormatInfo {
508 public string[] pattern;
510 public int[] groupSizes;
511 public string groupSeparator;
512 public string decimalSeparator;
513 public string symbol;
517 public class DateFormatInfo {
519 public string dateSeparator;
520 public string timeSeparator;
523 public MonthInfo months;
524 public MonthInfo monthsGenitive;
527 public EraInfo[] eras;
528 public int twoDigitYearMax;
529 public Dictionary<String, String> patterns;
530 internal string convertScriptBlock;
532 public class EraInfo {
538 public class MonthInfo {
539 public string[] names;
540 public string[] namesAbbr;
543 public class DayInfo {
544 public string[] names;
545 public string[] namesAbbr;
546 public string[] namesShort;
552 public class Program {
554 private static void WriteCulture(string outputdir, string fileName, string extend, string global, CultureInfo culture, StringBuilder aggregateScript) {
555 var globInfo = GlobalizationInfo.GetGlobInfo(culture);
556 var diff = (String.IsNullOrEmpty(extend) || culture == CultureInfo.InvariantCulture || culture.Name.Equals("en")) ? globInfo.ToDictionary(false) : GlobalizationInfo.DiffGlobInfos(GlobalizationInfo.BasisGlobInfo, globInfo.ToDictionary(true));
558 // Fix for Issue #31 - en-US 'englishName' is wrong
559 // Special case diff of englishName for en-US. The generator diff seemingly finds both "en" and "en-US" to
560 // have englishName "English (United States)" but globalize.js (correctly) has the neutral "English" for "en"/"default"
561 if (culture.Name.Equals("en-US")) {
562 diff.Add("name", globInfo.name);
563 diff.Add("englishName", globInfo.englishName);
566 var script = GlobalizationInfo.GenerateJavaScript(extend, global, culture, culture.Name, diff, aggregateScript);
567 var filePath = Path.Combine(outputdir, String.Format(fileName, (String.IsNullOrEmpty(culture.Name) ? "invariant" : culture.Name)));
569 File.WriteAllText(filePath, script);
570 Console.WriteLine(filePath);
574 static void Main(string[] args) {
575 string outputdir = "lib\\cultures";
576 string extend = "extend";
577 string global = "Globalization";
578 string fileName = "globalize.culture.{0}.js";
579 string aggregateFileName = "globalize.cultures.js";
580 foreach (string param in string.Join(" ", args).SplitCommandLine()) {
581 if (param.StartsWith("/o:")) {
582 outputdir = param.Substring("/o:".Length);
584 else if (param == "/?") {
586 Usage:glob-generator [<options>]
590 /o: The directory to put the culture scripts into. The directory will be
591 created if it does not exist. Existing scripts there will be
592 overwritten if necessary.
593 default: ""lib\cultures""
599 Directory.CreateDirectory(outputdir);
600 GlobalizationInfo.BasisGlobInfo = GlobalizationInfo.GetGlobInfo(CultureInfo.CreateSpecificCulture("en")).ToDictionary(false);
602 StringBuilder aggregateScript = new StringBuilder();
606 Globalize.addCultureInfo( ""{0}"", ""default"", {{
614 aggregateScript.Append(
618 * http://github.com/jquery/globalize
620 * Copyright Software Freedom Conservancy, Inc.
621 * Dual licensed under the MIT or GPL Version 2 licenses.
622 * http://jquery.org/license
624 * This file was generated by the Globalize Culture Generator
625 * Translation: bugs found in this file need to be fixed in the generator
628 (function( window, undefined ) {
632 if ( typeof require !== ""undefined""
633 && typeof exports !== ""undefined""
634 && typeof module !== ""undefined"" ) {
636 Globalize = require( ""globalize"" );
639 Globalize = window.Globalize;
644 foreach (var culture in CultureInfo.GetCultures(CultureTypes.AllCultures)) {
645 if (!String.IsNullOrEmpty(culture.Name) && culture != CultureInfo.InvariantCulture && culture.Name != "en") {
646 WriteCulture(outputdir, fileName, extend, global, culture, aggregateScript);
651 aggregateScript.Append("\r\n}( this ));\r\n");
652 string aggregateScriptString = aggregateScript.ToString();
653 string aggregatePath = Path.Combine(outputdir, aggregateFileName);
654 File.WriteAllText(aggregatePath, aggregateScriptString);
655 Console.WriteLine(aggregatePath);
657 Console.WriteLine("Done! Generated scripts for a total of {0} cultures, and 1 aggregate script.", count);