1 module i18n.culture; 2 3 private { 4 string[] _countryCodes; 5 string[string] _countryNames; 6 7 string[] _languageCodes; 8 string[string] _languageNames; 9 string[string] _languageNativeNames; 10 11 string _getLangCode(string code) { 12 import std.uni : toLower; 13 import std.algorithm.searching : canFind; 14 15 // Impossible case 16 if (code.length < 2) return null; 17 18 // C = no language set 19 if (code == "C") return code; 20 21 // Lang codes are always first, so check the 2 first code digits 22 string codecv = code[0..2].toLower(); 23 if (_languageCodes.canFind(codecv)) return codecv; 24 return null; 25 } 26 27 string _getCountryCode(string code) { 28 import std.string : indexOf; 29 import std.uni : toUpper; 30 import std.algorithm.searching : canFind; 31 32 // Maybe it's just a country code? 33 if (code.length == 2) { 34 if (_countryCodes.canFind(code.toUpper)) return null; 35 36 return code; 37 } 38 39 // Check for the 2 types of seperators 40 ptrdiff_t sep = code.indexOf("_"); 41 if (sep == -1) sep = code.indexOf("-"); 42 if (sep == -1 && code.length == 2) return code; 43 44 // No seperators, no single-country code 45 return null; 46 } 47 } 48 49 /* 50 Initialize internal culture info 51 */ 52 static this() { 53 _countryCodes = [ 54 "AD", "AE", "AF", "AG", "AI", "AL", "AM", "AO", "AQ", "AR", "AS", "AT", 55 "AU", "AW", "AX", "AZ", "BA", "BB", "BD", "BE", "BF", "BG", "BH", "BI", 56 "BJ", "BL", "BM", "BN", "BO", "BQ", "BR", "BS", "BT", "BV", "BW", "BY", 57 "BZ", "CA", "CC", "CD", "CF", "CG", "CH", "CI", "CK", "CL", "CM", "CN", 58 "CO", "CR", "CU", "CV", "CW", "CX", "CY", "CZ", "DE", "DJ", "DK", "DO", 59 "DZ", "EC", "EE", "EG", "EH", "ER", "ES", "ET", "FI", "FJ", "FK", "FM", 60 "FO", "FR", "GA", "GB", "GD", "GE", "GF", "GG", "GH", "GI", "GL", "GM", 61 "GN", "GP", "GQ", "GR", "GS", "GT", "GU", "GW", "GY", "HK", "HM", "HN", 62 "HR", "HT", "HU", "ID", "IE", "IL", "IM", "IN", "IO", "IQ", "IR", "IS", 63 "IT", "JE", "JM", "JO", "JP", "KE", "KG", "KH", "KI", "KM", "KN", "KP", 64 "KR", "KW", "KY", "KZ", "LA", "LB", "LC", "LI", "LK", "LR", "LS", "LT", 65 "LU", "LV", "LY", "MA", "MC", "MD", "ME", "MF", "MG", "MH", "MK", "ML", 66 "MM", "MN", "MO", "MP", "MQ", "MR", "MS", "MT", "MU", "MV", "MW", "MX", 67 "MY", "MZ", "NA", "NC", "NE", "NF", "NG", "NI", "NL", "NO", "NP", "NR", 68 "NU", "NZ", "OM", "PA", "PE", "PF", "PG", "PH", "PK", "PL", "PM", "PN", 69 "PR", "PS", "PT", "PW", "PY", "QA", "RE", "RO", "RS", "RU", "RW", "SA", 70 "SB", "SC", "SD", "SE", "SG", "SH", "SI", "SJ", "SK", "SL", "SM", "SN", 71 "SO", "SR", "SS", "ST", "SV", "SX", "SY", "SZ", "TC", "TD", "TF", "TG", 72 "TG", "TH", "TJ", "TK", "TL", "TM", "TN", "TO", "TR", "TT", "TV", "TW", 73 "TZ", "UA", "UG", "UM", "US", "UY", "UZ", "VA", "VC", "VE", "VG", "VI", 74 "VN", "VU", "WF", "WS", "YE", "YT", "ZA", "ZM", "ZW" 75 ]; 76 77 _countryNames = [ 78 "AD": "Andorra", 79 "AE": "United Arab Emirates", 80 "AF": "Afghanistan", 81 "AG": "Antigua and Barbuda", 82 "AI": "Anguilla", 83 "AL": "Albania", 84 "AM": "Armenia", 85 "AO": "Angola", 86 "AQ": "Antarctica", 87 "AR": "Argentina", 88 "AS": "American Samoa", 89 "AT": "Austria", 90 "AU": "Australia", 91 "AW": "Aruba", 92 "AX": "Aaland Islands", 93 "AZ": "Azerbaijan", 94 "BA": "Bosnia & Herzegovina", 95 "BB": "Barbados", 96 "BD": "Bangladesh", 97 "BE": "Belgium", 98 "BF": "Burkina Faso", 99 "BG": "Bulgaria", 100 "BH": "Bahrain", 101 "BI": "Burundi", 102 "BJ": "Benin", 103 "BL": "Saint Barthelemy", 104 "BM": "Bermuda", 105 "BN": "Brunei Darussalam", 106 "BO": "Bolivia", 107 "BQ": "Bonaire, Sint Eustatius & Saba", 108 "BR": "Brazil", 109 "BS": "Bahamas", 110 "BT": "Bhutan", 111 "BV": "Bouvet Island", 112 "BW": "Botswana", 113 "BY": "Belarus", 114 "BZ": "Belize", 115 "CA": "Canada", 116 "CC": "Cocos Islands", 117 "CD": "Democratic Republic of Congo", 118 "CF": "Central African Republic", 119 "CG": "Congo", 120 "CH": "Switzerland", 121 "CI": "Côte d'Ivoire", 122 "CK": "Cook Islands", 123 "CL": "Chile", 124 "CM": "Cameroon", 125 "CN": "China", 126 "CO": "Colombia", 127 "CR": "Costa Rica", 128 "CU": "Cuba", 129 "CV": "Cape Verde", 130 "CW": "Curaçao", 131 "CX": "Christmas Island", 132 "CY": "Cyprus", 133 "CZ": "Czech Republic", 134 "DE": "Germany", 135 "DJ": "Djibouti", 136 "DK": "Denmark", 137 "DM": "Dominica", 138 "DO": "Dominican Republic", 139 "DZ": "Algeria", 140 "EC": "Ecuador", 141 "EE": "Estonia", 142 "EG": "Egypt", 143 "EH": "Sahrawi Arab Democratic Republic", 144 "ER": "Eritrea", 145 "ES": "Spain", 146 "ET": "Ethiopia", 147 "FI": "Finland", 148 "FJ": "Fiji", 149 "FK": "Falkland Islands (Malvinas)", 150 "FM": "Federated States of Micronesia", 151 "FO": "Faroe Islands", 152 "FR": "France", 153 "GA": "Gabon", 154 "GB": "United Kingdom", 155 "GD": "Grenada", 156 "GE": "Georgia", 157 "GF": "French Guiana", 158 "GG": "Guernsey", 159 "GH": "Ghana", 160 "GI": "Gibraltar", 161 "GL": "Greenland", 162 "GM": "Gambia", 163 "GN": "Guinea", 164 "GP": "Guadaloupe", 165 "GQ": "Equatorial Guinea", 166 "GR": "Greece", 167 "GS": "South Georgia & South Sandwich Islands", 168 "GT": "Guatemala", 169 "GU": "Guam", 170 "GW": "Guinea-Bissau", 171 "GY": "Guyana", 172 "HK": "Hong Kong", 173 "HM": "Heard Island & McDonald Islands", 174 "HN": "Honduras", 175 "HR": "Croatia", 176 "HT": "Haiti", 177 "HU": "Hungary", 178 "ID": "Indonesia", 179 "IE": "Ireland", 180 "IL": "Israel", 181 "IM": "Isle of Man", 182 "IN": "India", 183 "IO": "British Indian Ocean Territory", 184 "IQ": "Iraq", 185 "IR": "Islamic Republic of Iran", 186 "IS": "Iceland", 187 "IT": "Italy", 188 "JE": "Jersey", 189 "JM": "Jamaica", 190 "JO": "Jordan", 191 "JP": "Japan", 192 "KE": "Kenya", 193 "KG": "Kyrgyzstan", 194 "KH": "Cambodia", 195 "KI": "Kiribati", 196 "KM": "Comoros", 197 "KN": "Saint Kitts & Nevis", 198 "KP": "Democratic People's Republic of Korea", 199 "KR": "Republic of Korea", 200 "KW": "Kuwait", 201 "KY": "Cayman Islands", 202 "KZ": "Kazakhstan", 203 "LA": "Lao People's Democratic Republic", 204 "LB": "Lebalon", 205 "LC": "Saint Lucia", 206 "LI": "Lichtenstein", 207 "LK": "Sri Lanka", 208 "LR": "Liberia", 209 "LS": "Lesotho", 210 "LT": "Lithuania", 211 "LU": "Luxembourg", 212 "LV": "Latvia", 213 "LY": "Libya", 214 "MA": "Morocco", 215 "MC": "Monaco", 216 "MD": "Republic of Moldova", 217 "ME": "Montenegro", 218 "MF": "Saint Martin (French part)", 219 "MG": "Madagascar", 220 "MH": "Marshall Islands", 221 "MK": "North Macedonia", 222 "ML": "Mali", 223 "MM": "Myanmar", 224 "MN": "Mongolia", 225 "MO": "Macao", 226 "MP": "Northen Marina Islands", 227 "MQ": "Martinique", 228 "MR": "Mauritania", 229 "MS": "Montserrat", 230 "MT": "Malta", 231 "MU": "Mauritius", 232 "MV": "Maldives", 233 "MW": "Malawi", 234 "MX": "Mexico", 235 "MY": "Malaysia", 236 "MZ": "Mozambique", 237 "NA": "Namibia", 238 "NC": "New Caledonia", 239 "NE": "Niger", 240 "NF": "Norfolk Island", 241 "NG": "Nigeria", 242 "NI": "Nicaragua", 243 "NL": "Netherlands", 244 "NO": "Norway", 245 "NP": "Nepal", 246 "NR": "Nauru", 247 "NU": "Niue", 248 "NZ": "New Zealand", 249 "OM": "Oman", 250 "PA": "Panama", 251 "PE": "Peru", 252 "PF": "French Polynesia", 253 "PG": "Papua New Guinea", 254 "PH": "Phillipines", 255 "PK": "Pakistan", 256 "PL": "Poland", 257 "PM": "Saint Pierre & Miquelon", 258 "PN": "Pitcairn", 259 "PR": "Puerto Rico", 260 "PS": "Palestine", 261 "PT": "Portugal", 262 "PW": "Palau", 263 "PY": "Paraguay", 264 "QA": "Qatar", 265 "RE": "Reunion", 266 "RO": "Romania", 267 "RS": "Serbia", 268 "RU": "Russian Federation", 269 "RW": "Rwanda", 270 "SA": "Saudi Arabia", 271 "SB": "Solomon Islands", 272 "SC": "Seychelles", 273 "SD": "Sudan", 274 "SE": "Sweden", 275 "SG": "Singapore", 276 "SH": "Saint Helena, Ascension & Tristan da Cunha", 277 "SI": "Slovenia", 278 "SJ": "Svalbard & Jan Mayen", 279 "SK": "Slovakia", 280 "SL": "Sierra Leone", 281 "SM": "San Marino", 282 "SN": "Senegal", 283 "SO": "Somalia", 284 "SR": "Suriname", 285 "SS": "South Sudan", 286 "ST": "Sao Tome & Principe", 287 "SV": "El Salvador", 288 "SX": "Sint Maarten (Dutch part)", 289 "SY": "Syrian Arab Republic", 290 "SZ": "Swaziland", 291 "TC": "Turks & Caicos Islands", 292 "TD": "Chad", 293 "TF": "French Southern Territories", 294 "TG": "Togo", 295 "TH": "Thailand", 296 "TJ": "Tajikistan", 297 "TK": "Tokelau", 298 "TL": "Timor-Leste", 299 "TM": "Turkmenistan", 300 "TN": "Tunisia", 301 "TO": "Tonga", 302 "TR": "Turkey", 303 "TT": "Trinidad & Tobago", 304 "TV": "Tuvalu", 305 "TW": "Taiwan", 306 "TZ": "United Republic of Tanzania", 307 "UA": "Ukraine", 308 "UG": "Uganda", 309 "UM": "United States Minor Outlying Islands", 310 "US": "United States", 311 "UY": "Uruguay", 312 "UZ": "Uzbekistan", 313 "VA": "Holy See (Vatican City State)", 314 "VC": "Saint Vincent & Grenadines", 315 "VE": "Bolivarian Republic of Venezuela", 316 "VG": "British Virgin Islands", 317 "VI": "US Virgin Islands", 318 "VN": "Vietnam", 319 "VU": "Vanuatu", 320 "WF": "Wallis & Futuna", 321 "WS": "Samoa", 322 "YE": "Yemen", 323 "YT": "Mayotte", 324 "ZA": "South Africa", 325 "ZM": "Zambia", 326 "ZW": "Zimbabwe" 327 ]; 328 329 _languageCodes = [ 330 "aa", "ab", "ae", "af", "ak", "am", "an", "ar", "as", "av", "ay", "az", 331 "ba", "be", "bg", "bh", "bi", "bm", "bn", "bo", "br", "bs", "ca", "ce", 332 "ch", "co", "cr", "cs", "cu", "cv", "cy", "da", "de", "dv", "dz", "ee", 333 "el", "en", "eo", "es", "et", "eu", "fa", "ff", "fi", "fj", "fo", "fr", 334 "fy", "ga", "gd", "gl", "gn", "gu", "gv", "ha", "he", "hi", "ho", "hr", 335 "ht", "hu", "hy", "hz", "ia", "id", "ie", "ig", "ii", "ik", "io", "is", 336 "it", "iu", "ja", "jv", "ka", "kg", "ki", "kj", "kk", "kl", "km", "kn", 337 "ko", "kr", "ks", "ku", "kv", "kw", "ky", "la", "lb", "lg", "li", "ln", 338 "lo", "lt", "lu", "lv", "mg", "mh", "mi", "mk", "ml", "mn", "mr", "ms", 339 "mt", "my", "na", "nb", "nd", "ne", "ng", "nl", "nn", "no", "nr", "nv", 340 "ny", "oc", "oj", "om", "or", "os", "pa", "pi", "pl", "ps", "pt", "qu", 341 "rm", "rn", "ro", "ru", "rw", "sa", "sc", "sd", "se", "sg", "si", "sk", 342 "sl", "sm", "sn", "so", "sq", "sr", "ss", "st", "su", "sv", "sw", "ta", 343 "te", "tg", "th", "ti", "tk", "tl", "tn", "to", "tr", "ts", "tt", "tw", 344 "ty", "ug", "uk", "ur", "uz", "ve", "vi", "vo", "wa", "wo", "xh", "yi", 345 "yo", "za", "zh", "zu" 346 ]; 347 348 _languageNames = [ 349 "aa": "Afar", 350 "ab": "Abkahzian", 351 "ae": "Avestan", 352 "af": "Afrikaans", 353 "ak": "Akan", 354 "am": "Amharic", 355 "an": "Aragonese", 356 "ar": "Arabic", 357 "as": "Assamese", 358 "av": "Avaric", 359 "ay": "Aymara", 360 "az": "Azerbaijani", 361 "ba": "Bashkir", 362 "be": "Belarusian", 363 "bg": "Bulgarian", 364 "bh": "Bihari", 365 "bi": "Bislama", 366 "bm": "Bambara", 367 "bn": "Bengali", 368 "bo": "Tibetan", 369 "br": "Breton", 370 "bs": "Bosnian", 371 "ca": "Catlan", 372 "ce": "Chechen", 373 "ch": "Chamorro", 374 "co": "Corsican", 375 "cr": "Cree", 376 "cs": "Czech", 377 "cu": "Church Slavic", 378 "cv": "Chuvash", 379 "cy": "Welsh", 380 "da": "Danish", 381 "de": "German", 382 "dv": "Divehi", 383 "dz": "Dzongkha", 384 "ee": "Ewe", 385 "el": "Greek", 386 "en": "English", 387 "eo": "Esperanto", 388 "es": "Spanish", 389 "et": "Estonian", 390 "eu": "Basque", 391 "fa": "Persian", 392 "ff": "Fulah", 393 "fi": "Finnish", 394 "fj": "Fijian", 395 "fo": "Faroese", 396 "fr": "French", 397 "fy": "Western Frisian", 398 "ga": "Irish", 399 "gd": "Gaelic", 400 "gl": "Galacian", 401 "gn": "Guarani", 402 "gu": "Gujarati", 403 "gv": "Manx", 404 "ha": "Hausa", 405 "he": "Hebrew", 406 "hi": "Hindi", 407 "ho": "Hiri Motu", 408 "hr": "Croatian", 409 "ht": "Haitian", 410 "hu": "Hungarian", 411 "hy": "Armenian", 412 "hz": "Herero", 413 "ia": "Interlingua", 414 "id": "Indonesian", 415 "ie": "Interlingue", 416 "ig": "Igbo", 417 "ii": "Sichuan Yi", 418 "ik": "Inupiak", 419 "io": "Ido", 420 "is": "Icelandic", 421 "it": "Italian", 422 "iu": "Inuktitut", 423 "ja": "Japanese", 424 "jv": "Javanese", 425 "ka": "Georgian", 426 "kg": "Kongo", 427 "ki": "Kikuyu", 428 "kj": "Kuanyama", 429 "kk": "Kazakh", 430 "kl": "Greenlandic", 431 "km": "Central Khmer", 432 "kn": "Kannada", 433 "ko": "Korean", 434 "kr": "Kanuri", 435 "ks": "Kashmiri", 436 "ku": "Kurdish", 437 "kv": "Komi", 438 "kw": "Cornish", 439 "ky": "Kirghiz", 440 "la": "Latin", 441 "lb": "Luxembourgish", 442 "lg": "Ganda", 443 "li": "Limburgish", 444 "ln": "Lingala", 445 "lo": "Lao", 446 "lt": "Lithuanian", 447 "lu": "Luba-Katanga", 448 "lv": "Latvian", 449 "mg": "Malagasy", 450 "mh": "Marshallese", 451 "mi": "Maori", 452 "mk": "Macedonian", 453 "ml": "Malayalam", 454 "mn": "Mongolian", 455 "mr": "Marathi", 456 "ms": "Malay", 457 "mt": "Maltese", 458 "my": "Burmese", 459 "na": "Nauru", 460 "nb": "Norwegian Bokmål", 461 "nd": "North Ndebele", 462 "ne": "Nepali", 463 "ng": "Ndonga", 464 "nl": "Dutch", 465 "nn": "Norwegian Nynorsk", 466 "no": "Norwegian", 467 "nr": "South Ndebele", 468 "nv": "Navajo", 469 "ny": "Chichewa", 470 "oc": "Occitan", 471 "oj": "Ojibwa", 472 "om": "Omoro", 473 "or": "Oriya", 474 "os": "Ossetian", 475 "pa": "Panjabi", 476 "pi": "Pali", 477 "pl": "Polish", 478 "ps": "Pushto", 479 "pt": "Portugese", 480 "qu": "Quechua", 481 "rm": "Romansh", 482 "rn": "Rundi", 483 "ro": "Romanian", 484 "ru": "Russian", 485 "rw": "Kinyarwanda", 486 "sa": "Sanskrit", 487 "sc": "Sardinian", 488 "sd": "Sindhi", 489 "se": "Northen Sami", 490 "sg": "Sango", 491 "si": "Sinhala", 492 "sk": "Slovak", 493 "sl": "Slovenian", 494 "sm": "Samoan", 495 "sn": "Shona", 496 "so": "Somali", 497 "sq": "Albanian", 498 "sr": "Serbian", 499 "ss": "Swati", 500 "st": "Southern Sotho", 501 "su": "Sundanese", 502 "sv": "Swedish", 503 "sw": "Swahili", 504 "ta": "Tamil", 505 "te": "Tegulu", 506 "tg": "Tajik", 507 "th": "Thai", 508 "ti": "Tigrinya", 509 "tk": "Turkmen", 510 "tl": "Tagalog", 511 "tn": "Tswana", 512 "to": "Tonga", 513 "tr": "Turkish", 514 "ts": "Tsonga", 515 "tt": "Tatar", 516 "tw": "Twi", 517 "ty": "Tahitian", 518 "ug": "Ughur", 519 "uk": "Ukranian", 520 "ur": "Urdu", 521 "uz": "Uzbek", 522 "ve": "Venda", 523 "vi": "Vietnamese", 524 "vo": "Volapük", 525 "wa": "Walloon", 526 "wo": "Wolof", 527 "xh": "Xhosa", 528 "yi": "Yiddish", 529 "yo": "Yoruba", 530 "za": "Zhuang", 531 "zh": "Chinese", 532 "zu": "Zulu" 533 ]; 534 535 /* 536 Estimated language names for fallback 537 This list is inaccurate and incomplete. 538 */ 539 _languageNativeNames = [ 540 "aa": "Qafaraf", 541 "ab": "Аҧсуа бызшәа", 542 "ae": "𐬛𐬍𐬥 𐬛𐬀𐬠𐬌𐬫𐬭𐬵", 543 "af": "Afrikaans", // No spelling found 544 "ak": "Akan", // No spelling found 545 "am": "ኣማርኛ", 546 "an": "Aragonés", 547 "ar": "عربية يهودية", 548 "as": "অসমীয়া", 549 "av": "Avaric", 550 "ay": "Aymar aru", 551 "az": "Azərbaycan dili", 552 "ba": "Башҡортса", 553 "be": "Беларуская мова", 554 "bg": "Български", 555 "bh": "Bihari", 556 "bi": "Bislama", 557 "bm": "Bamanankan", 558 "bn": "বাংলা", 559 "bo": "ཁམས་སྐད", 560 "br": "Brezhoneg", 561 "bs": "Bosanski", 562 "ca": "Catlan", 563 "ce": "Нохчийн мотт", 564 "ch": "Finuʼ Chamoru", 565 "co": "Corsu", 566 "cr": "Cree", // There's 4 versions of Cree, need to add a way to differentate between versions 567 "cs": "Čeština", 568 "cu": "Church Slavic", 569 "cv": "Чӑвашла", 570 "cy": "Cymraeg", 571 "da": "Dansk", 572 "de": "Deutsch", 573 "dv": "Divehi", // No spelling found 574 "dz": "རྫོང་ཁ་", 575 "ee": "Eʋegbe", 576 "el": "ελληνικά", 577 "en": "English", 578 "eo": "Esperanto", 579 "es": "Español", 580 "et": "Eesti keel", 581 "eu": "Euskara", 582 "fa": "فارسی", 583 "ff": "Fulah", // No spelling found 584 "fi": "Suomi", 585 "fj": "Na vosa vaka-Viti", 586 "fo": "Føroyskt mál", 587 "fr": "Français", 588 "fy": "Frysk", 589 "ga": "Gaeilge", 590 "gd": "Gàidhlig", 591 "gl": "Galacian", // No spelling found 592 "gn": "Avañeʼẽ", 593 "gu": "ગુજરાતી", 594 "gv": "Gaelg", 595 "ha": "هَرْشَن هَوْسَ", 596 "he": "עברית", 597 "hi": "हिन्दी ", 598 "ho": "Hiri Motu", // Is spelled same? 599 "hr": "Hrvatski", 600 "ht": "Kreyòl ayisyen", 601 "hu": "Magyar", 602 "hy": "Հայերէն", 603 "hz": "Otjiherero", 604 "ia": "Interlingua", 605 "id": "Bahasa Indonesia", 606 "ie": "Interlingue", 607 "ig": "Ásụ̀sụ̀ Ìgbò", 608 "ii": "ꆈꌠ꒿", // Not sure whether Nuosuhxop is correct. 609 "ik": "Inupiak", 610 "io": "Ido", 611 "is": "Íslenska", 612 "it": "Italiano", 613 "iu": "ᐃᓄᒃᑎᑐᑦ", 614 "ja": "日本語", 615 "jv": "Basa Jawa", 616 "ka": "ქართული ენა", 617 "kg": "Kikongo", 618 "ki": "Gĩkũyũ", 619 "kj": "Kuanyama", 620 "kk": "Қазақ тілі", 621 "kl": "Kalaallisut", 622 "km": "ភាសាខ្មែរ ", 623 "kn": "ಕನ್ನಡ", 624 "ko": "한국어", 625 "kr": "Kànùrí", 626 "ks": "कॉशुर", // Used first option, is this correct? 627 "ku": "کوردی", // کوردی is one spelling of 3 depending on region, need way to differentiate 628 "kv": "Коми кыв", 629 "kw": "Kernewek", 630 "ky": "Kirghiz", 631 "la": "Lingua Latina", 632 "lb": "Lëtzebuergesch", 633 "lg": "Oluganda", 634 "li": "Limburgs", 635 "ln": "Lingala", 636 "lo": "ພາສາລາວ", 637 "lt": "Lietuvių kalba", 638 "lu": "Luba-Katanga", // No spelling found 639 "lv": "Latviešu valoda", 640 "mg": "Malagasy", 641 "mh": "Kajin M̧ajeļ", // One option of 2 was chosen 642 "mi": "Māori", 643 "mk": "македонски", 644 "ml": "മലയാളം", 645 "mn": "монгол", 646 "mr": "मराठी", 647 "ms": "Bahasa Melayu", 648 "mt": "Malti", 649 "my": "မြန်မာဘာသာ", 650 "na": "Nauru", 651 "nb": "Bokmål", 652 "nd": "isiNdebele", // one of multiple options based on where in africa, chose one which seems to have the most speakers. 653 "ne": "नेपाली", 654 "ng": "Ndonga", 655 "nl": "Nederlands", 656 "nn": "Nynorsk", 657 "no": "Norsk", 658 "nr": "isiNdebele", 659 "nv": "Diné Bizaad", 660 "ny": "Chicheŵa", 661 "oc": "Occitan", // There's multiple options, sticking with the base one 662 "oj": "Ojibwa", // No spelling found 663 "om": "Omoro", // No spelling found 664 "or": "Oriya", // No spelling found 665 "os": "ирон ӕвзаг", 666 "pa": "ਪੰਜਾਬੀ", 667 "pi": "पालि", 668 "pl": "Polski", 669 "ps": "Pushto", // No spelling found 670 "pt": "Português", 671 "qu": "Kechua", 672 "rm": "Rumantsch", 673 "rn": "íkiRǔndi", 674 "ro": "Vlășéște ", 675 "ru": "Русский язык", 676 "rw": "Ikinyarwanda", 677 "sa": "संस्कृत", // Multiple options, chose first one 678 "sc": "Sardu", 679 "sd": "سنڌي", // Spoken in multiple places with their own local spelling. 680 "se": "Davvisámegiella", 681 "sg": "Yângâ tî sängö", 682 "si": "සිංහල", 683 "sk": "Slovenčina", 684 "sl": "Slovenščina", 685 "sm": "Gagana fa‘a Sāmoa", 686 "sn": "chiShona", 687 "so": "af Soomaali", 688 "sq": "Shqip", 689 "sr": "српски", 690 "ss": "siSwati", 691 "st": "seSotho", 692 "su": "Basa Sunda", 693 "sv": "Svenska", 694 "sw": "Kiswahili ", 695 "ta": "தமிழ்", 696 "te": "Tegulu", // No spelling found 697 "tg": "тоҷикӣ", 698 "th": "ภาษาไทย", 699 "ti": "ትግርኛ", 700 "tk": "Türkmen dili", 701 "tl": "Tagalog", // Is same 702 "tn": "Setswana", 703 "to": "Chitonga", 704 "tr": "Türkçe", 705 "ts": "Xitsonga", 706 "tt": "Tatarça", 707 "tw": "Twi", // Is same 708 "ty": "Reo Tahiti", 709 "ug": "ئۇيغۇر", // Found via Wikipedia 710 "uk": "украї́нська мо́ва", 711 "ur": "اُردُو", 712 "uz": "Ўзбек тили", 713 "ve": "Tshivenḓa", // 2 options, chose first one 714 "vi": "Tiếng Việt", 715 "vo": "Volapük", 716 "wa": "Walon", 717 "wo": "Wolof", // Is same ? 718 "xh": "isiXhosa", 719 "yi": "ייִדיש", 720 "yo": "Èdè Yorùbá", 721 "za": "Vahcuengh", 722 "zh": "中文", // There's incredibly many forms of chinese, this doesn't cover them all 723 "zu": "isiZulu" 724 ]; 725 726 // Set to user preferred locale 727 i18nResetLocale(); 728 } 729 730 /** 731 Validates a country code 732 733 Returns true if the code is a valid culture code 734 Returns false if the code is not a valid culture code 735 736 TODO: Validate whether encoding spec is correct 737 */ 738 bool i18nValidateCultureCode(bool caseSensitive = true)(string code) { 739 740 // Special case, C = no language set. 741 if (code == "C") return true; 742 743 // Make sure we don't crash when handling lang/country get 744 // by escaping early if we have too few characters to work with 745 if (code.length < 2) return false; 746 747 import std.uni : toUpper, toLower; 748 import std.algorithm.searching : canFind; 749 string lang = code[0..2]; 750 string country = code.length >= 5 ? code[3..5] : ""; 751 752 if (code.length == 2) { 753 // Language code only 754 755 // The main validity test 756 static if (!caseSensitive) return _languageCodes.canFind(lang.toLower); 757 else return _languageCodes.canFind(lang); 758 759 } else if (code.length >= 5) { 760 // Country AND language code 761 762 // Make sure we either use a MS or gettext seperator 763 if (!(code[2] == '-' || code[2] == '_')) return false; 764 765 // The main validity test 766 static if (!caseSensitive) return _languageCodes.canFind(lang.toLower) && _countryCodes.canFind(country.toUpper); 767 else return _languageCodes.canFind(lang) && _countryCodes.canFind(country); 768 769 } 770 return false; 771 } 772 773 @("i18nValidateCultureCode Case Sensitive") 774 unittest { 775 assert(i18nValidateCultureCode("da")); // Should succeed 776 assert(i18nValidateCultureCode("da_DK")); // Should succeed 777 assert(i18nValidateCultureCode("de-DE")); // Should succeed 778 779 assert(!i18nValidateCultureCode("daDK")); // Should fail 780 assert(!i18nValidateCultureCode("daaaaaDK")); // Should fail 781 assert(!i18nValidateCultureCode("da/DK")); // Should fail 782 assert(!i18nValidateCultureCode("zz_CH")); // Should fail 783 assert(!i18nValidateCultureCode("ch_ZZ")); // Should fail 784 } 785 786 @("i18nValidateCultureCode Case Insensitive") 787 unittest { 788 assert(i18nValidateCultureCode!false("da")); // Should succeed 789 assert(i18nValidateCultureCode!false("da_dk")); // Should succeed 790 assert(i18nValidateCultureCode!false("da_DK")); // Should succeed 791 assert(i18nValidateCultureCode!false("en-US")); // Should succeed 792 assert(i18nValidateCultureCode!false("en-us")); // Should succeed 793 794 assert(!i18nValidateCultureCode!false("daDK")); // Should fail 795 assert(!i18nValidateCultureCode!false("daaaaaDK")); // Should fail 796 assert(!i18nValidateCultureCode!false("da/DK")); // Should fail 797 assert(!i18nValidateCultureCode!false("zz_CH")); // Should fail 798 assert(!i18nValidateCultureCode!false("ch_ZZ")); // Should fail 799 } 800 801 /** 802 Gets the (english) name of the language associated with a culture code 803 804 Returns null if code is invalid 805 */ 806 string i18nGetCultureLanguage(string code) { 807 if (!i18nValidateCultureCode(code)) return null; 808 if (code[0..2] !in _languageNames) return null; 809 return _languageNames[code[0..2]]; 810 } 811 812 @("i18nGetCultureLanguage") 813 unittest { 814 assert(i18nGetCultureLanguage("da") == "Danish"); 815 assert(i18nGetCultureLanguage("de") == "German"); 816 assert(i18nGetCultureLanguage("en") == "English"); 817 assert(i18nGetCultureLanguage("ja") == "Japanese"); 818 } 819 820 /** 821 Attempts to get the name of the language associated with a culture code. 822 This may be inaccurate due to things such as multiple versions of a language existing. 823 824 ONLY USE THIS AS A FALLBACK. 825 826 Returns null if code is invalid 827 */ 828 string i18nGetCultureNativeLanguageEstimate(string code) { 829 if (!i18nValidateCultureCode(code)) return null; 830 if (code[0..2] !in _languageNativeNames) return null; 831 return _languageNativeNames[code[0..2]]; 832 } 833 834 @("i18nGetCultureNativeLanguageEstimate") 835 unittest { 836 assert(i18nGetCultureLanguage("da") == "Dansk"); 837 assert(i18nGetCultureLanguage("de") == "Deutsch"); 838 assert(i18nGetCultureLanguage("en") == "English"); 839 assert(i18nGetCultureLanguage("ja") == "日本語"); 840 } 841 842 /** 843 Gets the country associated with a culture code 844 */ 845 string i18nGetCultureCountry(string code) { 846 if (!i18nValidateCultureCode(code)) return null; 847 if (code.length < 5) return null; 848 if (code[$-2..5] !in _countryNames) return null; 849 return _countryNames[code[5-2..5]]; 850 } 851 852 @("i18nGetCultureCountry") 853 unittest { 854 assert(i18nGetCultureCountry("da_DK") == "Denmark"); 855 assert(i18nGetCultureCountry("en_US") == "United States"); 856 assert(i18nGetCultureCountry("en_GB") == "United Kingdom"); 857 assert(i18nGetCultureCountry("de_DE") == "Germany"); 858 assert(i18nGetCultureCountry("ja_JP") == "Japan"); 859 } 860 861 /** 862 Gets the current locale. 863 */ 864 string i18nGetLocale() { 865 import core.stdc.locale : setlocale, LC_ALL; 866 import std.string : fromStringz; 867 string locale = cast(string)setlocale(LC_ALL, null).fromStringz.idup; 868 869 // TODO: Implement 870 return locale; 871 } 872 873 @("i18nGetLocale") 874 unittest { 875 assert(i18nValidateCultureCode(i18nGetLocale())); 876 } 877 878 /** 879 Sets the locale for the app. 880 */ 881 void i18nSetLocale(string locale) { 882 import core.stdc.locale : setlocale, LC_ALL; 883 import core.stdc.string : memcpy; 884 import core.stdc.stdlib : malloc; 885 import std.string : toStringz, fromStringz; 886 887 if (locale == "C") { 888 setlocale(LC_ALL, "C"); 889 } else if (locale == "") { 890 setlocale(LC_ALL, ""); 891 } else setlocale(LC_ALL, (locale).toStringz); 892 } 893 894 void i18nResetLocale() { 895 import core.stdc.locale : setlocale, LC_ALL; 896 setlocale(LC_ALL, ""); 897 } 898 899 struct LocaleConv { 900 string decimalPoint; 901 string thousandSep; 902 string grouping; 903 string intCurrSymbol; 904 string currencySymbol; 905 string monDecimalPoint; 906 string monThousandsSep; 907 string monGrouping; 908 string positiveSign; 909 string negativeSign; 910 byte intFracDigits; 911 byte fracDigits; 912 byte pCSPrecedes; 913 byte pSepBySpace; 914 byte nCSPrecedes; 915 byte nSepBySpace; 916 byte pSignPosN; 917 byte nSignPosN; 918 byte intPCSPrecedes; 919 byte intP_sep_by_space; 920 byte intNCSPrecedes; 921 byte intNSepBySpace; 922 byte intPSignPosN; 923 byte intNSignPosN; 924 } 925 926 /** 927 Returns the locale's conversion units. 928 */ 929 LocaleConv i18nGetLocaleConversions() { 930 import core.stdc.locale : localeconv, lconv; 931 import std.string : fromStringz; 932 lconv* conv = localeconv(); 933 return LocaleConv( 934 cast(string)conv.decimal_point.fromStringz, 935 cast(string)conv.thousands_sep.fromStringz, 936 cast(string)conv.grouping.fromStringz, 937 cast(string)conv.int_curr_symbol.fromStringz, 938 cast(string)conv.currency_symbol.fromStringz, 939 cast(string)conv.mon_decimal_point.fromStringz, 940 cast(string)conv.mon_thousands_sep.fromStringz, 941 cast(string)conv.mon_grouping.fromStringz, 942 cast(string)conv.positive_sign.fromStringz, 943 cast(string)conv.negative_sign.fromStringz, 944 conv.int_frac_digits, 945 conv.frac_digits, 946 conv.p_cs_precedes, 947 conv.p_sep_by_space, 948 conv.n_cs_precedes, 949 conv.n_sep_by_space, 950 conv.p_sign_posn, 951 conv.n_sign_posn, 952 conv.int_p_cs_precedes, 953 conv.int_p_sep_by_space, 954 conv.int_n_cs_precedes, 955 conv.int_n_sep_by_space, 956 conv.int_p_sign_posn, 957 conv.int_n_sign_posn 958 ); 959 }