1 /*
  2  * Locale.js - Locale specifier definition
  3  *
  4  * Copyright © 2012-2015, 2018,2021 JEDLSoft
  5  *
  6  * Licensed under the Apache License, Version 2.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at
  9  *
 10  *     http://www.apache.org/licenses/LICENSE-2.0
 11  *
 12  * Unless required by applicable law or agreed to in writing, software
 13  * distributed under the License is distributed on an "AS IS" BASIS,
 14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 15  *
 16  * See the License for the specific language governing permissions and
 17  * limitations under the License.
 18  */
 19 
 20 var ilib = require("./ilib.js");
 21 var JSUtils = require("./JSUtils.js");
 22 
 23 /**
 24  * @class
 25  * Create a new locale instance. Locales are specified either with a specifier string
 26  * that follows the BCP-47 convention (roughly: "language-region-script-variant") or
 27  * with 4 parameters that specify the language, region, variant, and script individually.<p>
 28  *
 29  * The language is given as an ISO 639-1 two-letter, lower-case language code. You
 30  * can find a full list of these codes at
 31  * <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes</a><p>
 32  *
 33  * The region is given as an ISO 3166-1 two-letter, upper-case region code. You can
 34  * find a full list of these codes at
 35  * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2</a>.<p>
 36  *
 37  * The variant is any string that does not contain a dash which further differentiates
 38  * locales from each other.<p>
 39  *
 40  * The script is given as the ISO 15924 four-letter script code. In some locales,
 41  * text may be validly written in more than one script. For example, Serbian is often
 42  * written in both Latin and Cyrillic, though not usually mixed together. You can find a
 43  * full list of these codes at
 44  * <a href="http://en.wikipedia.org/wiki/ISO_15924#List_of_codes">http://en.wikipedia.org/wiki/ISO_15924#List_of_codes</a>.<p>
 45  *
 46  * As an example in ilib, the script can be used in the date formatter. Dates formatted
 47  * in Serbian could have day-of-week names or month names written in the Latin
 48  * or Cyrillic script. Often one script is default such that sr-SR-Latn is the same
 49  * as sr-SR so the script code "Latn" can be left off of the locale spec.<p>
 50  *
 51  * Each part is optional, and an empty string in the specifier before or after a
 52  * dash or as a parameter to the constructor denotes an unspecified value. In this
 53  * case, many of the ilib functions will treat the locale as generic. For example
 54  * the locale "en-" is equivalent to "en" and to "en--" and denotes a locale
 55  * of "English" with an unspecified region and variant, which typically matches
 56  * any region or variant.<p>
 57  *
 58  * Without any arguments to the constructor, this function returns the locale of
 59  * the host Javascript engine.<p>
 60  *
 61  *
 62  * @constructor
 63  * @param {?string|Locale=} language the ISO 639 2-letter code for the language, or a full
 64  * locale spec in BCP-47 format, or another Locale instance to copy from
 65  * @param {string=} region the ISO 3166 2-letter code for the region
 66  * @param {string=} variant the name of the variant of this locale, if any
 67  * @param {string=} script the ISO 15924 code of the script for this locale, if any
 68  */
 69 var Locale = function(language, region, variant, script) {
 70     if (typeof(region) === 'undefined' && typeof(variant) === 'undefined' && typeof(script) === 'undefined') {
 71         var spec = language || ilib.getLocale();
 72         if (typeof(spec) === 'string') {
 73             var parts = spec.split(/[-_]/g);
 74             for ( var i = 0; i < parts.length; i++ ) {
 75                 if (Locale._isLanguageCode(parts[i])) {
 76                     /**
 77                      * @private
 78                      * @type {string|undefined}
 79                      */
 80                     this.language = parts[i];
 81                 } else if (Locale._isRegionCode(parts[i])) {
 82                     /**
 83                      * @private
 84                      * @type {string|undefined}
 85                      */
 86                     this.region = parts[i];
 87                 } else if (Locale._isScriptCode(parts[i])) {
 88                     /**
 89                      * @private
 90                      * @type {string|undefined}
 91                      */
 92                     this.script = parts[i];
 93                 } else {
 94                     /**
 95                      * @private
 96                      * @type {string|undefined}
 97                      */
 98                     this.variant = parts[i];
 99                 }
100             }
101             this.language = this.language || undefined;
102             this.region = this.region || undefined;
103             this.script = this.script || undefined;
104             this.variant = this.variant || undefined;
105         } else if (typeof(spec) === 'object') {
106             this.language = spec.language || undefined;
107             this.region = spec.region || undefined;
108             this.script = spec.script || undefined;
109             this.variant = spec.variant || undefined;
110         }
111     } else {
112         if (language && typeof(language) === "string") {
113             language = language.trim();
114             this.language = language.length > 0 ? language.toLowerCase() : undefined;
115         } else {
116             this.language = undefined;
117         }
118         if (region && typeof(region) === "string") {
119             region = region.trim();
120             this.region = region.length > 0 ? region.toUpperCase() : undefined;
121         } else {
122             this.region = undefined;
123         }
124         if (variant && typeof(variant) === "string") {
125             variant = variant.trim();
126             this.variant = variant.length > 0 ? variant : undefined;
127         } else {
128             this.variant = undefined;
129         }
130         if (script && typeof(script) === "string") {
131             script = script.trim();
132             this.script = script.length > 0 ? script : undefined;
133         } else {
134             this.script = undefined;
135         }
136     }
137     this._genSpec();
138 };
139 
140 // from http://en.wikipedia.org/wiki/ISO_3166-1
141 Locale.a2toa3regmap = {
142     "AF": "AFG",
143     "AX": "ALA",
144     "AL": "ALB",
145     "DZ": "DZA",
146     "AS": "ASM",
147     "AD": "AND",
148     "AO": "AGO",
149     "AI": "AIA",
150     "AQ": "ATA",
151     "AG": "ATG",
152     "AR": "ARG",
153     "AM": "ARM",
154     "AW": "ABW",
155     "AU": "AUS",
156     "AT": "AUT",
157     "AZ": "AZE",
158     "BS": "BHS",
159     "BH": "BHR",
160     "BD": "BGD",
161     "BB": "BRB",
162     "BY": "BLR",
163     "BE": "BEL",
164     "BZ": "BLZ",
165     "BJ": "BEN",
166     "BM": "BMU",
167     "BT": "BTN",
168     "BO": "BOL",
169     "BQ": "BES",
170     "BA": "BIH",
171     "BW": "BWA",
172     "BV": "BVT",
173     "BR": "BRA",
174     "IO": "IOT",
175     "BN": "BRN",
176     "BG": "BGR",
177     "BF": "BFA",
178     "BI": "BDI",
179     "KH": "KHM",
180     "CM": "CMR",
181     "CA": "CAN",
182     "CV": "CPV",
183     "KY": "CYM",
184     "CF": "CAF",
185     "TD": "TCD",
186     "CL": "CHL",
187     "CN": "CHN",
188     "CX": "CXR",
189     "CC": "CCK",
190     "CO": "COL",
191     "KM": "COM",
192     "CG": "COG",
193     "CD": "COD",
194     "CK": "COK",
195     "CR": "CRI",
196     "CI": "CIV",
197     "HR": "HRV",
198     "CU": "CUB",
199     "CW": "CUW",
200     "CY": "CYP",
201     "CZ": "CZE",
202     "DK": "DNK",
203     "DJ": "DJI",
204     "DM": "DMA",
205     "DO": "DOM",
206     "EC": "ECU",
207     "EG": "EGY",
208     "SV": "SLV",
209     "GQ": "GNQ",
210     "ER": "ERI",
211     "EE": "EST",
212     "ET": "ETH",
213     "FK": "FLK",
214     "FO": "FRO",
215     "FJ": "FJI",
216     "FI": "FIN",
217     "FR": "FRA",
218     "GF": "GUF",
219     "PF": "PYF",
220     "TF": "ATF",
221     "GA": "GAB",
222     "GM": "GMB",
223     "GE": "GEO",
224     "DE": "DEU",
225     "GH": "GHA",
226     "GI": "GIB",
227     "GR": "GRC",
228     "GL": "GRL",
229     "GD": "GRD",
230     "GP": "GLP",
231     "GU": "GUM",
232     "GT": "GTM",
233     "GG": "GGY",
234     "GN": "GIN",
235     "GW": "GNB",
236     "GY": "GUY",
237     "HT": "HTI",
238     "HM": "HMD",
239     "VA": "VAT",
240     "HN": "HND",
241     "HK": "HKG",
242     "HU": "HUN",
243     "IS": "ISL",
244     "IN": "IND",
245     "ID": "IDN",
246     "IR": "IRN",
247     "IQ": "IRQ",
248     "IE": "IRL",
249     "IM": "IMN",
250     "IL": "ISR",
251     "IT": "ITA",
252     "JM": "JAM",
253     "JP": "JPN",
254     "JE": "JEY",
255     "JO": "JOR",
256     "KZ": "KAZ",
257     "KE": "KEN",
258     "KI": "KIR",
259     "KP": "PRK",
260     "KR": "KOR",
261     "KW": "KWT",
262     "KG": "KGZ",
263     "LA": "LAO",
264     "LV": "LVA",
265     "LB": "LBN",
266     "LS": "LSO",
267     "LR": "LBR",
268     "LY": "LBY",
269     "LI": "LIE",
270     "LT": "LTU",
271     "LU": "LUX",
272     "MO": "MAC",
273     "MK": "MKD",
274     "MG": "MDG",
275     "MW": "MWI",
276     "MY": "MYS",
277     "MV": "MDV",
278     "ML": "MLI",
279     "MT": "MLT",
280     "MH": "MHL",
281     "MQ": "MTQ",
282     "MR": "MRT",
283     "MU": "MUS",
284     "YT": "MYT",
285     "MX": "MEX",
286     "FM": "FSM",
287     "MD": "MDA",
288     "MC": "MCO",
289     "MN": "MNG",
290     "ME": "MNE",
291     "MS": "MSR",
292     "MA": "MAR",
293     "MZ": "MOZ",
294     "MM": "MMR",
295     "NA": "NAM",
296     "NR": "NRU",
297     "NP": "NPL",
298     "NL": "NLD",
299     "NC": "NCL",
300     "NZ": "NZL",
301     "NI": "NIC",
302     "NE": "NER",
303     "NG": "NGA",
304     "NU": "NIU",
305     "NF": "NFK",
306     "MP": "MNP",
307     "NO": "NOR",
308     "OM": "OMN",
309     "PK": "PAK",
310     "PW": "PLW",
311     "PS": "PSE",
312     "PA": "PAN",
313     "PG": "PNG",
314     "PY": "PRY",
315     "PE": "PER",
316     "PH": "PHL",
317     "PN": "PCN",
318     "PL": "POL",
319     "PT": "PRT",
320     "PR": "PRI",
321     "QA": "QAT",
322     "RE": "REU",
323     "RO": "ROU",
324     "RU": "RUS",
325     "RW": "RWA",
326     "BL": "BLM",
327     "SH": "SHN",
328     "KN": "KNA",
329     "LC": "LCA",
330     "MF": "MAF",
331     "PM": "SPM",
332     "VC": "VCT",
333     "WS": "WSM",
334     "SM": "SMR",
335     "ST": "STP",
336     "SA": "SAU",
337     "SN": "SEN",
338     "RS": "SRB",
339     "SC": "SYC",
340     "SL": "SLE",
341     "SG": "SGP",
342     "SX": "SXM",
343     "SK": "SVK",
344     "SI": "SVN",
345     "SB": "SLB",
346     "SO": "SOM",
347     "ZA": "ZAF",
348     "GS": "SGS",
349     "SS": "SSD",
350     "ES": "ESP",
351     "LK": "LKA",
352     "SD": "SDN",
353     "SR": "SUR",
354     "SJ": "SJM",
355     "SZ": "SWZ",
356     "SE": "SWE",
357     "CH": "CHE",
358     "SY": "SYR",
359     "TW": "TWN",
360     "TJ": "TJK",
361     "TZ": "TZA",
362     "TH": "THA",
363     "TL": "TLS",
364     "TG": "TGO",
365     "TK": "TKL",
366     "TO": "TON",
367     "TT": "TTO",
368     "TN": "TUN",
369     "TR": "TUR",
370     "TM": "TKM",
371     "TC": "TCA",
372     "TV": "TUV",
373     "UG": "UGA",
374     "UA": "UKR",
375     "AE": "ARE",
376     "GB": "GBR",
377     "US": "USA",
378     "UM": "UMI",
379     "UY": "URY",
380     "UZ": "UZB",
381     "VU": "VUT",
382     "VE": "VEN",
383     "VN": "VNM",
384     "VG": "VGB",
385     "VI": "VIR",
386     "WF": "WLF",
387     "EH": "ESH",
388     "YE": "YEM",
389     "ZM": "ZMB",
390     "ZW": "ZWE"
391 };
392 
393 
394 Locale.a1toa3langmap = {
395     "ab": "abk",
396     "aa": "aar",
397     "af": "afr",
398     "ak": "aka",
399     "sq": "sqi",
400     "am": "amh",
401     "ar": "ara",
402     "an": "arg",
403     "hy": "hye",
404     "as": "asm",
405     "av": "ava",
406     "ae": "ave",
407     "ay": "aym",
408     "az": "aze",
409     "bm": "bam",
410     "ba": "bak",
411     "eu": "eus",
412     "be": "bel",
413     "bn": "ben",
414     "bh": "bih",
415     "bi": "bis",
416     "bs": "bos",
417     "br": "bre",
418     "bg": "bul",
419     "my": "mya",
420     "ca": "cat",
421     "ch": "cha",
422     "ce": "che",
423     "ny": "nya",
424     "zh": "zho",
425     "cv": "chv",
426     "kw": "cor",
427     "co": "cos",
428     "cr": "cre",
429     "hr": "hrv",
430     "cs": "ces",
431     "da": "dan",
432     "dv": "div",
433     "nl": "nld",
434     "dz": "dzo",
435     "en": "eng",
436     "eo": "epo",
437     "et": "est",
438     "ee": "ewe",
439     "fo": "fao",
440     "fj": "fij",
441     "fi": "fin",
442     "fr": "fra",
443     "ff": "ful",
444     "gl": "glg",
445     "ka": "kat",
446     "de": "deu",
447     "el": "ell",
448     "gn": "grn",
449     "gu": "guj",
450     "ht": "hat",
451     "ha": "hau",
452     "he": "heb",
453     "hz": "her",
454     "hi": "hin",
455     "ho": "hmo",
456     "hu": "hun",
457     "ia": "ina",
458     "id": "ind",
459     "ie": "ile",
460     "ga": "gle",
461     "ig": "ibo",
462     "ik": "ipk",
463     "io": "ido",
464     "is": "isl",
465     "it": "ita",
466     "iu": "iku",
467     "ja": "jpn",
468     "jv": "jav",
469     "kl": "kal",
470     "kn": "kan",
471     "kr": "kau",
472     "ks": "kas",
473     "kk": "kaz",
474     "km": "khm",
475     "ki": "kik",
476     "rw": "kin",
477     "ky": "kir",
478     "kv": "kom",
479     "kg": "kon",
480     "ko": "kor",
481     "ku": "kur",
482     "kj": "kua",
483     "la": "lat",
484     "lb": "ltz",
485     "lg": "lug",
486     "li": "lim",
487     "ln": "lin",
488     "lo": "lao",
489     "lt": "lit",
490     "lu": "lub",
491     "lv": "lav",
492     "gv": "glv",
493     "mk": "mkd",
494     "mg": "mlg",
495     "ms": "msa",
496     "ml": "mal",
497     "mt": "mlt",
498     "mi": "mri",
499     "mr": "mar",
500     "mh": "mah",
501     "mn": "mon",
502     "na": "nau",
503     "nv": "nav",
504     "nb": "nob",
505     "nd": "nde",
506     "ne": "nep",
507     "ng": "ndo",
508     "nn": "nno",
509     "no": "nor",
510     "ii": "iii",
511     "nr": "nbl",
512     "oc": "oci",
513     "oj": "oji",
514     "cu": "chu",
515     "om": "orm",
516     "or": "ori",
517     "os": "oss",
518     "pa": "pan",
519     "pi": "pli",
520     "fa": "fas",
521     "pl": "pol",
522     "ps": "pus",
523     "pt": "por",
524     "qu": "que",
525     "rm": "roh",
526     "rn": "run",
527     "ro": "ron",
528     "ru": "rus",
529     "sa": "san",
530     "sc": "srd",
531     "sd": "snd",
532     "se": "sme",
533     "sm": "smo",
534     "sg": "sag",
535     "sr": "srp",
536     "gd": "gla",
537     "sn": "sna",
538     "si": "sin",
539     "sk": "slk",
540     "sl": "slv",
541     "so": "som",
542     "st": "sot",
543     "es": "spa",
544     "su": "sun",
545     "sw": "swa",
546     "ss": "ssw",
547     "sv": "swe",
548     "ta": "tam",
549     "te": "tel",
550     "tg": "tgk",
551     "th": "tha",
552     "ti": "tir",
553     "bo": "bod",
554     "tk": "tuk",
555     "tl": "tgl",
556     "tn": "tsn",
557     "to": "ton",
558     "tr": "tur",
559     "ts": "tso",
560     "tt": "tat",
561     "tw": "twi",
562     "ty": "tah",
563     "ug": "uig",
564     "uk": "ukr",
565     "ur": "urd",
566     "uz": "uzb",
567     "ve": "ven",
568     "vi": "vie",
569     "vo": "vol",
570     "wa": "wln",
571     "cy": "cym",
572     "wo": "wol",
573     "fy": "fry",
574     "xh": "xho",
575     "yi": "yid",
576     "yo": "yor",
577     "za": "zha",
578     "zu": "zul"
579 };
580 
581 // the list below is originally from https://unicode.org/iso15924/iso15924-codes.html
582 Locale.iso15924 = [
583     "Adlm",
584     "Afak",
585     "Aghb",
586     "Ahom",
587     "Arab",
588     "Aran",
589     "Armi",
590     "Armn",
591     "Avst",
592     "Bali",
593     "Bamu",
594     "Bass",
595     "Batk",
596     "Beng",
597     "Bhks",
598     "Blis",
599     "Bopo",
600     "Brah",
601     "Brai",
602     "Bugi",
603     "Buhd",
604     "Cakm",
605     "Cans",
606     "Cari",
607     "Cham",
608     "Cher",
609     "Chrs",
610     "Cirt",
611     "Copt",
612     "Cpmn",
613     "Cprt",
614     "Cyrl",
615     "Cyrs",
616     "Deva",
617     "Diak",
618     "Dogr",
619     "Dsrt",
620     "Dupl",
621     "Egyd",
622     "Egyh",
623     "Egyp",
624     "Elba",
625     "Elym",
626     "Ethi",
627     "Geok",
628     "Geor",
629     "Glag",
630     "Gong",
631     "Gonm",
632     "Goth",
633     "Gran",
634     "Grek",
635     "Gujr",
636     "Guru",
637     "Hanb",
638     "Hang",
639     "Hani",
640     "Hano",
641     "Hans",
642     "Hant",
643     "Hatr",
644     "Hebr",
645     "Hira",
646     "Hluw",
647     "Hmng",
648     "Hmnp",
649     "Hrkt",
650     "Hung",
651     "Inds",
652     "Ital",
653     "Jamo",
654     "Java",
655     "Jpan",
656     "Jurc",
657     "Kali",
658     "Kana",
659     "Khar",
660     "Khmr",
661     "Khoj",
662     "Kitl",
663     "Kits",
664     "Knda",
665     "Kore",
666     "Kpel",
667     "Kthi",
668     "Lana",
669     "Laoo",
670     "Latf",
671     "Latg",
672     "Latn",
673     "Leke",
674     "Lepc",
675     "Limb",
676     "Lina",
677     "Linb",
678     "Lisu",
679     "Loma",
680     "Lyci",
681     "Lydi",
682     "Mahj",
683     "Maka",
684     "Mand",
685     "Mani",
686     "Marc",
687     "Maya",
688     "Medf",
689     "Mend",
690     "Merc",
691     "Mero",
692     "Mlym",
693     "Modi",
694     "Mong",
695     "Moon",
696     "Mroo",
697     "Mtei",
698     "Mult",
699     "Mymr",
700     "Nand",
701     "Narb",
702     "Nbat",
703     "Newa",
704     "Nkdb",
705     "Nkgb",
706     "Nkoo",
707     "Nshu",
708     "Ogam",
709     "Olck",
710     "Orkh",
711     "Orya",
712     "Osge",
713     "Osma",
714     "Palm",
715     "Pauc",
716     "Perm",
717     "Phag",
718     "Phli",
719     "Phlp",
720     "Phlv",
721     "Phnx",
722     "Plrd",
723     "Piqd",
724     "Prti",
725     "Qaaa",
726     "Qabx",
727     "Rjng",
728     "Rohg",
729     "Roro",
730     "Runr",
731     "Samr",
732     "Sara",
733     "Sarb",
734     "Saur",
735     "Sgnw",
736     "Shaw",
737     "Shrd",
738     "Shui",
739     "Sidd",
740     "Sind",
741     "Sinh",
742     "Sogd",
743     "Sogo",
744     "Sora",
745     "Soyo",
746     "Sund",
747     "Sylo",
748     "Syrc",
749     "Syre",
750     "Syrj",
751     "Syrn",
752     "Tagb",
753     "Takr",
754     "Tale",
755     "Talu",
756     "Taml",
757     "Tang",
758     "Tavt",
759     "Telu",
760     "Teng",
761     "Tfng",
762     "Tglg",
763     "Thaa",
764     "Thai",
765     "Tibt",
766     "Tirh",
767     "Toto",
768     "Ugar",
769     "Vaii",
770     "Visp",
771     "Wara",
772     "Wcho",
773     "Wole",
774     "Xpeo",
775     "Xsux",
776     "Yezi",
777     "Yiii",
778     "Zanb",
779     "Zinh",
780     "Zmth",
781     "Zsye",
782     "Zsym",
783     "Zxxx",
784     "Zyyy",
785     "Zzzz",
786 ];
787 
788 /**
789  * Tell whether or not the str does not start with a lower case ASCII char.
790  * @private
791  * @param {string} str the char to check
792  * @return {boolean} true if the char is not a lower case ASCII char
793  */
794 Locale._notLower = function(str) {
795     // do this with ASCII only so we don't have to depend on the CType functions
796     var ch = str.charCodeAt(0);
797     return ch < 97 || ch > 122;
798 };
799 
800 /**
801  * Tell whether or not the str does not start with an upper case ASCII char.
802  * @private
803  * @param {string} str the char to check
804  * @return {boolean} true if the char is a not an upper case ASCII char
805  */
806 Locale._notUpper = function(str) {
807     // do this with ASCII only so we don't have to depend on the CType functions
808     var ch = str.charCodeAt(0);
809     return ch < 65 || ch > 90;
810 };
811 
812 /**
813  * Tell whether or not the str does not start with a digit char.
814  * @private
815  * @param {string} str the char to check
816  * @return {boolean} true if the char is a not an upper case ASCII char
817  */
818 Locale._notDigit = function(str) {
819     // do this with ASCII only so we don't have to depend on the CType functions
820     var ch = str.charCodeAt(0);
821     return ch < 48 || ch > 57;
822 };
823 
824 /**
825  * Tell whether or not the given string has the correct syntax to be
826  * an ISO 639 language code.
827  *
828  * @private
829  * @param {string} str the string to parse
830  * @return {boolean} true if the string could syntactically be a language code.
831  */
832 Locale._isLanguageCode = function(str) {
833     if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) {
834         return false;
835     }
836 
837     for (var i = 0; i < str.length; i++) {
838         if (Locale._notLower(str.charAt(i))) {
839             return false;
840         }
841     }
842 
843     return true;
844 };
845 
846 /**
847  * Tell whether or not the given string has the correct syntax to be
848  * an ISO 3166 2-letter region code or M.49 3-digit region code.
849  *
850  * @private
851  * @param {string} str the string to parse
852  * @return {boolean} true if the string could syntactically be a language code.
853  */
854 Locale._isRegionCode = function (str) {
855     var i;
856 
857     if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) {
858         return false;
859     }
860 
861     if (str.length === 2) {
862         for (i = 0; i < str.length; i++) {
863             if (Locale._notUpper(str.charAt(i))) {
864                 return false;
865             }
866         }
867     } else {
868         for (i = 0; i < str.length; i++) {
869             if (Locale._notDigit(str.charAt(i))) {
870                 return false;
871             }
872         }
873     }
874 
875     return true;
876 };
877 
878 /**
879  * Tell whether or not the given string has the correct syntax to be
880  * an ISO 639 language code.
881  *
882  * @private
883  * @param {string} str the string to parse
884  * @return {boolean} true if the string could syntactically be a language code.
885  */
886 Locale._isScriptCode = function(str) {
887     if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) {
888         return false;
889     }
890 
891     for (var i = 1; i < 4; i++) {
892         if (Locale._notLower(str.charAt(i))) {
893             return false;
894         }
895     }
896 
897     return true;
898 };
899 
900 /**
901  * Return the ISO-3166 alpha3 equivalent region code for the given ISO 3166 alpha2
902  * region code. If the given alpha2 code is not found, this function returns its
903  * argument unchanged.
904  * @static
905  * @param {string|undefined} alpha2 the alpha2 code to map
906  * @return {string|undefined} the alpha3 equivalent of the given alpha2 code, or the alpha2
907  * parameter if the alpha2 value is not found
908  */
909 Locale.regionAlpha2ToAlpha3 = function(alpha2) {
910     return Locale.a2toa3regmap[alpha2] || alpha2;
911 };
912 
913 /**
914  * Return the ISO-639 alpha3 equivalent language code for the given ISO 639 alpha1
915  * language code. If the given alpha1 code is not found, this function returns its
916  * argument unchanged.
917  * @static
918  * @param {string|undefined} alpha1 the alpha1 code to map
919  * @return {string|undefined} the alpha3 equivalent of the given alpha1 code, or the alpha1
920  * parameter if the alpha1 value is not found
921  */
922 Locale.languageAlpha1ToAlpha3 = function(alpha1) {
923     return Locale.a1toa3langmap[alpha1] || alpha1;
924 };
925 
926 Locale.prototype = {
927     /**
928      * @private
929      */
930     _genSpec: function () {
931         this.spec = this.language || "";
932 
933         if (this.script) {
934             if (this.spec.length > 0) {
935                 this.spec += "-";
936             }
937             this.spec += this.script;
938         }
939 
940         if (this.region) {
941             if (this.spec.length > 0) {
942                 this.spec += "-";
943             }
944             this.spec += this.region;
945         }
946 
947         if (this.variant) {
948             if (this.spec.length > 0) {
949                 this.spec += "-";
950             }
951             this.spec += this.variant;
952         }
953     },
954 
955     /**
956      * Return the ISO 639 language code for this locale.
957      * @return {string|undefined} the language code for this locale
958      */
959     getLanguage: function() {
960         return this.language;
961     },
962 
963     /**
964      * Return the language of this locale as an ISO-639-alpha3 language code
965      * @return {string|undefined} the alpha3 language code of this locale
966      */
967     getLanguageAlpha3: function() {
968         return Locale.languageAlpha1ToAlpha3(this.language);
969     },
970 
971     /**
972      * Return the ISO 3166 region code for this locale.
973      * @return {string|undefined} the region code of this locale
974      */
975     getRegion: function() {
976         return this.region;
977     },
978 
979     /**
980      * Return the region of this locale as an ISO-3166-alpha3 region code
981      * @return {string|undefined} the alpha3 region code of this locale
982      */
983     getRegionAlpha3: function() {
984         return Locale.regionAlpha2ToAlpha3(this.region);
985     },
986 
987     /**
988      * Return the ISO 15924 script code for this locale
989      * @return {string|undefined} the script code of this locale
990      */
991     getScript: function () {
992         return this.script;
993     },
994 
995     /**
996      * Return the variant code for this locale
997      * @return {string|undefined} the variant code of this locale, if any
998      */
999     getVariant: function() {
1000         return this.variant;
1001     },
1002 
1003     /**
1004      * Return the whole locale specifier as a string.
1005      * @return {string} the locale specifier
1006      */
1007     getSpec: function() {
1008         if (!this.spec) this._genSpec();
1009         return this.spec;
1010     },
1011 
1012     /**
1013      * Return the language locale specifier. This includes the
1014      * language and the script if it is available. This can be
1015      * used to see whether the written language of two locales
1016      * match each other regardless of the region or variant.
1017      *
1018      * @return {string} the language locale specifier
1019      */
1020     getLangSpec: function() {
1021         var spec = this.language;
1022         if (spec && this.script) {
1023             spec += "-" + this.script;
1024         }
1025         return spec || "";
1026     },
1027 
1028     /**
1029      * Express this locale object as a string. Currently, this simply calls the getSpec
1030      * function to represent the locale as its specifier.
1031      *
1032      * @return {string} the locale specifier
1033      */
1034     toString: function() {
1035         return this.getSpec();
1036     },
1037 
1038     /**
1039      * Return true if the the other locale is exactly equal to the current one.
1040      * @return {boolean} whether or not the other locale is equal to the current one
1041      */
1042     equals: function(other) {
1043         return this.language === other.language &&
1044             this.region === other.region &&
1045             this.script === other.script &&
1046             this.variant === other.variant;
1047     },
1048 
1049     /**
1050      * Return true if the current locale is the special pseudo locale.
1051      * @return {boolean} true if the current locale is the special pseudo locale
1052      */
1053     isPseudo: function () {
1054         return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1;
1055     },
1056 
1057     /**
1058      * Return true if the current locale uses a valid ISO codes for each component
1059      * of the locale that exists.
1060      * @return {boolean} true if the current locale has all valid components, and
1061      * false otherwise.
1062      */
1063     isValid: function() {
1064         if (!this.language && !this.script && !this.region) return false;
1065 
1066         return !!((!this.language || (Locale._isLanguageCode(this.language) && Locale.a1toa3langmap[this.language])) &&
1067             (!this.script || (Locale._isScriptCode(this.script) && Locale.iso15924.indexOf(this.script) > -1)) &&
1068             (!this.region || (Locale._isRegionCode(this.region) && Locale.a2toa3regmap[this.region])));
1069     }
1070 };
1071 
1072 // static functions
1073 /**
1074  * @private
1075  */
1076 Locale.locales = [];
1077 // !macro localelist
1078 
1079 /**
1080  * Return the list of available locales that this iLib file supports.
1081  * If this copy of ilib is pre-assembled with locale data, then the
1082  * list locales may be much smaller
1083  * than the list of all available locales in the iLib repository. The
1084  * assembly tool will automatically fill in the list for an assembled
1085  * copy of iLib. If this copy is being used with dynamically loaded
1086  * data, then you
1087  * can load any locale that iLib supports. You can form a locale with any
1088  * combination of a language and region tags that exist in the locale
1089  * data directory. Language tags are in the root of the locale data dir,
1090  * and region tags can be found underneath the "und" directory. (The
1091  * region tags are separated into a different dir because the region names
1092  * conflict with language names on file systems that are case-insensitive.)
1093  * If you have culled the locale data directory to limit the size of
1094  * your app, then this function should return only those files that actually exist
1095  * according to the ilibmanifest.json file in the root of that locale
1096  * data dir. Make sure your ilibmanifest.json file is up-to-date with
1097  * respect to the list of files that exist in the locale data dir.
1098  *
1099  * @param {boolean} sync if false, load the list of available files from disk
1100  * asynchronously, otherwise load them synchronously. (Default: true/synchronously)
1101  * @param {Function} onLoad a callback function to call if asynchronous
1102  * load was requested and the list of files have been loaded.
1103  * @return {Array.<string>} this is an array of locale specs for which
1104  * this iLib file has locale data for
1105  */
1106 Locale.getAvailableLocales = function (sync, onLoad) {
1107     var locales = [];
1108     if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') {
1109         locales = Locale.locales;
1110         if (onLoad && typeof(onLoad) === 'function') {
1111             onLoad(locales);
1112         }
1113     } else {
1114         if (typeof(sync) === 'undefined') {
1115             sync = true;
1116         }
1117         ilib._load.listAvailableFiles(sync, function(manifest) {
1118             if (manifest) {
1119                 for (var dir in manifest) {
1120                     var filelist = manifest[dir];
1121                     for (var i = 0; i < filelist.length; i++) {
1122                         if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") {
1123                             locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-"));
1124                         }
1125                     }
1126                 }
1127             }
1128             if (onLoad && typeof(onLoad) === 'function') {
1129                 onLoad(locales);
1130             }
1131         });
1132     }
1133     return locales;
1134 };
1135 
1136 module.exports = Locale;
1137