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