diff options
Diffstat (limited to 'core/string/translation.cpp')
-rw-r--r-- | core/string/translation.cpp | 1303 |
1 files changed, 1303 insertions, 0 deletions
diff --git a/core/string/translation.cpp b/core/string/translation.cpp new file mode 100644 index 0000000000..df8a26e5ce --- /dev/null +++ b/core/string/translation.cpp @@ -0,0 +1,1303 @@ +/*************************************************************************/ +/* translation.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "translation.h" + +#include "core/config/project_settings.h" +#include "core/io/resource_loader.h" +#include "core/os/os.h" + +// ISO 639-1 language 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/ + +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_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_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_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_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_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_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_RU", // Tatar (Russia) + "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 (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 (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 (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 (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 (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 (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 (Russia)", + "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 + { nullptr, nullptr } +}; + +/////////////////////////////////////////////// + +Dictionary Translation::_get_messages() const { + Dictionary d; + for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) { + d[E->key()] = E->value(); + } + return d; +} + +Vector<String> Translation::_get_message_list() const { + Vector<String> msgs; + msgs.resize(translation_map.size()); + int idx = 0; + for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) { + msgs.set(idx, E->key()); + idx += 1; + } + + return msgs; +} + +void Translation::_set_messages(const Dictionary &p_messages) { + List<Variant> keys; + p_messages.get_key_list(&keys); + for (auto E = keys.front(); E; E = E->next()) { + translation_map[E->get()] = p_messages[E->get()]; + } +} + +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; + } + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } +} + +void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) { + translation_map[p_src_text] = p_xlated_text; +} + +void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) { + WARN_PRINT("Translation class doesn't handle plural messages. Calling add_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class"); + ERR_FAIL_COND_MSG(p_plural_xlated_texts.empty(), "Parameter vector p_plural_xlated_texts passed in is empty."); + translation_map[p_src_text] = p_plural_xlated_texts[0]; +} + +StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const { + if (p_context != StringName()) { + WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class"); + } + + const Map<StringName, StringName>::Element *E = translation_map.find(p_src_text); + if (!E) { + return StringName(); + } + + return E->get(); +} + +StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const { + WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class"); + return get_message(p_src_text); +} + +void Translation::erase_message(const StringName &p_src_text, const StringName &p_context) { + if (p_context != StringName()) { + WARN_PRINT("Translation class doesn't handle context. Using context in erase_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class"); + } + + translation_map.erase(p_src_text); +} + +void Translation::get_message_list(List<StringName> *r_messages) const { + for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) { + r_messages->push_back(E->key()); + } +} + +int Translation::get_message_count() const { + return translation_map.size(); +} + +void Translation::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_locale", "locale"), &Translation::set_locale); + ClassDB::bind_method(D_METHOD("get_locale"), &Translation::get_locale); + ClassDB::bind_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL("")); + ClassDB::bind_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL("")); + ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL("")); + ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list); + ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count); + ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages); + ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages); + + ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages"); + ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale"); +} + +/////////////////////////////////////////////// + +bool TranslationServer::is_locale_valid(const String &p_locale) { + const char **ptr = locale_list; + + while (*ptr) { + if (*ptr == p_locale) { + return true; + } + ptr++; + } + + return false; +} + +String TranslationServer::standardize_locale(const String &p_locale) { + // Replaces '-' with '_' for macOS Sierra-style locales + String univ_locale = p_locale.replace("-", "_"); + + // Handles known non-ISO locale names used e.g. on Windows + int idx = 0; + while (locale_renames[idx][0] != nullptr) { + if (locale_renames[idx][0] == univ_locale) { + univ_locale = locale_renames[idx][1]; + break; + } + idx++; + } + + return univ_locale; +} + +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("-"); + } + if (split == -1) { // No separator, so the locale is already only a language code. + return p_locale; + } + return p_locale.left(split); +} + +void TranslationServer::set_locale(const String &p_locale) { + String univ_locale = standardize_locale(p_locale); + + 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)); + + if (!is_locale_valid(trimmed_locale)) { + ERR_PRINT(vformat("Unsupported locale '%s', falling back to 'en'.", trimmed_locale)); + locale = "en"; + } else { + locale = trimmed_locale; + } + } else { + locale = univ_locale; + } + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } + + ResourceLoader::reload_translation_remaps(); +} + +String TranslationServer::get_locale() const { + return locale; +} + +String TranslationServer::get_locale_name(const String &p_locale) const { + if (!locale_name_map.has(p_locale)) { + return String(); + } + return locale_name_map[p_locale]; +} + +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(); + + locales.push_back(l); + } + + return locales; +} + +Vector<String> TranslationServer::get_all_locales() { + Vector<String> locales; + + const char **ptr = locale_list; + + while (*ptr) { + locales.push_back(*ptr); + ptr++; + } + + return locales; +} + +Vector<String> TranslationServer::get_all_locale_names() { + Vector<String> locales; + + const char **ptr = locale_names; + + while (*ptr) { + locales.push_back(String::utf8(*ptr)); + ptr++; + } + + return locales; +} + +void TranslationServer::add_translation(const Ref<Translation> &p_translation) { + translations.insert(p_translation); +} + +void TranslationServer::remove_translation(const Ref<Translation> &p_translation) { + translations.erase(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; + + 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) { + res = t; + near_match_found = true; + } + } + return res; +} + +void TranslationServer::clear() { + translations.clear(); +} + +StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const { + // Match given message against the translation catalog for the project locale. + + if (!enabled) { + return p_message; + } + + ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid."); + + StringName res = _get_message_from_translations(p_message, p_context, locale, false); + + if (!res && fallback.length() >= 2) { + res = _get_message_from_translations(p_message, p_context, fallback, false); + } + + if (!res) { + return p_message; + } + + return res; +} + +StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + if (!enabled) { + if (p_n == 1) { + return p_message; + } + return p_message_plural; + } + + ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid."); + + StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n); + + if (!res && fallback.length() >= 2) { + res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n); + } + + if (!res) { + if (p_n == 1) { + return p_message; + } + return p_message_plural; + } + + return res; +} + +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; + + 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. + } + if (get_language_code(l) != lang) { + continue; // Language code does not match. + } + } + + 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; +} + +TranslationServer *TranslationServer::singleton = nullptr; + +bool TranslationServer::_load_translations(const String &p_from) { + if (ProjectSettings::get_singleton()->has_setting(p_from)) { + Vector<String> translations = ProjectSettings::get_singleton()->get(p_from); + + int tcount = translations.size(); + + if (tcount) { + const String *r = translations.ptr(); + + for (int i = 0; i < tcount; i++) { + Ref<Translation> tr = ResourceLoader::load(r[i]); + if (tr.is_valid()) { + add_translation(tr); + } + } + } + return true; + } + + return false; +} + +void TranslationServer::setup() { + String test = GLOBAL_DEF("locale/test", ""); + test = test.strip_edges(); + if (test != "") { + set_locale(test); + } else { + set_locale(OS::get_singleton()->get_locale()); + } + fallback = GLOBAL_DEF("locale/fallback", "en"); +#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("locale/fallback", PropertyInfo(Variant::STRING, "locale/fallback", PROPERTY_HINT_ENUM, options)); + } +#endif +} + +void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) { + tool_translation = p_translation; +} + +StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const { + if (tool_translation.is_valid()) { + StringName r = tool_translation->get_message(p_message, p_context); + if (r) { + return r; + } + } + return p_message; +} + +StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + if (tool_translation.is_valid()) { + StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); + if (r) { + return r; + } + } + + if (p_n == 1) { + return p_message; + } + return p_message_plural; +} + +void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) { + doc_translation = p_translation; +} + +StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const { + if (doc_translation.is_valid()) { + StringName r = doc_translation->get_message(p_message, p_context); + if (r) { + return r; + } + } + return p_message; +} + +StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + if (doc_translation.is_valid()) { + StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); + if (r) { + return r; + } + } + + if (p_n == 1) { + return p_message; + } + return p_message_plural; +} + +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("get_locale_name", "locale"), &TranslationServer::get_locale_name); + + ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL("")); + ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL("")); + + ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation); + ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation); + ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object); + + ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear); + + ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales); +} + +void TranslationServer::load_translations() { + String locale = get_locale(); + _load_translations("locale/translations"); //all + _load_translations("locale/translations_" + locale.substr(0, 2)); + + if (locale.substr(0, 2) != locale) { + _load_translations("locale/translations_" + locale); + } +} + +TranslationServer::TranslationServer() { + singleton = this; + + for (int i = 0; locale_list[i]; ++i) { + locale_name_map.insert(locale_list[i], String::utf8(locale_names[i])); + } +} |