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 }