1 /*
  2  * Locale.js - Locale specifier definition
  3  *
  4  * Copyright © 2012-2015, 2018, 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('-');
 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 /**
582  * Tell whether or not the str does not start with a lower case ASCII char.
583  * @private
584  * @param {string} str the char to check
585  * @return {boolean} true if the char is not a lower case ASCII char
586  */
587 Locale._notLower = function(str) {
588     // do this with ASCII only so we don't have to depend on the CType functions
589     var ch = str.charCodeAt(0);
590     return ch < 97 || ch > 122;
591 };
592 
593 /**
594  * Tell whether or not the str does not start with an upper case ASCII char.
595  * @private
596  * @param {string} str the char to check
597  * @return {boolean} true if the char is a not an upper case ASCII char
598  */
599 Locale._notUpper = function(str) {
600     // do this with ASCII only so we don't have to depend on the CType functions
601     var ch = str.charCodeAt(0);
602     return ch < 65 || ch > 90;
603 };
604 
605 /**
606  * Tell whether or not the str does not start with a digit char.
607  * @private
608  * @param {string} str the char to check
609  * @return {boolean} true if the char is a not an upper case ASCII char
610  */
611 Locale._notDigit = function(str) {
612     // do this with ASCII only so we don't have to depend on the CType functions
613     var ch = str.charCodeAt(0);
614     return ch < 48 || ch > 57;
615 };
616 
617 /**
618  * Tell whether or not the given string has the correct syntax to be
619  * an ISO 639 language code.
620  *
621  * @private
622  * @param {string} str the string to parse
623  * @return {boolean} true if the string could syntactically be a language code.
624  */
625 Locale._isLanguageCode = function(str) {
626     if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) {
627         return false;
628     }
629 
630     for (var i = 0; i < str.length; i++) {
631         if (Locale._notLower(str.charAt(i))) {
632             return false;
633         }
634     }
635 
636     return true;
637 };
638 
639 /**
640  * Tell whether or not the given string has the correct syntax to be
641  * an ISO 3166 2-letter region code or M.49 3-digit region code.
642  *
643  * @private
644  * @param {string} str the string to parse
645  * @return {boolean} true if the string could syntactically be a language code.
646  */
647 Locale._isRegionCode = function (str) {
648     var i;
649 
650     if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) {
651         return false;
652     }
653 
654     if (str.length === 2) {
655         for (i = 0; i < str.length; i++) {
656             if (Locale._notUpper(str.charAt(i))) {
657                 return false;
658             }
659         }
660     } else {
661         for (i = 0; i < str.length; i++) {
662             if (Locale._notDigit(str.charAt(i))) {
663                 return false;
664             }
665         }
666     }
667 
668     return true;
669 };
670 
671 /**
672  * Tell whether or not the given string has the correct syntax to be
673  * an ISO 639 language code.
674  *
675  * @private
676  * @param {string} str the string to parse
677  * @return {boolean} true if the string could syntactically be a language code.
678  */
679 Locale._isScriptCode = function(str) {
680     if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) {
681         return false;
682     }
683 
684     for (var i = 1; i < 4; i++) {
685         if (Locale._notLower(str.charAt(i))) {
686             return false;
687         }
688     }
689 
690     return true;
691 };
692 
693 /**
694  * Return the ISO-3166 alpha3 equivalent region code for the given ISO 3166 alpha2
695  * region code. If the given alpha2 code is not found, this function returns its
696  * argument unchanged.
697  * @static
698  * @param {string|undefined} alpha2 the alpha2 code to map
699  * @return {string|undefined} the alpha3 equivalent of the given alpha2 code, or the alpha2
700  * parameter if the alpha2 value is not found
701  */
702 Locale.regionAlpha2ToAlpha3 = function(alpha2) {
703     return Locale.a2toa3regmap[alpha2] || alpha2;
704 };
705 
706 /**
707  * Return the ISO-639 alpha3 equivalent language code for the given ISO 639 alpha1
708  * language code. If the given alpha1 code is not found, this function returns its
709  * argument unchanged.
710  * @static
711  * @param {string|undefined} alpha1 the alpha1 code to map
712  * @return {string|undefined} the alpha3 equivalent of the given alpha1 code, or the alpha1
713  * parameter if the alpha1 value is not found
714  */
715 Locale.languageAlpha1ToAlpha3 = function(alpha1) {
716     return Locale.a1toa3langmap[alpha1] || alpha1;
717 };
718 
719 Locale.prototype = {
720     /**
721      * @private
722      */
723     _genSpec: function () {
724         this.spec = this.language || "";
725 
726         if (this.script) {
727             if (this.spec.length > 0) {
728                 this.spec += "-";
729             }
730             this.spec += this.script;
731         }
732 
733         if (this.region) {
734             if (this.spec.length > 0) {
735                 this.spec += "-";
736             }
737             this.spec += this.region;
738         }
739 
740         if (this.variant) {
741             if (this.spec.length > 0) {
742                 this.spec += "-";
743             }
744             this.spec += this.variant;
745         }
746     },
747 
748     /**
749      * Return the ISO 639 language code for this locale.
750      * @return {string|undefined} the language code for this locale
751      */
752     getLanguage: function() {
753         return this.language;
754     },
755 
756     /**
757      * Return the language of this locale as an ISO-639-alpha3 language code
758      * @return {string|undefined} the alpha3 language code of this locale
759      */
760     getLanguageAlpha3: function() {
761         return Locale.languageAlpha1ToAlpha3(this.language);
762     },
763 
764     /**
765      * Return the ISO 3166 region code for this locale.
766      * @return {string|undefined} the region code of this locale
767      */
768     getRegion: function() {
769         return this.region;
770     },
771 
772     /**
773      * Return the region of this locale as an ISO-3166-alpha3 region code
774      * @return {string|undefined} the alpha3 region code of this locale
775      */
776     getRegionAlpha3: function() {
777         return Locale.regionAlpha2ToAlpha3(this.region);
778     },
779 
780     /**
781      * Return the ISO 15924 script code for this locale
782      * @return {string|undefined} the script code of this locale
783      */
784     getScript: function () {
785         return this.script;
786     },
787 
788     /**
789      * Return the variant code for this locale
790      * @return {string|undefined} the variant code of this locale, if any
791      */
792     getVariant: function() {
793         return this.variant;
794     },
795 
796     /**
797      * Return the whole locale specifier as a string.
798      * @return {string} the locale specifier
799      */
800     getSpec: function() {
801         if (!this.spec) this._genSpec();
802         return this.spec;
803     },
804 
805     /**
806      * Return the language locale specifier. This includes the
807      * language and the script if it is available. This can be
808      * used to see whether the written language of two locales
809      * match each other regardless of the region or variant.
810      *
811      * @return {string} the language locale specifier
812      */
813     getLangSpec: function() {
814         var spec = this.language;
815         if (spec && this.script) {
816             spec += "-" + this.script;
817         }
818         return spec || "";
819     },
820 
821     /**
822      * Express this locale object as a string. Currently, this simply calls the getSpec
823      * function to represent the locale as its specifier.
824      *
825      * @return {string} the locale specifier
826      */
827     toString: function() {
828         return this.getSpec();
829     },
830 
831     /**
832      * Return true if the the other locale is exactly equal to the current one.
833      * @return {boolean} whether or not the other locale is equal to the current one
834      */
835     equals: function(other) {
836         return this.language === other.language &&
837             this.region === other.region &&
838             this.script === other.script &&
839             this.variant === other.variant;
840     },
841 
842     /**
843      * Return true if the current locale is the special pseudo locale.
844      * @return {boolean} true if the current locale is the special pseudo locale
845      */
846     isPseudo: function () {
847         return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1;
848     }
849 };
850 
851 // static functions
852 /**
853  * @private
854  */
855 Locale.locales = [];
856 // !macro localelist
857 
858 /**
859  * Return the list of available locales that this iLib file supports.
860  * If this copy of ilib is pre-assembled with locale data, then the
861  * list locales may be much smaller
862  * than the list of all available locales in the iLib repository. The
863  * assembly tool will automatically fill in the list for an assembled
864  * copy of iLib. If this copy is being used with dynamically loaded
865  * data, then you
866  * can load any locale that iLib supports. You can form a locale with any
867  * combination of a language and region tags that exist in the locale
868  * data directory. Language tags are in the root of the locale data dir,
869  * and region tags can be found underneath the "und" directory. (The
870  * region tags are separated into a different dir because the region names
871  * conflict with language names on file systems that are case-insensitive.)
872  * If you have culled the locale data directory to limit the size of
873  * your app, then this function should return only those files that actually exist
874  * according to the ilibmanifest.json file in the root of that locale
875  * data dir. Make sure your ilibmanifest.json file is up-to-date with
876  * respect to the list of files that exist in the locale data dir.
877  *
878  * @param {boolean} sync if false, load the list of available files from disk
879  * asynchronously, otherwise load them synchronously. (Default: true/synchronously)
880  * @param {Function} onLoad a callback function to call if asynchronous
881  * load was requested and the list of files have been loaded.
882  * @return {Array.<string>} this is an array of locale specs for which
883  * this iLib file has locale data for
884  */
885 Locale.getAvailableLocales = function (sync, onLoad) {
886     var locales = [];
887     if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') {
888         locales = Locale.locales;
889         if (onLoad && typeof(onLoad) === 'function') {
890             onLoad(locales);
891         }
892     } else {
893         if (typeof(sync) === 'undefined') {
894             sync = true;
895         }
896         ilib._load.listAvailableFiles(sync, function(manifest) {
897             if (manifest) {
898                 for (var dir in manifest) {
899                     var filelist = manifest[dir];
900                     for (var i = 0; i < filelist.length; i++) {
901                         if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") {
902                             locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-"));
903                         }
904                     }
905                 }
906             }
907             if (onLoad && typeof(onLoad) === 'function') {
908                 onLoad(locales);
909             }
910         });
911     }
912     return locales;
913 };
914 
915 module.exports = Locale;
916