diff options
Diffstat (limited to 'core/string/translation.cpp')
-rw-r--r-- | core/string/translation.cpp | 1227 |
1 files changed, 298 insertions, 929 deletions
diff --git a/core/string/translation.cpp b/core/string/translation.cpp index 615ecf2432..5d7e583de1 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -33,793 +33,12 @@ #include "core/config/project_settings.h" #include "core/io/resource_loader.h" #include "core/os/os.h" +#include "core/string/locales.h" #ifdef TOOLS_ENABLED #include "main/main.h" #endif -// ISO 639-1 language codes (and a couple of three-letter ISO 639-2 codes), -// with the addition of glibc locales with their regional identifiers. -// This list must match the language names (in English) of locale_names. -// -// References: -// - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes -// - https://lh.2xlibre.net/locales/ -// - https://iso639-3.sil.org/ - -static const char *locale_list[] = { - "aa", // Afar - "aa_DJ", // Afar (Djibouti) - "aa_ER", // Afar (Eritrea) - "aa_ET", // Afar (Ethiopia) - "af", // Afrikaans - "af_ZA", // Afrikaans (South Africa) - "agr_PE", // Aguaruna (Peru) - "ak_GH", // Akan (Ghana) - "am_ET", // Amharic (Ethiopia) - "an_ES", // Aragonese (Spain) - "anp_IN", // Angika (India) - "ar", // Arabic - "ar_AE", // Arabic (United Arab Emirates) - "ar_BH", // Arabic (Bahrain) - "ar_DZ", // Arabic (Algeria) - "ar_EG", // Arabic (Egypt) - "ar_IN", // Arabic (India) - "ar_IQ", // Arabic (Iraq) - "ar_JO", // Arabic (Jordan) - "ar_KW", // Arabic (Kuwait) - "ar_LB", // Arabic (Lebanon) - "ar_LY", // Arabic (Libya) - "ar_MA", // Arabic (Morocco) - "ar_OM", // Arabic (Oman) - "ar_QA", // Arabic (Qatar) - "ar_SA", // Arabic (Saudi Arabia) - "ar_SD", // Arabic (Sudan) - "ar_SS", // Arabic (South Soudan) - "ar_SY", // Arabic (Syria) - "ar_TN", // Arabic (Tunisia) - "ar_YE", // Arabic (Yemen) - "as_IN", // Assamese (India) - "ast_ES", // Asturian (Spain) - "ayc_PE", // Southern Aymara (Peru) - "ay_PE", // Aymara (Peru) - "az", // Azerbaijani - "az_AZ", // Azerbaijani (Azerbaijan) - "be", // Belarusian - "be_BY", // Belarusian (Belarus) - "bem_ZM", // Bemba (Zambia) - "ber_DZ", // Berber languages (Algeria) - "ber_MA", // Berber languages (Morocco) - "bg", // Bulgarian - "bg_BG", // Bulgarian (Bulgaria) - "bhb_IN", // Bhili (India) - "bho_IN", // Bhojpuri (India) - "bi_TV", // Bislama (Tuvalu) - "bn", // Bengali - "bn_BD", // Bengali (Bangladesh) - "bn_IN", // Bengali (India) - "bo", // Tibetan - "bo_CN", // Tibetan (China) - "bo_IN", // Tibetan (India) - "br", // Breton - "br_FR", // Breton (France) - "brx_IN", // Bodo (India) - "bs_BA", // Bosnian (Bosnia and Herzegovina) - "byn_ER", // Bilin (Eritrea) - "ca", // Catalan - "ca_AD", // Catalan (Andorra) - "ca_ES", // Catalan (Spain) - "ca_FR", // Catalan (France) - "ca_IT", // Catalan (Italy) - "ce_RU", // Chechen (Russia) - "chr_US", // Cherokee (United States) - "cmn_TW", // Mandarin Chinese (Taiwan) - "crh_UA", // Crimean Tatar (Ukraine) - "csb_PL", // Kashubian (Poland) - "cs", // Czech - "cs_CZ", // Czech (Czech Republic) - "cv_RU", // Chuvash (Russia) - "cy_GB", // Welsh (United Kingdom) - "da", // Danish - "da_DK", // Danish (Denmark) - "de", // German - "de_AT", // German (Austria) - "de_BE", // German (Belgium) - "de_CH", // German (Switzerland) - "de_DE", // German (Germany) - "de_IT", // German (Italy) - "de_LU", // German (Luxembourg) - "doi_IN", // Dogri (India) - "dv_MV", // Dhivehi (Maldives) - "dz_BT", // Dzongkha (Bhutan) - "el", // Greek - "el_CY", // Greek (Cyprus) - "el_GR", // Greek (Greece) - "en", // English - "en_AG", // English (Antigua and Barbuda) - "en_AU", // English (Australia) - "en_BW", // English (Botswana) - "en_CA", // English (Canada) - "en_DK", // English (Denmark) - "en_GB", // English (United Kingdom) - "en_HK", // English (Hong Kong) - "en_IE", // English (Ireland) - "en_IL", // English (Israel) - "en_IN", // English (India) - "en_NG", // English (Nigeria) - "en_NZ", // English (New Zealand) - "en_PH", // English (Philippines) - "en_SG", // English (Singapore) - "en_US", // English (United States) - "en_ZA", // English (South Africa) - "en_ZM", // English (Zambia) - "en_ZW", // English (Zimbabwe) - "eo", // Esperanto - "es", // Spanish - "es_AR", // Spanish (Argentina) - "es_BO", // Spanish (Bolivia) - "es_CL", // Spanish (Chile) - "es_CO", // Spanish (Colombia) - "es_CR", // Spanish (Costa Rica) - "es_CU", // Spanish (Cuba) - "es_DO", // Spanish (Dominican Republic) - "es_EC", // Spanish (Ecuador) - "es_ES", // Spanish (Spain) - "es_GT", // Spanish (Guatemala) - "es_HN", // Spanish (Honduras) - "es_MX", // Spanish (Mexico) - "es_NI", // Spanish (Nicaragua) - "es_PA", // Spanish (Panama) - "es_PE", // Spanish (Peru) - "es_PR", // Spanish (Puerto Rico) - "es_PY", // Spanish (Paraguay) - "es_SV", // Spanish (El Salvador) - "es_US", // Spanish (United States) - "es_UY", // Spanish (Uruguay) - "es_VE", // Spanish (Venezuela) - "et", // Estonian - "et_EE", // Estonian (Estonia) - "eu", // Basque - "eu_ES", // Basque (Spain) - "fa", // Persian - "fa_IR", // Persian (Iran) - "ff_SN", // Fulah (Senegal) - "fi", // Finnish - "fi_FI", // Finnish (Finland) - "fil", // Filipino - "fil_PH", // Filipino (Philippines) - "fo_FO", // Faroese (Faroe Islands) - "fr", // French - "fr_BE", // French (Belgium) - "fr_CA", // French (Canada) - "fr_CH", // French (Switzerland) - "fr_FR", // French (France) - "fr_LU", // French (Luxembourg) - "fur_IT", // Friulian (Italy) - "fy_DE", // Western Frisian (Germany) - "fy_NL", // Western Frisian (Netherlands) - "ga", // Irish - "ga_IE", // Irish (Ireland) - "gd_GB", // Scottish Gaelic (United Kingdom) - "gez_ER", // Geez (Eritrea) - "gez_ET", // Geez (Ethiopia) - "gl", // Galician - "gl_ES", // Galician (Spain) - "gu_IN", // Gujarati (India) - "gv_GB", // Manx (United Kingdom) - "hak_TW", // Hakka Chinese (Taiwan) - "ha_NG", // Hausa (Nigeria) - "he", // Hebrew - "he_IL", // Hebrew (Israel) - "hi", // Hindi - "hi_IN", // Hindi (India) - "hne_IN", // Chhattisgarhi (India) - "hr", // Croatian - "hr_HR", // Croatian (Croatia) - "hsb_DE", // Upper Sorbian (Germany) - "ht_HT", // Haitian (Haiti) - "hu", // Hungarian - "hu_HU", // Hungarian (Hungary) - "hus_MX", // Huastec (Mexico) - "hy_AM", // Armenian (Armenia) - "ia_FR", // Interlingua (France) - "id", // Indonesian - "id_ID", // Indonesian (Indonesia) - "ig_NG", // Igbo (Nigeria) - "ik_CA", // Inupiaq (Canada) - "is", // Icelandic - "is_IS", // Icelandic (Iceland) - "it", // Italian - "it_CH", // Italian (Switzerland) - "it_IT", // Italian (Italy) - "iu_CA", // Inuktitut (Canada) - "ja", // Japanese - "ja_JP", // Japanese (Japan) - "kab_DZ", // Kabyle (Algeria) - "ka", // Georgian - "ka_GE", // Georgian (Georgia) - "kk_KZ", // Kazakh (Kazakhstan) - "kl_GL", // Kalaallisut (Greenland) - "km", // Central Khmer - "km_KH", // Central Khmer (Cambodia) - "kn_IN", // Kannada (India) - "kok_IN", // Konkani (India) - "ko", // Korean - "ko_KR", // Korean (South Korea) - "ks_IN", // Kashmiri (India) - "ku", // Kurdish - "ku_TR", // Kurdish (Turkey) - "kw_GB", // Cornish (United Kingdom) - "ky_KG", // Kirghiz (Kyrgyzstan) - "lb_LU", // Luxembourgish (Luxembourg) - "lg_UG", // Ganda (Uganda) - "li_BE", // Limburgan (Belgium) - "li_NL", // Limburgan (Netherlands) - "lij_IT", // Ligurian (Italy) - "ln_CD", // Lingala (Congo) - "lo_LA", // Lao (Laos) - "lt", // Lithuanian - "lt_LT", // Lithuanian (Lithuania) - "lv", // Latvian - "lv_LV", // Latvian (Latvia) - "lzh_TW", // Literary Chinese (Taiwan) - "mag_IN", // Magahi (India) - "mai_IN", // Maithili (India) - "mg_MG", // Malagasy (Madagascar) - "mh_MH", // Marshallese (Marshall Islands) - "mhr_RU", // Eastern Mari (Russia) - "mi", // Māori - "mi_NZ", // Māori (New Zealand) - "miq_NI", // Mískito (Nicaragua) - "mk", // Macedonian - "mk_MK", // Macedonian (Macedonia) - "ml", // Malayalam - "ml_IN", // Malayalam (India) - "mni_IN", // Manipuri (India) - "mn_MN", // Mongolian (Mongolia) - "mr", // Marathi - "mr_IN", // Marathi (India) - "ms", // Malay - "ms_MY", // Malay (Malaysia) - "mt", // Maltese - "mt_MT", // Maltese (Malta) - "my_MM", // Burmese (Myanmar) - "myv_RU", // Erzya (Russia) - "nah_MX", // Nahuatl languages (Mexico) - "nan_TW", // Min Nan Chinese (Taiwan) - "nb", // Norwegian Bokmål - "nb_NO", // Norwegian Bokmål (Norway) - "nds_DE", // Low German (Germany) - "nds_NL", // Low German (Netherlands) - "ne_NP", // Nepali (Nepal) - "nhn_MX", // Central Nahuatl (Mexico) - "niu_NU", // Niuean (Niue) - "niu_NZ", // Niuean (New Zealand) - "nl", // Dutch - "nl_AW", // Dutch (Aruba) - "nl_BE", // Dutch (Belgium) - "nl_NL", // Dutch (Netherlands) - "nn", // Norwegian Nynorsk - "nn_NO", // Norwegian Nynorsk (Norway) - "nr_ZA", // South Ndebele (South Africa) - "nso_ZA", // Pedi (South Africa) - "oc_FR", // Occitan (France) - "om", // Oromo - "om_ET", // Oromo (Ethiopia) - "om_KE", // Oromo (Kenya) - "or", // Oriya - "or_IN", // Oriya (India) - "os_RU", // Ossetian (Russia) - "pa_IN", // Panjabi (India) - "pap", // Papiamento - "pap_AN", // Papiamento (Netherlands Antilles) - "pap_AW", // Papiamento (Aruba) - "pap_CW", // Papiamento (Curaçao) - "pa_PK", // Panjabi (Pakistan) - "pl", // Polish - "pl_PL", // Polish (Poland) - "pr", // Pirate - "ps_AF", // Pushto (Afghanistan) - "pt", // Portuguese - "pt_BR", // Portuguese (Brazil) - "pt_PT", // Portuguese (Portugal) - "quy_PE", // Ayacucho Quechua (Peru) - "quz_PE", // Cusco Quechua (Peru) - "raj_IN", // Rajasthani (India) - "ro", // Romanian - "ro_RO", // Romanian (Romania) - "ru", // Russian - "ru_RU", // Russian (Russia) - "ru_UA", // Russian (Ukraine) - "rw_RW", // Kinyarwanda (Rwanda) - "sa_IN", // Sanskrit (India) - "sat_IN", // Santali (India) - "sc_IT", // Sardinian (Italy) - "sco", // Scots - "sd_IN", // Sindhi (India) - "se_NO", // Northern Sami (Norway) - "sgs_LT", // Samogitian (Lithuania) - "shs_CA", // Shuswap (Canada) - "sid_ET", // Sidamo (Ethiopia) - "si", // Sinhala - "si_LK", // Sinhala (Sri Lanka) - "sk", // Slovak - "sk_SK", // Slovak (Slovakia) - "sl", // Slovenian - "sl_SI", // Slovenian (Slovenia) - "so", // Somali - "so_DJ", // Somali (Djibouti) - "so_ET", // Somali (Ethiopia) - "so_KE", // Somali (Kenya) - "so_SO", // Somali (Somalia) - "son_ML", // Songhai languages (Mali) - "sq", // Albanian - "sq_AL", // Albanian (Albania) - "sq_KV", // Albanian (Kosovo) - "sq_MK", // Albanian (Macedonia) - "sr", // Serbian - "sr_Cyrl", // Serbian (Cyrillic) - "sr_Latn", // Serbian (Latin) - "sr_ME", // Serbian (Montenegro) - "sr_RS", // Serbian (Serbia) - "ss_ZA", // Swati (South Africa) - "st_ZA", // Southern Sotho (South Africa) - "sv", // Swedish - "sv_FI", // Swedish (Finland) - "sv_SE", // Swedish (Sweden) - "sw_KE", // Swahili (Kenya) - "sw_TZ", // Swahili (Tanzania) - "szl_PL", // Silesian (Poland) - "ta", // Tamil - "ta_IN", // Tamil (India) - "ta_LK", // Tamil (Sri Lanka) - "tcy_IN", // Tulu (India) - "te", // Telugu - "te_IN", // Telugu (India) - "tg_TJ", // Tajik (Tajikistan) - "the_NP", // Chitwania Tharu (Nepal) - "th", // Thai - "th_TH", // Thai (Thailand) - "ti", // Tigrinya - "ti_ER", // Tigrinya (Eritrea) - "ti_ET", // Tigrinya (Ethiopia) - "tig_ER", // Tigre (Eritrea) - "tk_TM", // Turkmen (Turkmenistan) - "tl_PH", // Tagalog (Philippines) - "tn_ZA", // Tswana (South Africa) - "tr", // Turkish - "tr_CY", // Turkish (Cyprus) - "tr_TR", // Turkish (Turkey) - "ts_ZA", // Tsonga (South Africa) - "tt", // Tatar - "tt_RU", // Tatar (Russia) - "tzm", // Central Atlas Tamazight - "tzm_MA", // Central Atlas Tamazight (Marrocos) - "ug_CN", // Uighur (China) - "uk", // Ukrainian - "uk_UA", // Ukrainian (Ukraine) - "unm_US", // Unami (United States) - "ur", // Urdu - "ur_IN", // Urdu (India) - "ur_PK", // Urdu (Pakistan) - "uz", // Uzbek - "uz_UZ", // Uzbek (Uzbekistan) - "ve_ZA", // Venda (South Africa) - "vi", // Vietnamese - "vi_VN", // Vietnamese (Vietnam) - "wa_BE", // Walloon (Belgium) - "wae_CH", // Walser (Switzerland) - "wal_ET", // Wolaytta (Ethiopia) - "wo_SN", // Wolof (Senegal) - "xh_ZA", // Xhosa (South Africa) - "yi_US", // Yiddish (United States) - "yo_NG", // Yoruba (Nigeria) - "yue_HK", // Yue Chinese (Hong Kong) - "zh", // Chinese - "zh_CN", // Chinese (China) - "zh_HK", // Chinese (Hong Kong) - "zh_SG", // Chinese (Singapore) - "zh_TW", // Chinese (Taiwan) - "zu_ZA", // Zulu (South Africa) - nullptr -}; - -static const char *locale_names[] = { - "Afar", - "Afar (Djibouti)", - "Afar (Eritrea)", - "Afar (Ethiopia)", - "Afrikaans", - "Afrikaans (South Africa)", - "Aguaruna (Peru)", - "Akan (Ghana)", - "Amharic (Ethiopia)", - "Aragonese (Spain)", - "Angika (India)", - "Arabic", - "Arabic (United Arab Emirates)", - "Arabic (Bahrain)", - "Arabic (Algeria)", - "Arabic (Egypt)", - "Arabic (India)", - "Arabic (Iraq)", - "Arabic (Jordan)", - "Arabic (Kuwait)", - "Arabic (Lebanon)", - "Arabic (Libya)", - "Arabic (Morocco)", - "Arabic (Oman)", - "Arabic (Qatar)", - "Arabic (Saudi Arabia)", - "Arabic (Sudan)", - "Arabic (South Soudan)", - "Arabic (Syria)", - "Arabic (Tunisia)", - "Arabic (Yemen)", - "Assamese (India)", - "Asturian (Spain)", - "Southern Aymara (Peru)", - "Aymara (Peru)", - "Azerbaijani", - "Azerbaijani (Azerbaijan)", - "Belarusian", - "Belarusian (Belarus)", - "Bemba (Zambia)", - "Berber languages (Algeria)", - "Berber languages (Morocco)", - "Bulgarian", - "Bulgarian (Bulgaria)", - "Bhili (India)", - "Bhojpuri (India)", - "Bislama (Tuvalu)", - "Bengali", - "Bengali (Bangladesh)", - "Bengali (India)", - "Tibetan", - "Tibetan (China)", - "Tibetan (India)", - "Breton", - "Breton (France)", - "Bodo (India)", - "Bosnian (Bosnia and Herzegovina)", - "Bilin (Eritrea)", - "Catalan", - "Catalan (Andorra)", - "Catalan (Spain)", - "Catalan (France)", - "Catalan (Italy)", - "Chechen (Russia)", - "Cherokee (United States)", - "Mandarin Chinese (Taiwan)", - "Crimean Tatar (Ukraine)", - "Kashubian (Poland)", - "Czech", - "Czech (Czech Republic)", - "Chuvash (Russia)", - "Welsh (United Kingdom)", - "Danish", - "Danish (Denmark)", - "German", - "German (Austria)", - "German (Belgium)", - "German (Switzerland)", - "German (Germany)", - "German (Italy)", - "German (Luxembourg)", - "Dogri (India)", - "Dhivehi (Maldives)", - "Dzongkha (Bhutan)", - "Greek", - "Greek (Cyprus)", - "Greek (Greece)", - "English", - "English (Antigua and Barbuda)", - "English (Australia)", - "English (Botswana)", - "English (Canada)", - "English (Denmark)", - "English (United Kingdom)", - "English (Hong Kong)", - "English (Ireland)", - "English (Israel)", - "English (India)", - "English (Nigeria)", - "English (New Zealand)", - "English (Philippines)", - "English (Singapore)", - "English (United States)", - "English (South Africa)", - "English (Zambia)", - "English (Zimbabwe)", - "Esperanto", - "Spanish", - "Spanish (Argentina)", - "Spanish (Bolivia)", - "Spanish (Chile)", - "Spanish (Colombia)", - "Spanish (Costa Rica)", - "Spanish (Cuba)", - "Spanish (Dominican Republic)", - "Spanish (Ecuador)", - "Spanish (Spain)", - "Spanish (Guatemala)", - "Spanish (Honduras)", - "Spanish (Mexico)", - "Spanish (Nicaragua)", - "Spanish (Panama)", - "Spanish (Peru)", - "Spanish (Puerto Rico)", - "Spanish (Paraguay)", - "Spanish (El Salvador)", - "Spanish (United States)", - "Spanish (Uruguay)", - "Spanish (Venezuela)", - "Estonian", - "Estonian (Estonia)", - "Basque", - "Basque (Spain)", - "Persian", - "Persian (Iran)", - "Fulah (Senegal)", - "Finnish", - "Finnish (Finland)", - "Filipino", - "Filipino (Philippines)", - "Faroese (Faroe Islands)", - "French", - "French (Belgium)", - "French (Canada)", - "French (Switzerland)", - "French (France)", - "French (Luxembourg)", - "Friulian (Italy)", - "Western Frisian (Germany)", - "Western Frisian (Netherlands)", - "Irish", - "Irish (Ireland)", - "Scottish Gaelic (United Kingdom)", - "Geez (Eritrea)", - "Geez (Ethiopia)", - "Galician", - "Galician (Spain)", - "Gujarati (India)", - "Manx (United Kingdom)", - "Hakka Chinese (Taiwan)", - "Hausa (Nigeria)", - "Hebrew", - "Hebrew (Israel)", - "Hindi", - "Hindi (India)", - "Chhattisgarhi (India)", - "Croatian", - "Croatian (Croatia)", - "Upper Sorbian (Germany)", - "Haitian (Haiti)", - "Hungarian", - "Hungarian (Hungary)", - "Huastec (Mexico)", - "Armenian (Armenia)", - "Interlingua (France)", - "Indonesian", - "Indonesian (Indonesia)", - "Igbo (Nigeria)", - "Inupiaq (Canada)", - "Icelandic", - "Icelandic (Iceland)", - "Italian", - "Italian (Switzerland)", - "Italian (Italy)", - "Inuktitut (Canada)", - "Japanese", - "Japanese (Japan)", - "Kabyle (Algeria)", - "Georgian", - "Georgian (Georgia)", - "Kazakh (Kazakhstan)", - "Kalaallisut (Greenland)", - "Central Khmer", - "Central Khmer (Cambodia)", - "Kannada (India)", - "Konkani (India)", - "Korean", - "Korean (South Korea)", - "Kashmiri (India)", - "Kurdish", - "Kurdish (Turkey)", - "Cornish (United Kingdom)", - "Kirghiz (Kyrgyzstan)", - "Luxembourgish (Luxembourg)", - "Ganda (Uganda)", - "Limburgan (Belgium)", - "Limburgan (Netherlands)", - "Ligurian (Italy)", - "Lingala (Congo)", - "Lao (Laos)", - "Lithuanian", - "Lithuanian (Lithuania)", - "Latvian", - "Latvian (Latvia)", - "Literary Chinese (Taiwan)", - "Magahi (India)", - "Maithili (India)", - "Malagasy (Madagascar)", - "Marshallese (Marshall Islands)", - "Eastern Mari (Russia)", - "Māori", - "Māori (New Zealand)", - "Mískito (Nicaragua)", - "Macedonian", - "Macedonian (Macedonia)", - "Malayalam", - "Malayalam (India)", - "Manipuri (India)", - "Mongolian (Mongolia)", - "Marathi", - "Marathi (India)", - "Malay", - "Malay (Malaysia)", - "Maltese", - "Maltese (Malta)", - "Burmese (Myanmar)", - "Erzya (Russia)", - "Nahuatl languages (Mexico)", - "Min Nan Chinese (Taiwan)", - "Norwegian Bokmål", - "Norwegian Bokmål (Norway)", - "Low German (Germany)", - "Low German (Netherlands)", - "Nepali (Nepal)", - "Central Nahuatl (Mexico)", - "Niuean (Niue)", - "Niuean (New Zealand)", - "Dutch", - "Dutch (Aruba)", - "Dutch (Belgium)", - "Dutch (Netherlands)", - "Norwegian Nynorsk", - "Norwegian Nynorsk (Norway)", - "South Ndebele (South Africa)", - "Pedi (South Africa)", - "Occitan (France)", - "Oromo", - "Oromo (Ethiopia)", - "Oromo (Kenya)", - "Oriya", - "Oriya (India)", - "Ossetian (Russia)", - "Panjabi (India)", - "Papiamento", - "Papiamento (Netherlands Antilles)", - "Papiamento (Aruba)", - "Papiamento (Curaçao)", - "Panjabi (Pakistan)", - "Polish", - "Polish (Poland)", - "Pirate", - "Pushto (Afghanistan)", - "Portuguese", - "Portuguese (Brazil)", - "Portuguese (Portugal)", - "Ayacucho Quechua (Peru)", - "Cusco Quechua (Peru)", - "Rajasthani (India)", - "Romanian", - "Romanian (Romania)", - "Russian", - "Russian (Russia)", - "Russian (Ukraine)", - "Kinyarwanda (Rwanda)", - "Sanskrit (India)", - "Santali (India)", - "Sardinian (Italy)", - "Scots (Scotland)", - "Sindhi (India)", - "Northern Sami (Norway)", - "Samogitian (Lithuania)", - "Shuswap (Canada)", - "Sidamo (Ethiopia)", - "Sinhala", - "Sinhala (Sri Lanka)", - "Slovak", - "Slovak (Slovakia)", - "Slovenian", - "Slovenian (Slovenia)", - "Somali", - "Somali (Djibouti)", - "Somali (Ethiopia)", - "Somali (Kenya)", - "Somali (Somalia)", - "Songhai languages (Mali)", - "Albanian", - "Albanian (Albania)", - "Albanian (Kosovo)", - "Albanian (Macedonia)", - "Serbian", - "Serbian (Cyrillic)", - "Serbian (Latin)", - "Serbian (Montenegro)", - "Serbian (Serbia)", - "Swati (South Africa)", - "Southern Sotho (South Africa)", - "Swedish", - "Swedish (Finland)", - "Swedish (Sweden)", - "Swahili (Kenya)", - "Swahili (Tanzania)", - "Silesian (Poland)", - "Tamil", - "Tamil (India)", - "Tamil (Sri Lanka)", - "Tulu (India)", - "Telugu", - "Telugu (India)", - "Tajik (Tajikistan)", - "Chitwania Tharu (Nepal)", - "Thai", - "Thai (Thailand)", - "Tigrinya", - "Tigrinya (Eritrea)", - "Tigrinya (Ethiopia)", - "Tigre (Eritrea)", - "Turkmen (Turkmenistan)", - "Tagalog (Philippines)", - "Tswana (South Africa)", - "Turkish", - "Turkish (Cyprus)", - "Turkish (Turkey)", - "Tsonga (South Africa)", - "Tatar", - "Tatar (Russia)", - "Central Atlas Tamazight", - "Central Atlas Tamazight (Marrocos)", - "Uighur (China)", - "Ukrainian", - "Ukrainian (Ukraine)", - "Unami (United States)", - "Urdu", - "Urdu (India)", - "Urdu (Pakistan)", - "Uzbek", - "Uzbek (Uzbekistan)", - "Venda (South Africa)", - "Vietnamese", - "Vietnamese (Vietnam)", - "Walloon (Belgium)", - "Walser (Switzerland)", - "Wolaytta (Ethiopia)", - "Wolof (Senegal)", - "Xhosa (South Africa)", - "Yiddish (United States)", - "Yoruba (Nigeria)", - "Yue Chinese (Hong Kong)", - "Chinese", - "Chinese (China)", - "Chinese (Hong Kong)", - "Chinese (Singapore)", - "Chinese (Taiwan)", - "Zulu (South Africa)", - nullptr -}; - -// Windows has some weird locale identifiers which do not honor the ISO 639-1 -// standardized nomenclature. Whenever those don't conflict with existing ISO -// identifiers, we override them. -// -// Reference: -// - https://msdn.microsoft.com/en-us/library/windows/desktop/ms693062(v=vs.85).aspx - -static const char *locale_renames[][2] = { - { "in", "id" }, // Indonesian - { "iw", "he" }, // Hebrew - { "no", "nb" }, // Norwegian Bokmål - { "C", "en" }, // "C" is the simple/default/untranslated Computer locale. - // ASCII-only, English, no currency symbols. Godot treats this as "en". - // See https://unix.stackexchange.com/a/87763/164141 "The C locale is"... - { nullptr, nullptr } -}; - -/////////////////////////////////////////////// - Dictionary Translation::_get_messages() const { Dictionary d; for (const KeyValue<StringName, StringName> &E : translation_map) { @@ -849,17 +68,7 @@ void Translation::_set_messages(const Dictionary &p_messages) { } void Translation::set_locale(const String &p_locale) { - String univ_locale = TranslationServer::standardize_locale(p_locale); - - if (!TranslationServer::is_locale_valid(univ_locale)) { - String trimmed_locale = TranslationServer::get_language_code(univ_locale); - - ERR_FAIL_COND_MSG(!TranslationServer::is_locale_valid(trimmed_locale), "Invalid locale: " + trimmed_locale + "."); - - locale = trimmed_locale; - } else { - locale = univ_locale; - } + locale = TranslationServer::get_singleton()->standardize_locale(p_locale); if (OS::get_singleton()->get_main_loop() && TranslationServer::get_singleton()->get_loaded_locales().has(this)) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); @@ -1004,124 +213,314 @@ static _character_accent_pair _character_to_accented[] = { { 'z', U"ź" }, }; -bool TranslationServer::is_locale_valid(const String &p_locale) { - const char **ptr = locale_list; +static _FORCE_INLINE_ bool is_upper_case(char32_t c) { + return (c >= 'A' && c <= 'Z'); +} - while (*ptr) { - if (*ptr == p_locale) { - return true; +static _FORCE_INLINE_ bool is_lower_case(char32_t c) { + return (c >= 'a' && c <= 'z'); +} + +Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info; + +Map<String, String> TranslationServer::language_map; +Map<String, String> TranslationServer::script_map; +Map<String, String> TranslationServer::locale_rename_map; +Map<String, String> TranslationServer::country_name_map; +Map<String, String> TranslationServer::variant_map; +Map<String, String> TranslationServer::country_rename_map; + +void TranslationServer::init_locale_info() { + // Init locale info. + language_map.clear(); + int idx = 0; + while (language_list[idx][0] != nullptr) { + language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]); + idx++; + } + + // Init locale-script map. + locale_script_info.clear(); + idx = 0; + while (locale_scripts[idx][0] != nullptr) { + LocaleScriptInfo info; + info.name = locale_scripts[idx][0]; + info.script = locale_scripts[idx][1]; + info.default_country = locale_scripts[idx][2]; + Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false); + for (int i = 0; i < supported_countries.size(); i++) { + info.supported_countries.insert(supported_countries[i]); } - ptr++; + locale_script_info.push_back(info); + idx++; } - return false; -} + // Init supported script list. + script_map.clear(); + idx = 0; + while (script_list[idx][0] != nullptr) { + script_map[script_list[idx][1]] = String::utf8(script_list[idx][0]); + idx++; + } -String TranslationServer::standardize_locale(const String &p_locale) { - // Replaces '-' with '_' for macOS Sierra-style locales - String univ_locale = p_locale.replace("-", "_"); + // Init regional variant map. + variant_map.clear(); + idx = 0; + while (locale_variants[idx][0] != nullptr) { + variant_map[locale_variants[idx][0]] = locale_variants[idx][1]; + idx++; + } - // Handles known non-ISO locale names used e.g. on Windows - int idx = 0; + // Init locale renames. + locale_rename_map.clear(); + idx = 0; while (locale_renames[idx][0] != nullptr) { - if (locale_renames[idx][0] == univ_locale) { - univ_locale = locale_renames[idx][1]; - break; + if (!String(locale_renames[idx][1]).is_empty()) { + locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1]; } idx++; } - return univ_locale; + // Init country names. + country_name_map.clear(); + idx = 0; + while (country_names[idx][0] != nullptr) { + country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]); + idx++; + } + + // Init country renames. + country_rename_map.clear(); + idx = 0; + while (country_renames[idx][0] != nullptr) { + if (!String(country_renames[idx][1]).is_empty()) { + country_rename_map[country_renames[idx][0]] = country_renames[idx][1]; + } + idx++; + } } -String TranslationServer::get_language_code(const String &p_locale) { - ERR_FAIL_COND_V_MSG(p_locale.length() < 2, p_locale, "Invalid locale '" + p_locale + "'."); - // Most language codes are two letters, but some are three, - // so we have to look for a regional code separator ('_' or '-') - // to extract the left part. - // For example we get 'nah_MX' as input and should return 'nah'. - int split = p_locale.find("_"); - if (split == -1) { - split = p_locale.find("-"); +String TranslationServer::standardize_locale(const String &p_locale) const { + // Replaces '-' with '_' for macOS style locales. + String univ_locale = p_locale.replace("-", "_"); + + // Extract locale elements. + String lang, script, country, variant; + Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_"); + lang = locale_elements[0]; + if (locale_elements.size() >= 2) { + if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) { + script = locale_elements[1]; + } + if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) { + country = locale_elements[1]; + } } - if (split == -1) { // No separator, so the locale is already only a language code. - return p_locale; + if (locale_elements.size() >= 3) { + if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) { + country = locale_elements[2]; + } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang) { + variant = locale_elements[2].to_lower(); + } + } + if (locale_elements.size() >= 4) { + if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang) { + variant = locale_elements[3].to_lower(); + } } - return p_locale.left(split); -} -void TranslationServer::set_locale(const String &p_locale) { - String univ_locale = standardize_locale(p_locale); + // Try extract script and variant from the extra part. + Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";"); + for (int i = 0; i < script_extra.size(); i++) { + if (script_extra[i].to_lower() == "cyrillic") { + script = "Cyrl"; + break; + } else if (script_extra[i].to_lower() == "latin") { + script = "Latn"; + break; + } else if (script_extra[i].to_lower() == "devanagari") { + script = "Deva"; + break; + } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang) { + variant = script_extra[i].to_lower(); + } + } - if (!is_locale_valid(univ_locale)) { - String trimmed_locale = get_language_code(univ_locale); - print_verbose(vformat("Unsupported locale '%s', falling back to '%s'.", p_locale, trimmed_locale)); + // Handles known non-ISO language names used e.g. on Windows. + if (locale_rename_map.has(lang)) { + lang = locale_rename_map[lang]; + } - if (!is_locale_valid(trimmed_locale)) { - ERR_PRINT(vformat("Unsupported locale '%s', falling back to 'en'.", trimmed_locale)); - locale = "en"; - } else { - locale = trimmed_locale; + // Handle country renames. + if (country_rename_map.has(country)) { + country = country_rename_map[country]; + } + + // Remove unsupported script codes. + if (!script_map.has(script)) { + script = ""; + } + + // Add script code base on language and country codes for some ambiguous cases. + if (script.is_empty()) { + for (int i = 0; i < locale_script_info.size(); i++) { + const LocaleScriptInfo &info = locale_script_info[i]; + if (info.name == lang) { + if (country.is_empty() || info.supported_countries.has(country)) { + script = info.script; + break; + } + } + } + } + if (!script.is_empty() && country.is_empty()) { + // Add conntry code based on script for some ambiguous cases. + for (int i = 0; i < locale_script_info.size(); i++) { + const LocaleScriptInfo &info = locale_script_info[i]; + if (info.name == lang && info.script == script) { + country = info.default_country; + break; + } + } + } + + // Combine results. + String locale = lang; + if (!script.is_empty()) { + locale = locale + "_" + script; + } + if (!country.is_empty()) { + locale = locale + "_" + country; + } + if (!variant.is_empty()) { + locale = locale + "_" + variant; + } + return locale; +} + +int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const { + String locale_a = standardize_locale(p_locale_a); + String locale_b = standardize_locale(p_locale_b); + + if (locale_a == locale_b) { + // Exact match. + return 10; + } + + Vector<String> locale_a_elements = locale_a.split("_"); + Vector<String> locale_b_elements = locale_b.split("_"); + if (locale_a_elements[0] == locale_b_elements[0]) { + // Matching language, both locales have extra parts. + // Return number of matching elements. + int matching_elements = 1; + for (int i = 1; i < locale_a_elements.size(); i++) { + for (int j = 1; j < locale_b_elements.size(); j++) { + if (locale_a_elements[i] == locale_b_elements[j]) { + matching_elements++; + } + } } + return matching_elements; } else { - locale = univ_locale; + // No match. + return 0; } +} - if (OS::get_singleton()->get_main_loop()) { - OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); +String TranslationServer::get_locale_name(const String &p_locale) const { + String locale = standardize_locale(p_locale); + + String lang, script, country; + Vector<String> locale_elements = locale.split("_"); + lang = locale_elements[0]; + if (locale_elements.size() >= 2) { + if (locale_elements[1].length() == 4 && is_upper_case(locale_elements[1][0]) && is_lower_case(locale_elements[1][1]) && is_lower_case(locale_elements[1][2]) && is_lower_case(locale_elements[1][3])) { + script = locale_elements[1]; + } + if (locale_elements[1].length() == 2 && is_upper_case(locale_elements[1][0]) && is_upper_case(locale_elements[1][1])) { + country = locale_elements[1]; + } + } + if (locale_elements.size() >= 3) { + if (locale_elements[2].length() == 2 && is_upper_case(locale_elements[2][0]) && is_upper_case(locale_elements[2][1])) { + country = locale_elements[2]; + } } - ResourceLoader::reload_translation_remaps(); + String name = language_map[lang]; + if (!script.is_empty()) { + name = name + " (" + script_map[script] + ")"; + } + if (!country.is_empty()) { + name = name + ", " + country_name_map[country]; + } + return name; } -String TranslationServer::get_locale() const { - return locale; +Vector<String> TranslationServer::get_all_languages() const { + Vector<String> languages; + + for (const Map<String, String>::Element *E = language_map.front(); E; E = E->next()) { + languages.push_back(E->key()); + } + + return languages; } -String TranslationServer::get_locale_name(const String &p_locale) const { - if (!locale_name_map.has(p_locale)) { - return String(); +String TranslationServer::get_language_name(const String &p_language) const { + return language_map[p_language]; +} + +Vector<String> TranslationServer::get_all_scripts() const { + Vector<String> scripts; + + for (const Map<String, String>::Element *E = script_map.front(); E; E = E->next()) { + scripts.push_back(E->key()); } - return locale_name_map[p_locale]; + + return scripts; } -Array TranslationServer::get_loaded_locales() const { - Array locales; - for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) { - const Ref<Translation> &t = E->get(); - ERR_FAIL_COND_V(t.is_null(), Array()); - String l = t->get_locale(); +String TranslationServer::get_script_name(const String &p_script) const { + return script_map[p_script]; +} - if (!locales.has(l)) { - locales.push_back(l); - } +Vector<String> TranslationServer::get_all_countries() const { + Vector<String> countries; + + for (const Map<String, String>::Element *E = country_name_map.front(); E; E = E->next()) { + countries.push_back(E->key()); } - locales.sort(); - return locales; + return countries; } -Vector<String> TranslationServer::get_all_locales() { - Vector<String> locales; +String TranslationServer::get_country_name(const String &p_country) const { + return country_name_map[p_country]; +} - const char **ptr = locale_list; +void TranslationServer::set_locale(const String &p_locale) { + locale = standardize_locale(p_locale); - while (*ptr) { - locales.push_back(*ptr); - ptr++; + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); } - return locales; + ResourceLoader::reload_translation_remaps(); } -Vector<String> TranslationServer::get_all_locale_names() { - Vector<String> locales; +String TranslationServer::get_locale() const { + return locale; +} - const char **ptr = locale_names; +Array TranslationServer::get_loaded_locales() const { + Array locales; + for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) { + const Ref<Translation> &t = E->get(); + ERR_FAIL_COND_V(t.is_null(), Array()); + String l = t->get_locale(); - while (*ptr) { - locales.push_back(String::utf8(*ptr)); - ptr++; + locales.push_back(l); } return locales; @@ -1137,23 +536,20 @@ void TranslationServer::remove_translation(const Ref<Translation> &p_translation Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) { Ref<Translation> res; - String lang = get_language_code(p_locale); - bool near_match_found = false; + int best_score = 0; for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) { const Ref<Translation> &t = E->get(); ERR_FAIL_COND_V(t.is_null(), nullptr); String l = t->get_locale(); - // Exact match. - if (l == p_locale) { - return t; - } - - // If near match found, keep that match, but keep looking to try to look for perfect match. - if (get_language_code(l) == lang && !near_match_found) { + int score = compare_locales(p_locale, l); + if (score >= best_score) { res = t; - near_match_found = true; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. + } } } return res; @@ -1212,52 +608,27 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons } StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const { - // Locale can be of the form 'll_CC', i.e. language code and regional code, - // e.g. 'en_US', 'en_GB', etc. It might also be simply 'll', e.g. 'en'. - // To find the relevant translation, we look for those with locale starting - // with the language code, and then if any is an exact match for the long - // form. If not found, we fall back to a near match (another locale with - // same language code). - - // Note: ResourceLoader::_path_remap reproduces this locale near matching - // logic, so be sure to propagate changes there when changing things here. - StringName res; - String lang = get_language_code(p_locale); - bool near_match = false; + int best_score = 0; for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) { const Ref<Translation> &t = E->get(); ERR_FAIL_COND_V(t.is_null(), p_message); String l = t->get_locale(); - bool exact_match = (l == p_locale); - if (!exact_match) { - if (near_match) { - continue; // Only near-match once, but keep looking for exact matches. + int score = compare_locales(p_locale, l); + if (score >= best_score) { + StringName r; + if (!plural) { + res = t->get_message(p_message, p_context); + } else { + res = t->get_plural_message(p_message, p_message_plural, p_n, p_context); } - if (get_language_code(l) != lang) { - continue; // Language code does not match. + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. } } - - StringName r; - if (!plural) { - r = t->get_message(p_message, p_context); - } else { - r = t->get_plural_message(p_message, p_message_plural, p_n, p_context); - } - - if (!r) { - continue; - } - res = r; - - if (exact_match) { - break; - } else { - near_match = true; - } } return res; @@ -1308,18 +679,7 @@ void TranslationServer::setup() { pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true); #ifdef TOOLS_ENABLED - { - String options = ""; - int idx = 0; - while (locale_list[idx]) { - if (idx > 0) { - options += ","; - } - options += locale_list[idx]; - idx++; - } - ProjectSettings::get_singleton()->set_custom_property_info("internationalization/locale/fallback", PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_ENUM, options)); - } + ProjectSettings::get_singleton()->set_custom_property_info("internationalization/locale/fallback", PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, "")); #endif } @@ -1574,6 +934,18 @@ void TranslationServer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale); ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale); + ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales); + ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale); + + ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages); + ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name); + + ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts); + ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name); + + ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries); + ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name); + ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name); ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL("")); @@ -1606,8 +978,5 @@ void TranslationServer::load_translations() { TranslationServer::TranslationServer() { singleton = this; - - for (int i = 0; locale_list[i]; ++i) { - locale_name_map.insert(locale_list[i], String::utf8(locale_names[i])); - } + init_locale_info(); } |