From a8eb779ac3819b513ae0281b413f821dfa8d523e Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Thu, 9 Jun 2022 13:19:30 +0300 Subject: [PATCH] Backport locale selection improvements. --- core/global_constants.cpp | 2 + core/io/resource_loader.cpp | 26 +- core/locales.h | 1197 ++++++++++++++++ core/object.h | 1 + core/translation.cpp | 1255 ++++------------- core/translation.h | 37 +- doc/classes/@GlobalScope.xml | 3 + doc/classes/TranslationServer.xml | 54 + editor/editor_locale_dialog.cpp | 572 ++++++++ editor/editor_locale_dialog.h | 93 ++ editor/editor_properties.cpp | 65 + editor/editor_properties.h | 21 + editor/editor_settings.cpp | 15 +- .../resource_importer_csv_translation.cpp | 3 +- editor/project_settings_editor.cpp | 249 +--- editor/project_settings_editor.h | 17 +- editor/property_editor.cpp | 26 +- editor/property_editor.h | 3 + .../include/nativescript/godot_nativescript.h | 5 + 19 files changed, 2430 insertions(+), 1214 deletions(-) create mode 100644 core/locales.h create mode 100644 editor/editor_locale_dialog.cpp create mode 100644 editor/editor_locale_dialog.h diff --git a/core/global_constants.cpp b/core/global_constants.cpp index ab073013129..56250764c06 100644 --- a/core/global_constants.cpp +++ b/core/global_constants.cpp @@ -608,6 +608,8 @@ void register_global_constants() { BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_IMAGE_COMPRESS_LOSSY); BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_IMAGE_COMPRESS_LOSSLESS); + BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_HINT_LOCALE_ID); + BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_STORAGE); BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_EDITOR); BIND_GLOBAL_ENUM_CONSTANT(PROPERTY_USAGE_NETWORK); diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp index 03505227204..39e07301530 100644 --- a/core/io/resource_loader.cpp +++ b/core/io/resource_loader.cpp @@ -721,38 +721,26 @@ String ResourceLoader::_path_remap(const String &p_path, bool *r_translation_rem // To find the path of the remapped resource, we extract the locale name after // the last ':' to match the project locale. - // We also fall back in case of regional locales as done in TranslationServer::translate - // (e.g. 'ru_RU' -> 'ru' if the former has no specific mapping). String locale = TranslationServer::get_singleton()->get_locale(); ERR_FAIL_COND_V_MSG(locale.length() < 2, p_path, "Could not remap path '" + p_path + "' for translation as configured locale '" + locale + "' is invalid."); - String lang = TranslationServer::get_language_code(locale); Vector &res_remaps = *translation_remaps.getptr(new_path); - bool near_match = false; + int best_score = 0; for (int i = 0; i < res_remaps.size(); i++) { int split = res_remaps[i].rfind(":"); if (split == -1) { continue; } - String l = res_remaps[i].right(split + 1).strip_edges(); - if (l == locale) { // Exact match. + int score = TranslationServer::get_singleton()->compare_locales(locale, l); + if (score > 0 && score >= best_score) { new_path = res_remaps[i].left(split); - break; - } else if (near_match) { - continue; // Already found near match, keep going for potential exact match. - } - - // No exact match (e.g. locale 'ru_RU' but remap is 'ru'), let's look further - // for a near match (same language code, i.e. first 2 or 3 letters before - // regional code, if included). - if (TranslationServer::get_language_code(l) == lang) { - // Language code matches, that's a near match. Keep looking for exact match. - near_match = true; - new_path = res_remaps[i].left(split); - continue; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. + } } } diff --git a/core/locales.h b/core/locales.h new file mode 100644 index 00000000000..32d6608ec26 --- /dev/null +++ b/core/locales.h @@ -0,0 +1,1197 @@ +/*************************************************************************/ +/* locales.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +#ifndef LOCALES_H +#define LOCALES_H + +// 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" }, // Locale is not set, fallback to English. + { nullptr, nullptr } +}; + +// Additional script information to preferred scripts. +// Language code, script code, default country, supported countries. +// Reference: +// - https://lh.2xlibre.net/locales/ +// - https://www.localeplanet.com/icu/index.html +// - https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-lcid/70feba9f-294e-491e-b6eb-56532684c37f + +static const char *locale_scripts[][4] = { + { "az", "Latn", "", "AZ" }, + { "az", "Arab", "", "IR" }, + { "bs", "Latn", "", "BA" }, + { "ff", "Latn", "", "BF,CM,GH,GM,GN,GW,LR,MR,NE,NG,SL,SN" }, + { "pa", "Arab", "PK", "PK" }, + { "pa", "Guru", "IN", "IN" }, + { "sd", "Arab", "PK", "PK" }, + { "sd", "Deva", "IN", "IN" }, + { "shi", "Tfng", "", "MA" }, + { "sr", "Cyrl", "", "BA,RS,XK" }, + { "sr", "Latn", "", "ME" }, + { "uz", "Latn", "", "UZ" }, + { "uz", "Arab", "AF", "AF" }, + { "vai", "Vaii", "", "LR" }, + { "yue", "Hans", "CN", "CN" }, + { "yue", "Hant", "HK", "HK" }, + { "zh", "Hans", "CN", "CN,SG" }, + { "zh", "Hant", "TW", "HK,MO,TW" }, + { nullptr, nullptr, nullptr, nullptr } +}; + +// Additional mapping for outdated, temporary or exceptionally reserved country codes. +// Reference: +// - https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 +// - https://www.iso.org/obp/ui/#search/code/ + +static const char *country_renames[][2] = { + { "BU", "MM" }, // Burma, name changed to Myanmar. + { "KV", "XK" }, // Kosovo (temporary FIPS code to European Commission code), no official ISO code assigned. + { "TP", "TL" }, // East Timor, name changed to Timor-Leste. + { "UK", "GB" }, // United Kingdom, exceptionally reserved code. + { nullptr, nullptr } +}; + +// Country code, country name. +// Reference: +// - https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2 +// - https://www.iso.org/obp/ui/#search/code/ + +static const char *country_names[][2] = { + { "AC", "Ascension Island" }, // Exceptionally reserved. + { "AD", "Andorra" }, + { "AE", "United Arab Emirates" }, + { "AF", "Afghanistan" }, + { "AG", "Antigua and Barbuda" }, + { "AI", "Anguilla" }, + { "AL", "Albania" }, + { "AM", "Armenia" }, + { "AN", "Netherlands Antilles" }, // Transitionally reserved, divided into BQ, CW and SX. + { "AO", "Angola" }, + { "AQ", "Antarctica" }, + { "AR", "Argentina" }, + { "AS", "American Samoa" }, + { "AT", "Austria" }, + { "AU", "Australia" }, + { "AW", "Aruba" }, + { "AX", "Åland Islands" }, + { "AZ", "Azerbaijan" }, + { "BA", "Bosnia and Herzegovina" }, + { "BB", "Barbados" }, + { "BD", "Bangladesh" }, + { "BE", "Belgium" }, + { "BF", "Burkina Faso" }, + { "BG", "Bulgaria" }, + { "BH", "Bahrain" }, + { "BI", "Burundi" }, + { "BJ", "Benin" }, + { "BL", "St. Barthélemy" }, + { "BM", "Bermuda" }, + { "BN", "Brunei" }, + { "BO", "Bolivia" }, + { "BQ", "Caribbean Netherlands" }, + { "BR", "Brazil" }, + { "BS", "Bahamas" }, + { "BT", "Bhutan" }, + { "BV", "Bouvet Island" }, + { "BW", "Botswana" }, + { "BY", "Belarus" }, + { "BZ", "Belize" }, + { "CA", "Canada" }, + { "CC", "Cocos (Keeling) Islands" }, + { "CD", "Congo - Kinshasa" }, + { "CF", "Central African Republic" }, + { "CG", "Congo - Brazzaville" }, + { "CH", "Switzerland" }, + { "CI", "Côte d'Ivoire" }, + { "CK", "Cook Islands" }, + { "CL", "Chile" }, + { "CM", "Cameroon" }, + { "CN", "China" }, + { "CO", "Colombia" }, + { "CP", "Clipperton Island" }, // Exceptionally reserved. + { "CR", "Costa Rica" }, + { "CQ", "Island of Sark" }, // Exceptionally reserved. + { "CU", "Cuba" }, + { "CV", "Cabo Verde" }, + { "CW", "Curaçao" }, + { "CX", "Christmas Island" }, + { "CY", "Cyprus" }, + { "CZ", "Czechia" }, + { "DE", "Germany" }, + { "DG", "Diego Garcia" }, // Exceptionally reserved. + { "DJ", "Djibouti" }, + { "DK", "Denmark" }, + { "DM", "Dominica" }, + { "DO", "Dominican Republic" }, + { "DZ", "Algeria" }, + { "EA", "Ceuta and Melilla" }, // Exceptionally reserved. + { "EC", "Ecuador" }, + { "EE", "Estonia" }, + { "EG", "Egypt" }, + { "EH", "Western Sahara" }, + { "ER", "Eritrea" }, + { "ES", "Spain" }, + { "ET", "Ethiopia" }, + { "EU", "European Union" }, // Exceptionally reserved. + { "EZ", "Eurozone" }, // Exceptionally reserved. + { "FI", "Finland" }, + { "FJ", "Fiji" }, + { "FK", "Falkland Islands" }, + { "FM", "Micronesia" }, + { "FO", "Faroe Islands" }, + { "FR", "France" }, + { "FX", "France, Metropolitan" }, // Exceptionally reserved. + { "GA", "Gabon" }, + { "GB", "United Kingdom" }, + { "GD", "Grenada" }, + { "GE", "Georgia" }, + { "GF", "French Guiana" }, + { "GG", "Guernsey" }, + { "GH", "Ghana" }, + { "GI", "Gibraltar" }, + { "GL", "Greenland" }, + { "GM", "Gambia" }, + { "GN", "Guinea" }, + { "GP", "Guadeloupe" }, + { "GQ", "Equatorial Guinea" }, + { "GR", "Greece" }, + { "GS", "South Georgia and South Sandwich Islands" }, + { "GT", "Guatemala" }, + { "GU", "Guam" }, + { "GW", "Guinea-Bissau" }, + { "GY", "Guyana" }, + { "HK", "Hong Kong" }, + { "HM", "Heard Island and McDonald Islands" }, + { "HN", "Honduras" }, + { "HR", "Croatia" }, + { "HT", "Haiti" }, + { "HU", "Hungary" }, + { "IC", "Canary Islands" }, // Exceptionally reserved. + { "ID", "Indonesia" }, + { "IE", "Ireland" }, + { "IL", "Israel" }, + { "IM", "Isle of Man" }, + { "IN", "India" }, + { "IO", "British Indian Ocean Territory" }, + { "IQ", "Iraq" }, + { "IR", "Iran" }, + { "IS", "Iceland" }, + { "IT", "Italy" }, + { "JE", "Jersey" }, + { "JM", "Jamaica" }, + { "JO", "Jordan" }, + { "JP", "Japan" }, + { "KE", "Kenya" }, + { "KG", "Kyrgyzstan" }, + { "KH", "Cambodia" }, + { "KI", "Kiribati" }, + { "KM", "Comoros" }, + { "KN", "St. Kitts and Nevis" }, + { "KP", "North Korea" }, + { "KR", "South Korea" }, + { "KW", "Kuwait" }, + { "KY", "Cayman Islands" }, + { "KZ", "Kazakhstan" }, + { "LA", "Laos" }, + { "LB", "Lebanon" }, + { "LC", "St. Lucia" }, + { "LI", "Liechtenstein" }, + { "LK", "Sri Lanka" }, + { "LR", "Liberia" }, + { "LS", "Lesotho" }, + { "LT", "Lithuania" }, + { "LU", "Luxembourg" }, + { "LV", "Latvia" }, + { "LY", "Libya" }, + { "MA", "Morocco" }, + { "MC", "Monaco" }, + { "MD", "Moldova" }, + { "ME", "Montenegro" }, + { "MF", "St. Martin" }, + { "MG", "Madagascar" }, + { "MH", "Marshall Islands" }, + { "MK", "North Macedonia" }, + { "ML", "Mali" }, + { "MM", "Myanmar" }, + { "MN", "Mongolia" }, + { "MO", "Macao" }, + { "MP", "Northern Mariana Islands" }, + { "MQ", "Martinique" }, + { "MR", "Mauritania" }, + { "MS", "Montserrat" }, + { "MT", "Malta" }, + { "MU", "Mauritius" }, + { "MV", "Maldives" }, + { "MW", "Malawi" }, + { "MX", "Mexico" }, + { "MY", "Malaysia" }, + { "MZ", "Mozambique" }, + { "NA", "Namibia" }, + { "NC", "New Caledonia" }, + { "NE", "Niger" }, + { "NF", "Norfolk Island" }, + { "NG", "Nigeria" }, + { "NI", "Nicaragua" }, + { "NL", "Netherlands" }, + { "NO", "Norway" }, + { "NP", "Nepal" }, + { "NR", "Nauru" }, + { "NU", "Niue" }, + { "NZ", "New Zealand" }, + { "OM", "Oman" }, + { "PA", "Panama" }, + { "PE", "Peru" }, + { "PF", "French Polynesia" }, + { "PG", "Papua New Guinea" }, + { "PH", "Philippines" }, + { "PK", "Pakistan" }, + { "PL", "Poland" }, + { "PM", "St. Pierre and Miquelon" }, + { "PN", "Pitcairn Islands" }, + { "PR", "Puerto Rico" }, + { "PS", "Palestine" }, + { "PT", "Portugal" }, + { "PW", "Palau" }, + { "PY", "Paraguay" }, + { "QA", "Qatar" }, + { "RE", "Réunion" }, + { "RO", "Romania" }, + { "RS", "Serbia" }, + { "RU", "Russia" }, + { "RW", "Rwanda" }, + { "SA", "Saudi Arabia" }, + { "SB", "Solomon Islands" }, + { "SC", "Seychelles" }, + { "SD", "Sudan" }, + { "SE", "Sweden" }, + { "SG", "Singapore" }, + { "SH", "St. Helena, Ascension and Tristan da Cunha" }, + { "SI", "Slovenia" }, + { "SJ", "Svalbard and Jan Mayen" }, + { "SK", "Slovakia" }, + { "SL", "Sierra Leone" }, + { "SM", "San Marino" }, + { "SN", "Senegal" }, + { "SO", "Somalia" }, + { "SR", "Suriname" }, + { "SS", "South Sudan" }, + { "ST", "Sao Tome and Principe" }, + { "SV", "El Salvador" }, + { "SX", "Sint Maarten" }, + { "SY", "Syria" }, + { "SZ", "Eswatini" }, + { "TA", "Tristan da Cunha" }, // Exceptionally reserved. + { "TC", "Turks and Caicos Islands" }, + { "TD", "Chad" }, + { "TF", "French Southern Territories" }, + { "TG", "Togo" }, + { "TH", "Thailand" }, + { "TJ", "Tajikistan" }, + { "TK", "Tokelau" }, + { "TL", "Timor-Leste" }, + { "TM", "Turkmenistan" }, + { "TN", "Tunisia" }, + { "TO", "Tonga" }, + { "TR", "Turkey" }, + { "TT", "Trinidad and Tobago" }, + { "TV", "Tuvalu" }, + { "TW", "Taiwan" }, + { "TZ", "Tanzania" }, + { "UA", "Ukraine" }, + { "UG", "Uganda" }, + { "UM", "U.S. Outlying Islands" }, + { "US", "United States of America" }, + { "UY", "Uruguay" }, + { "UZ", "Uzbekistan" }, + { "VA", "Holy See" }, + { "VC", "St. Vincent and the Grenadines" }, + { "VE", "Venezuela" }, + { "VG", "British Virgin Islands" }, + { "VI", "U.S. Virgin Islands" }, + { "VN", "Viet Nam" }, + { "VU", "Vanuatu" }, + { "WF", "Wallis and Futuna" }, + { "WS", "Samoa" }, + { "XK", "Kosovo" }, // Temporary code, no official ISO code assigned. + { "YE", "Yemen" }, + { "YT", "Mayotte" }, + { "ZA", "South Africa" }, + { "ZM", "Zambia" }, + { "ZW", "Zimbabwe" }, + { nullptr, nullptr } +}; + +// Languages code, language name. +// Reference: +// - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes +// - https://www.localeplanet.com/icu/index.html +// - https://lh.2xlibre.net/locales/ + +static const char *language_list[][2] = { + { "aa", "Afar" }, + { "ab", "Abkhazian" }, + { "ace", "Achinese" }, + { "ach", "Acoli" }, + { "ada", "Adangme" }, + { "ady", "Adyghe" }, + { "ae", "Avestan" }, + { "aeb", "Tunisian Arabic" }, + { "af", "Afrikaans" }, + { "afh", "Afrihili" }, + { "agq", "Aghem" }, + { "ain", "Ainu" }, + { "agr", "Aguaruna" }, + { "ak", "Akan" }, + { "akk", "Akkadian" }, + { "akz", "Alabama" }, + { "ale", "Aleut" }, + { "aln", "Gheg Albanian" }, + { "alt", "Southern Altai" }, + { "am", "Amharic" }, + { "an", "Aragonese" }, + { "ang", "Old English" }, + { "anp", "Angika" }, + { "ar", "Arabic" }, + { "arc", "Aramaic" }, + { "arn", "Mapudungun" }, + { "aro", "Araona" }, + { "arp", "Arapaho" }, + { "arq", "Algerian Arabic" }, + { "ars", "Najdi Arabic" }, + { "arw", "Arawak" }, + { "ary", "Moroccan Arabic" }, + { "arz", "Egyptian Arabic" }, + { "as", "Assamese" }, + { "asa", "Asu" }, + { "ase", "American Sign Language" }, + { "ast", "Asturian" }, + { "av", "Avaric" }, + { "avk", "Kotava" }, + { "awa", "Awadhi" }, + { "ayc", "Southern Aymara" }, + { "ay", "Aymara" }, + { "az", "Azerbaijani" }, + { "ba", "Bashkir" }, + { "bal", "Baluchi" }, + { "ban", "Balinese" }, + { "bar", "Bavarian" }, + { "bas", "Bassa" }, + { "bax", "Bamun" }, + { "bbc", "Batak Toba" }, + { "bbj", "Ghomala" }, + { "be", "Belarusian" }, + { "bej", "Beja" }, + { "bem", "Bemba" }, + { "ber", "Berber" }, + { "bew", "Betawi" }, + { "bez", "Bena" }, + { "bfd", "Bafut" }, + { "bfq", "Badaga" }, + { "bg", "Bulgarian" }, + { "bhb", "Bhili" }, + { "bgn", "Western Balochi" }, + { "bho", "Bhojpuri" }, + { "bi", "Bislama" }, + { "bik", "Bikol" }, + { "bin", "Bini" }, + { "bjn", "Banjar" }, + { "bkm", "Kom" }, + { "bla", "Siksika" }, + { "bm", "Bambara" }, + { "bn", "Bengali" }, + { "bo", "Tibetan" }, + { "bpy", "Bishnupriya" }, + { "bqi", "Bakhtiari" }, + { "br", "Breton" }, + { "brh", "Brahui" }, + { "brx", "Bodo" }, + { "bs", "Bosnian" }, + { "bss", "Akoose" }, + { "bua", "Buriat" }, + { "bug", "Buginese" }, + { "bum", "Bulu" }, + { "byn", "Bilin" }, + { "byv", "Medumba" }, + { "ca", "Catalan" }, + { "cad", "Caddo" }, + { "car", "Carib" }, + { "cay", "Cayuga" }, + { "cch", "Atsam" }, + { "ccp", "Chakma" }, + { "ce", "Chechen" }, + { "ceb", "Cebuano" }, + { "cgg", "Chiga" }, + { "ch", "Chamorro" }, + { "chb", "Chibcha" }, + { "chg", "Chagatai" }, + { "chk", "Chuukese" }, + { "chm", "Mari" }, + { "chn", "Chinook Jargon" }, + { "cho", "Choctaw" }, + { "chp", "Chipewyan" }, + { "chr", "Cherokee" }, + { "chy", "Cheyenne" }, + { "cic", "Chickasaw" }, + { "ckb", "Central Kurdish" }, + { "csb", "Kashubian" }, + { "cmn", "Mandarin Chinese" }, + { "co", "Corsican" }, + { "cop", "Coptic" }, + { "cps", "Capiznon" }, + { "cr", "Cree" }, + { "crh", "Crimean Tatar" }, + { "crs", "Seselwa Creole French" }, + { "cs", "Czech" }, + { "csb", "Kashubian" }, + { "cu", "Church Slavic" }, + { "cv", "Chuvash" }, + { "cy", "Welsh" }, + { "da", "Danish" }, + { "dak", "Dakota" }, + { "dar", "Dargwa" }, + { "dav", "Taita" }, + { "de", "German" }, + { "del", "Delaware" }, + { "den", "Slave" }, + { "dgr", "Dogrib" }, + { "din", "Dinka" }, + { "dje", "Zarma" }, + { "doi", "Dogri" }, + { "dsb", "Lower Sorbian" }, + { "dtp", "Central Dusun" }, + { "dua", "Duala" }, + { "dum", "Middle Dutch" }, + { "dv", "Dhivehi" }, + { "dyo", "Jola-Fonyi" }, + { "dyu", "Dyula" }, + { "dz", "Dzongkha" }, + { "dzg", "Dazaga" }, + { "ebu", "Embu" }, + { "ee", "Ewe" }, + { "efi", "Efik" }, + { "egl", "Emilian" }, + { "egy", "Ancient Egyptian" }, + { "eka", "Ekajuk" }, + { "el", "Greek" }, + { "elx", "Elamite" }, + { "en", "English" }, + { "enm", "Middle English" }, + { "eo", "Esperanto" }, + { "es", "Spanish" }, + { "esu", "Central Yupik" }, + { "et", "Estonian" }, + { "eu", "Basque" }, + { "ewo", "Ewondo" }, + { "ext", "Extremaduran" }, + { "fa", "Persian" }, + { "fan", "Fang" }, + { "fat", "Fanti" }, + { "ff", "Fulah" }, + { "fi", "Finnish" }, + { "fil", "Filipino" }, + { "fit", "Tornedalen Finnish" }, + { "fj", "Fijian" }, + { "fo", "Faroese" }, + { "fon", "Fon" }, + { "fr", "French" }, + { "frc", "Cajun French" }, + { "frm", "Middle French" }, + { "fro", "Old French" }, + { "frp", "Arpitan" }, + { "frr", "Northern Frisian" }, + { "frs", "Eastern Frisian" }, + { "fur", "Friulian" }, + { "fy", "Western Frisian" }, + { "ga", "Irish" }, + { "gaa", "Ga" }, + { "gag", "Gagauz" }, + { "gan", "Gan Chinese" }, + { "gay", "Gayo" }, + { "gba", "Gbaya" }, + { "gbz", "Zoroastrian Dari" }, + { "gd", "Scottish Gaelic" }, + { "gez", "Geez" }, + { "gil", "Gilbertese" }, + { "gl", "Galician" }, + { "glk", "Gilaki" }, + { "gmh", "Middle High German" }, + { "gn", "Guarani" }, + { "goh", "Old High German" }, + { "gom", "Goan Konkani" }, + { "gon", "Gondi" }, + { "gor", "Gorontalo" }, + { "got", "Gothic" }, + { "grb", "Grebo" }, + { "grc", "Ancient Greek" }, + { "gsw", "Swiss German" }, + { "gu", "Gujarati" }, + { "guc", "Wayuu" }, + { "gur", "Frafra" }, + { "guz", "Gusii" }, + { "gv", "Manx" }, + { "gwi", "Gwichʼin" }, + { "ha", "Hausa" }, + { "hai", "Haida" }, + { "hak", "Hakka Chinese" }, + { "haw", "Hawaiian" }, + { "he", "Hebrew" }, + { "hi", "Hindi" }, + { "hif", "Fiji Hindi" }, + { "hil", "Hiligaynon" }, + { "hit", "Hittite" }, + { "hmn", "Hmong" }, + { "ho", "Hiri Motu" }, + { "hne", "Chhattisgarhi" }, + { "hr", "Croatian" }, + { "hsb", "Upper Sorbian" }, + { "hsn", "Xiang Chinese" }, + { "ht", "Haitian" }, + { "hu", "Hungarian" }, + { "hup", "Hupa" }, + { "hus", "Huastec" }, + { "hy", "Armenian" }, + { "hz", "Herero" }, + { "ia", "Interlingua" }, + { "iba", "Iban" }, + { "ibb", "Ibibio" }, + { "id", "Indonesian" }, + { "ie", "Interlingue" }, + { "ig", "Igbo" }, + { "ii", "Sichuan Yi" }, + { "ik", "Inupiaq" }, + { "ilo", "Iloko" }, + { "inh", "Ingush" }, + { "io", "Ido" }, + { "is", "Icelandic" }, + { "it", "Italian" }, + { "iu", "Inuktitut" }, + { "izh", "Ingrian" }, + { "ja", "Japanese" }, + { "jam", "Jamaican Creole English" }, + { "jbo", "Lojban" }, + { "jgo", "Ngomba" }, + { "jmc", "Machame" }, + { "jpr", "Judeo-Persian" }, + { "jrb", "Judeo-Arabic" }, + { "jut", "Jutish" }, + { "jv", "Javanese" }, + { "ka", "Georgian" }, + { "kaa", "Kara-Kalpak" }, + { "kab", "Kabyle" }, + { "kac", "Kachin" }, + { "kaj", "Jju" }, + { "kam", "Kamba" }, + { "kaw", "Kawi" }, + { "kbd", "Kabardian" }, + { "kbl", "Kanembu" }, + { "kcg", "Tyap" }, + { "kde", "Makonde" }, + { "kea", "Kabuverdianu" }, + { "ken", "Kenyang" }, + { "kfo", "Koro" }, + { "kg", "Kongo" }, + { "kgp", "Kaingang" }, + { "kha", "Khasi" }, + { "kho", "Khotanese" }, + { "khq", "Koyra Chiini" }, + { "khw", "Khowar" }, + { "ki", "Kikuyu" }, + { "kiu", "Kirmanjki" }, + { "kj", "Kuanyama" }, + { "kk", "Kazakh" }, + { "kkj", "Kako" }, + { "kl", "Kalaallisut" }, + { "kln", "Kalenjin" }, + { "km", "Central Khmer" }, + { "kmb", "Kimbundu" }, + { "kn", "Kannada" }, + { "ko", "Korean" }, + { "koi", "Komi-Permyak" }, + { "kok", "Konkani" }, + { "kos", "Kosraean" }, + { "kpe", "Kpelle" }, + { "kr", "Kanuri" }, + { "krc", "Karachay-Balkar" }, + { "kri", "Krio" }, + { "krj", "Kinaray-a" }, + { "krl", "Karelian" }, + { "kru", "Kurukh" }, + { "ks", "Kashmiri" }, + { "ksb", "Shambala" }, + { "ksf", "Bafia" }, + { "ksh", "Colognian" }, + { "ku", "Kurdish" }, + { "kum", "Kumyk" }, + { "kut", "Kutenai" }, + { "kv", "Komi" }, + { "kw", "Cornish" }, + { "ky", "Kirghiz" }, + { "lag", "Langi" }, + { "la", "Latin" }, + { "lad", "Ladino" }, + { "lag", "Langi" }, + { "lah", "Lahnda" }, + { "lam", "Lamba" }, + { "lb", "Luxembourgish" }, + { "lez", "Lezghian" }, + { "lfn", "Lingua Franca Nova" }, + { "lg", "Ganda" }, + { "li", "Limburgan" }, + { "lij", "Ligurian" }, + { "liv", "Livonian" }, + { "lkt", "Lakota" }, + { "lmo", "Lombard" }, + { "ln", "Lingala" }, + { "lo", "Lao" }, + { "lol", "Mongo" }, + { "lou", "Louisiana Creole" }, + { "loz", "Lozi" }, + { "lrc", "Northern Luri" }, + { "lt", "Lithuanian" }, + { "ltg", "Latgalian" }, + { "lu", "Luba-Katanga" }, + { "lua", "Luba-Lulua" }, + { "lui", "Luiseno" }, + { "lun", "Lunda" }, + { "luo", "Luo" }, + { "lus", "Mizo" }, + { "luy", "Luyia" }, + { "lv", "Latvian" }, + { "lzh", "Literary Chinese" }, + { "lzz", "Laz" }, + { "mad", "Madurese" }, + { "maf", "Mafa" }, + { "mag", "Magahi" }, + { "mai", "Maithili" }, + { "mak", "Makasar" }, + { "man", "Mandingo" }, + { "mas", "Masai" }, + { "mde", "Maba" }, + { "mdf", "Moksha" }, + { "mdr", "Mandar" }, + { "men", "Mende" }, + { "mer", "Meru" }, + { "mfe", "Morisyen" }, + { "mg", "Malagasy" }, + { "mga", "Middle Irish" }, + { "mgh", "Makhuwa-Meetto" }, + { "mgo", "Metaʼ" }, + { "mh", "Marshallese" }, + { "mhr", "Eastern Mari" }, + { "mi", "Māori" }, + { "mic", "Mi'kmaq" }, + { "min", "Minangkabau" }, + { "miq", "Mískito" }, + { "mjw", "Karbi" }, + { "mk", "Macedonian" }, + { "ml", "Malayalam" }, + { "mn", "Mongolian" }, + { "mnc", "Manchu" }, + { "mni", "Manipuri" }, + { "mnw", "Mon" }, + { "mos", "Mossi" }, + { "moh", "Mohawk" }, + { "mr", "Marathi" }, + { "mrj", "Western Mari" }, + { "ms", "Malay" }, + { "mt", "Maltese" }, + { "mua", "Mundang" }, + { "mus", "Muscogee" }, + { "mwl", "Mirandese" }, + { "mwr", "Marwari" }, + { "mwv", "Mentawai" }, + { "my", "Burmese" }, + { "mye", "Myene" }, + { "myv", "Erzya" }, + { "mzn", "Mazanderani" }, + { "na", "Nauru" }, + { "nah", "Nahuatl" }, + { "nan", "Min Nan Chinese" }, + { "nap", "Neapolitan" }, + { "naq", "Nama" }, + { "nan", "Min Nan Chinese" }, + { "nb", "Norwegian Bokmål" }, + { "nd", "North Ndebele" }, + { "nds", "Low German" }, + { "ne", "Nepali" }, + { "new", "Newari" }, + { "nhn", "Central Nahuatl" }, + { "ng", "Ndonga" }, + { "nia", "Nias" }, + { "niu", "Niuean" }, + { "njo", "Ao Naga" }, + { "nl", "Dutch" }, + { "nmg", "Kwasio" }, + { "nn", "Norwegian Nynorsk" }, + { "nnh", "Ngiemboon" }, + { "nog", "Nogai" }, + { "non", "Old Norse" }, + { "nov", "Novial" }, + { "nqo", "N'ko" }, + { "nr", "South Ndebele" }, + { "nso", "Pedi" }, + { "nus", "Nuer" }, + { "nv", "Navajo" }, + { "nwc", "Classical Newari" }, + { "ny", "Nyanja" }, + { "nym", "Nyamwezi" }, + { "nyn", "Nyankole" }, + { "nyo", "Nyoro" }, + { "nzi", "Nzima" }, + { "oc", "Occitan" }, + { "oj", "Ojibwa" }, + { "om", "Oromo" }, + { "or", "Odia" }, + { "os", "Ossetic" }, + { "osa", "Osage" }, + { "ota", "Ottoman Turkish" }, + { "pa", "Panjabi" }, + { "pag", "Pangasinan" }, + { "pal", "Pahlavi" }, + { "pam", "Pampanga" }, + { "pap", "Papiamento" }, + { "pau", "Palauan" }, + { "pcd", "Picard" }, + { "pcm", "Nigerian Pidgin" }, + { "pdc", "Pennsylvania German" }, + { "pdt", "Plautdietsch" }, + { "peo", "Old Persian" }, + { "pfl", "Palatine German" }, + { "phn", "Phoenician" }, + { "pi", "Pali" }, + { "pl", "Polish" }, + { "pms", "Piedmontese" }, + { "pnt", "Pontic" }, + { "pon", "Pohnpeian" }, + { "pr", "Pirate" }, + { "prg", "Prussian" }, + { "pro", "Old Provençal" }, + { "prs", "Dari" }, + { "ps", "Pushto" }, + { "pt", "Portuguese" }, + { "qu", "Quechua" }, + { "quc", "K'iche" }, + { "qug", "Chimborazo Highland Quichua" }, + { "quy", "Ayacucho Quechua" }, + { "quz", "Cusco Quechua" }, + { "raj", "Rajasthani" }, + { "rap", "Rapanui" }, + { "rar", "Rarotongan" }, + { "rgn", "Romagnol" }, + { "rif", "Riffian" }, + { "rm", "Romansh" }, + { "rn", "Rundi" }, + { "ro", "Romanian" }, + { "rof", "Rombo" }, + { "rom", "Romany" }, + { "rtm", "Rotuman" }, + { "ru", "Russian" }, + { "rue", "Rusyn" }, + { "rug", "Roviana" }, + { "rup", "Aromanian" }, + { "rw", "Kinyarwanda" }, + { "rwk", "Rwa" }, + { "sa", "Sanskrit" }, + { "sad", "Sandawe" }, + { "sah", "Sakha" }, + { "sam", "Samaritan Aramaic" }, + { "saq", "Samburu" }, + { "sas", "Sasak" }, + { "sat", "Santali" }, + { "saz", "Saurashtra" }, + { "sba", "Ngambay" }, + { "sbp", "Sangu" }, + { "sc", "Sardinian" }, + { "scn", "Sicilian" }, + { "sco", "Scots" }, + { "sd", "Sindhi" }, + { "sdc", "Sassarese Sardinian" }, + { "sdh", "Southern Kurdish" }, + { "se", "Northern Sami" }, + { "see", "Seneca" }, + { "seh", "Sena" }, + { "sei", "Seri" }, + { "sel", "Selkup" }, + { "ses", "Koyraboro Senni" }, + { "sg", "Sango" }, + { "sga", "Old Irish" }, + { "sgs", "Samogitian" }, + { "sh", "Serbo-Croatian" }, + { "shi", "Tachelhit" }, + { "shn", "Shan" }, + { "shs", "Shuswap" }, + { "shu", "Chadian Arabic" }, + { "si", "Sinhala" }, + { "sid", "Sidamo" }, + { "sk", "Slovak" }, + { "sl", "Slovenian" }, + { "sli", "Lower Silesian" }, + { "sly", "Selayar" }, + { "sm", "Samoan" }, + { "sma", "Southern Sami" }, + { "smj", "Lule Sami" }, + { "smn", "Inari Sami" }, + { "sms", "Skolt Sami" }, + { "sn", "Shona" }, + { "snk", "Soninke" }, + { "so", "Somali" }, + { "sog", "Sogdien" }, + { "son", "Songhai" }, + { "sq", "Albanian" }, + { "sr", "Serbian" }, + { "srn", "Sranan Tongo" }, + { "srr", "Serer" }, + { "ss", "Swati" }, + { "ssy", "Saho" }, + { "st", "Southern Sotho" }, + { "stq", "Saterland Frisian" }, + { "su", "Sundanese" }, + { "suk", "Sukuma" }, + { "sus", "Susu" }, + { "sux", "Sumerian" }, + { "sv", "Swedish" }, + { "sw", "Swahili" }, + { "swb", "Comorian" }, + { "swc", "Congo Swahili" }, + { "syc", "Classical Syriac" }, + { "syr", "Syriac" }, + { "szl", "Silesian" }, + { "ta", "Tamil" }, + { "tcy", "Tulu" }, + { "te", "Telugu" }, + { "tem", "Timne" }, + { "teo", "Teso" }, + { "ter", "Tereno" }, + { "tet", "Tetum" }, + { "tg", "Tajik" }, + { "th", "Thai" }, + { "the", "Chitwania Tharu" }, + { "ti", "Tigrinya" }, + { "tig", "Tigre" }, + { "tiv", "Tiv" }, + { "tk", "Turkmen" }, + { "tkl", "Tokelau" }, + { "tkr", "Tsakhur" }, + { "tl", "Tagalog" }, + { "tlh", "Klingon" }, + { "tli", "Tlingit" }, + { "tly", "Talysh" }, + { "tmh", "Tamashek" }, + { "tn", "Tswana" }, + { "to", "Tongan" }, + { "tog", "Nyasa Tonga" }, + { "tpi", "Tok Pisin" }, + { "tr", "Turkish" }, + { "tru", "Turoyo" }, + { "trv", "Taroko" }, + { "ts", "Tsonga" }, + { "tsd", "Tsakonian" }, + { "tsi", "Tsimshian" }, + { "tt", "Tatar" }, + { "ttt", "Muslim Tat" }, + { "tum", "Tumbuka" }, + { "tvl", "Tuvalu" }, + { "tw", "Twi" }, + { "twq", "Tasawaq" }, + { "ty", "Tahitian" }, + { "tyv", "Tuvinian" }, + { "tzm", "Central Atlas Tamazight" }, + { "udm", "Udmurt" }, + { "ug", "Uyghur" }, + { "uga", "Ugaritic" }, + { "uk", "Ukrainian" }, + { "umb", "Umbundu" }, + { "unm", "Unami" }, + { "ur", "Urdu" }, + { "uz", "Uzbek" }, + { "vai", "Vai" }, + { "ve", "Venda" }, + { "vec", "Venetian" }, + { "vep", "Veps" }, + { "vi", "Vietnamese" }, + { "vls", "West Flemish" }, + { "vmf", "Main-Franconian" }, + { "vo", "Volapük" }, + { "vot", "Votic" }, + { "vro", "Võro" }, + { "vun", "Vunjo" }, + { "wa", "Walloon" }, + { "wae", "Walser" }, + { "wal", "Wolaytta" }, + { "war", "Waray" }, + { "was", "Washo" }, + { "wbp", "Warlpiri" }, + { "wo", "Wolof" }, + { "wuu", "Wu Chinese" }, + { "xal", "Kalmyk" }, + { "xh", "Xhosa" }, + { "xmf", "Mingrelian" }, + { "xog", "Soga" }, + { "yao", "Yao" }, + { "yap", "Yapese" }, + { "yav", "Yangben" }, + { "ybb", "Yemba" }, + { "yi", "Yiddish" }, + { "yo", "Yoruba" }, + { "yrl", "Nheengatu" }, + { "yue", "Yue Chinese" }, + { "yuw", "Papua New Guinea" }, + { "za", "Zhuang" }, + { "zap", "Zapotec" }, + { "zbl", "Blissymbols" }, + { "zea", "Zeelandic" }, + { "zen", "Zenaga" }, + { "zgh", "Standard Moroccan Tamazight" }, + { "zh", "Chinese" }, + { "zu", "Zulu" }, + { "zun", "Zuni" }, + { "zza", "Zaza" }, + { nullptr, nullptr } +}; + +// Additional regional variants. +// Variant name, supported languages. + +static const char *locale_variants[][2] = { + { "valencia", "ca" }, + { "iqtelif", "tt" }, + { "saaho", "aa" }, + { "tradnl", "es" }, + { nullptr, nullptr }, +}; + +// Script names and codes (excludes typographic variants, special codes, reserved codes and aliases for combined scripts). +// Reference: +// - https://en.wikipedia.org/wiki/ISO_15924 + +static const char *script_list[][2] = { + { "Adlam", "Adlm" }, + { "Afaka", "Afak" }, + { "Caucasian Albanian", "Aghb" }, + { "Ahom", "Ahom" }, + { "Arabic", "Arab" }, + { "Imperial Aramaic", "Armi" }, + { "Armenian", "Armn" }, + { "Avestan", "Avst" }, + { "Balinese", "Bali" }, + { "Bamum", "Bamu" }, + { "Bassa Vah", "Bass" }, + { "Batak", "Batk" }, + { "Bengali", "Beng" }, + { "Bhaiksuki", "Bhks" }, + { "Blissymbols", "Blis" }, + { "Bopomofo", "Bopo" }, + { "Brahmi", "Brah" }, + { "Braille", "Brai" }, + { "Buginese", "Bugi" }, + { "Buhid", "Buhd" }, + { "Chakma", "Cakm" }, + { "Unified Canadian Aboriginal", "Cans" }, + { "Carian", "Cari" }, + { "Cham", "Cham" }, + { "Cherokee", "Cher" }, + { "Chorasmian", "Chrs" }, + { "Cirth", "Cirt" }, + { "Coptic", "Copt" }, + { "Cypro-Minoan", "Cpmn" }, + { "Cypriot", "Cprt" }, + { "Cyrillic", "Cyrl" }, + { "Devanagari", "Deva" }, + { "Dives Akuru", "Diak" }, + { "Dogra", "Dogr" }, + { "Deseret", "Dsrt" }, + { "Duployan", "Dupl" }, + { "Egyptian demotic", "Egyd" }, + { "Egyptian hieratic", "Egyh" }, + { "Egyptian hieroglyphs", "Egyp" }, + { "Elbasan", "Elba" }, + { "Elymaic", "Elym" }, + { "Ethiopic", "Ethi" }, + { "Khutsuri", "Geok" }, + { "Georgian", "Geor" }, + { "Glagolitic", "Glag" }, + { "Gunjala Gondi", "Gong" }, + { "Masaram Gondi", "Gonm" }, + { "Gothic", "Goth" }, + { "Grantha", "Gran" }, + { "Greek", "Grek" }, + { "Gujarati", "Gujr" }, + { "Gurmukhi", "Guru" }, + { "Hangul", "Hang" }, + { "Han", "Hani" }, + { "Hanunoo", "Hano" }, + { "Simplified", "Hans" }, + { "Traditional", "Hant" }, + { "Hatran", "Hatr" }, + { "Hebrew", "Hebr" }, + { "Hiragana", "Hira" }, + { "Anatolian Hieroglyphs", "Hluw" }, + { "Pahawh Hmong", "Hmng" }, + { "Nyiakeng Puachue Hmong", "Hmnp" }, + { "Old Hungarian", "Hung" }, + { "Indus", "Inds" }, + { "Old Italic", "Ital" }, + { "Javanese", "Java" }, + { "Jurchen", "Jurc" }, + { "Kayah Li", "Kali" }, + { "Katakana", "Kana" }, + { "Kharoshthi", "Khar" }, + { "Khmer", "Khmr" }, + { "Khojki", "Khoj" }, + { "Khitan large script", "Kitl" }, + { "Khitan small script", "Kits" }, + { "Kannada", "Knda" }, + { "Kpelle", "Kpel" }, + { "Kaithi", "Kthi" }, + { "Tai Tham", "Lana" }, + { "Lao", "Laoo" }, + { "Latin", "Latn" }, + { "Leke", "Leke" }, + { "Lepcha", "Lepc" }, + { "Limbu", "Limb" }, + { "Linear A", "Lina" }, + { "Linear B", "Linb" }, + { "Lisu", "Lisu" }, + { "Loma", "Loma" }, + { "Lycian", "Lyci" }, + { "Lydian", "Lydi" }, + { "Mahajani", "Mahj" }, + { "Makasar", "Maka" }, + { "Mandaic", "Mand" }, + { "Manichaean", "Mani" }, + { "Marchen", "Marc" }, + { "Mayan Hieroglyphs", "Maya" }, + { "Medefaidrin", "Medf" }, + { "Mende Kikakui", "Mend" }, + { "Meroitic Cursive", "Merc" }, + { "Meroitic Hieroglyphs", "Mero" }, + { "Malayalam", "Mlym" }, + { "Modi", "Modi" }, + { "Mongolian", "Mong" }, + { "Moon", "Moon" }, + { "Mro", "Mroo" }, + { "Meitei Mayek", "Mtei" }, + { "Multani", "Mult" }, + { "Myanmar (Burmese)", "Mymr" }, + { "Nandinagari", "Nand" }, + { "Old North Arabian", "Narb" }, + { "Nabataean", "Nbat" }, + { "Newa", "Newa" }, + { "Naxi Dongba", "Nkdb" }, + { "Nakhi Geba", "Nkgb" }, + { "N'ko", "Nkoo" }, + { "Nüshu", "Nshu" }, + { "Ogham", "Ogam" }, + { "Ol Chiki", "Olck" }, + { "Old Turkic", "Orkh" }, + { "Oriya", "Orya" }, + { "Osage", "Osge" }, + { "Osmanya", "Osma" }, + { "Old Uyghur", "Ougr" }, + { "Palmyrene", "Palm" }, + { "Pau Cin Hau", "Pauc" }, + { "Proto-Cuneiform", "Pcun" }, + { "Proto-Elamite", "Pelm" }, + { "Old Permic", "Perm" }, + { "Phags-pa", "Phag" }, + { "Inscriptional Pahlavi", "Phli" }, + { "Psalter Pahlavi", "Phlp" }, + { "Book Pahlavi", "Phlv" }, + { "Phoenician", "Phnx" }, + { "Klingon", "Piqd" }, + { "Miao", "Plrd" }, + { "Inscriptional Parthian", "Prti" }, + { "Proto-Sinaitic", "Psin" }, + { "Ranjana", "Ranj" }, + { "Rejang", "Rjng" }, + { "Hanifi Rohingya", "Rohg" }, + { "Rongorongo", "Roro" }, + { "Runic", "Runr" }, + { "Samaritan", "Samr" }, + { "Sarati", "Sara" }, + { "Old South Arabian", "Sarb" }, + { "Saurashtra", "Saur" }, + { "SignWriting", "Sgnw" }, + { "Shavian", "Shaw" }, + { "Sharada", "Shrd" }, + { "Shuishu", "Shui" }, + { "Siddham", "Sidd" }, + { "Khudawadi", "Sind" }, + { "Sinhala", "Sinh" }, + { "Sogdian", "Sogd" }, + { "Old Sogdian", "Sogo" }, + { "Sora Sompeng", "Sora" }, + { "Soyombo", "Soyo" }, + { "Sundanese", "Sund" }, + { "Syloti Nagri", "Sylo" }, + { "Syriac", "Syrc" }, + { "Tagbanwa", "Tagb" }, + { "Takri", "Takr" }, + { "Tai Le", "Tale" }, + { "New Tai Lue", "Talu" }, + { "Tamil", "Taml" }, + { "Tangut", "Tang" }, + { "Tai Viet", "Tavt" }, + { "Telugu", "Telu" }, + { "Tengwar", "Teng" }, + { "Tifinagh", "Tfng" }, + { "Tagalog", "Tglg" }, + { "Thaana", "Thaa" }, + { "Thai", "Thai" }, + { "Tibetan", "Tibt" }, + { "Tirhuta", "Tirh" }, + { "Tangsa", "Tnsa" }, + { "Toto", "Toto" }, + { "Ugaritic", "Ugar" }, + { "Vai", "Vaii" }, + { "Visible Speech", "Visp" }, + { "Vithkuqi", "Vith" }, + { "Warang Citi", "Wara" }, + { "Wancho", "Wcho" }, + { "Woleai", "Wole" }, + { "Old Persian", "Xpeo" }, + { "Cuneiform", "Xsux" }, + { "Yezidi", "Yezi" }, + { "Yi", "Yiii" }, + { "Zanabazar Square", "Zanb" }, + { nullptr, nullptr } +}; + +#endif // LOCALES_H diff --git a/core/object.h b/core/object.h index 3e025771b3a..e43781bd1d6 100644 --- a/core/object.h +++ b/core/object.h @@ -96,6 +96,7 @@ enum PropertyHint { PROPERTY_HINT_NODE_PATH_VALID_TYPES, PROPERTY_HINT_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog PROPERTY_HINT_ENUM_SUGGESTION, ///< hint_text= "val1,val2,val3,etc" + PROPERTY_HINT_LOCALE_ID, PROPERTY_HINT_MAX, // When updating PropertyHint, also sync the hardcoded list in VisualScriptEditorVariableEdit }; diff --git a/core/translation.cpp b/core/translation.cpp index 16384c15fe7..bb24d81e036 100644 --- a/core/translation.cpp +++ b/core/translation.cpp @@ -31,791 +31,10 @@ #include "translation.h" #include "core/io/resource_loader.h" +#include "core/locales.h" #include "core/os/os.h" #include "core/project_settings.h" -// 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 } -}; - -/////////////////////////////////////////////// - PoolVector Translation::_get_messages() const { PoolVector msgs; msgs.resize(translation_map.size() * 2); @@ -853,17 +72,7 @@ void Translation::_set_messages(const PoolVector &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()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); @@ -964,68 +173,294 @@ StringName ContextTranslation::get_context_message(const StringName &p_src_text, /////////////////////////////////////////////// -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; +static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) { + return (c >= 'A' && c <= 'Z'); } -String TranslationServer::standardize_locale(const String &p_locale) { - // Replaces '-' with '_' for macOS Sierra-style locales - String univ_locale = p_locale.replace("-", "_"); +static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) { + return (c >= 'a' && c <= 'z'); +} - // Handles known non-ISO locale names used e.g. on Windows +Vector TranslationServer::locale_script_info; + +Map TranslationServer::language_map; +Map TranslationServer::script_map; +Map TranslationServer::locale_rename_map; +Map TranslationServer::country_name_map; +Map TranslationServer::variant_map; +Map 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 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]); + } + locale_script_info.push_back(info); + idx++; + } + + // 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++; + } + + // 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++; + } + + // 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]).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]).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 locale_elements = univ_locale.get_slice("@", 0).split("_"); + lang = locale_elements[0]; + if (locale_elements.size() >= 2) { + if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { + script = locale_elements[1]; + } + if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_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_ascii_upper_case(locale_elements[2][0]) && is_ascii_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(); + } } - return p_locale.left(split); + 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(); + } + } + + // Try extract script and variant from the extra part. + Vector 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(); + } + } + + // Handles known non-ISO language names used e.g. on Windows. + if (locale_rename_map.has(lang)) { + lang = locale_rename_map[lang]; + } + + // 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.empty()) { + for (int i = 0; i < locale_script_info.size(); i++) { + const LocaleScriptInfo &info = locale_script_info[i]; + if (info.name == lang) { + if (country.empty() || info.supported_countries.has(country)) { + script = info.script; + break; + } + } + } + } + if (!script.empty() && country.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.empty()) { + locale = locale + "_" + script; + } + if (!country.empty()) { + locale = locale + "_" + country; + } + if (!variant.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 locale_a_elements = locale_a.split("_"); + Vector 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 { + // No match. + return 0; + } +} + +String TranslationServer::get_locale_name(const String &p_locale) const { + String locale = standardize_locale(p_locale); + + String lang, script, country; + Vector locale_elements = locale.split("_"); + lang = locale_elements[0]; + if (locale_elements.size() >= 2) { + if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { + script = locale_elements[1]; + } + if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { + country = locale_elements[1]; + } + } + if (locale_elements.size() >= 3) { + if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { + country = locale_elements[2]; + } + } + + String name = language_map[lang]; + if (!script.empty()) { + name = name + " (" + script_map[script] + ")"; + } + if (!country.empty()) { + name = name + ", " + country_name_map[country]; + } + return name; +} + +Vector TranslationServer::get_all_languages() const { + Vector languages; + + for (Map::Element *E = language_map.front(); E; E = E->next()) { + languages.push_back(E->key()); + } + + return languages; +} + +String TranslationServer::get_language_name(const String &p_language) const { + return language_map[p_language]; +} + +Vector TranslationServer::get_all_scripts() const { + Vector scripts; + + for (Map::Element *E = script_map.front(); E; E = E->next()) { + scripts.push_back(E->key()); + } + + return scripts; +} + +String TranslationServer::get_script_name(const String &p_script) const { + return script_map[p_script]; +} + +Vector TranslationServer::get_all_countries() const { + Vector countries; + + for (Map::Element *E = country_name_map.front(); E; E = E->next()) { + countries.push_back(E->key()); + } + + return countries; +} + +String TranslationServer::get_country_name(const String &p_country) const { + return country_name_map[p_country]; } 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; - } + locale = standardize_locale(p_locale); if (OS::get_singleton()->get_main_loop()) { OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); @@ -1038,50 +473,14 @@ 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>::Element *E = translations.front(); E; E = E->next()) { + for (Set>::Element *E = translations.front(); E; E = E->next()) { const Ref &t = E->get(); ERR_FAIL_COND_V(t.is_null(), Array()); String l = t->get_locale(); - if (!locales.has(l)) { - locales.push_back(l); - } - } - - locales.sort(); - return locales; -} - -Vector TranslationServer::get_all_locales() { - Vector locales; - - const char **ptr = locale_list; - - while (*ptr) { - locales.push_back(*ptr); - ptr++; - } - - return locales; -} - -Vector TranslationServer::get_all_locale_names() { - Vector locales; - - const char **ptr = locale_names; - - while (*ptr) { - locales.push_back(String::utf8(*ptr)); - ptr++; + locales.push_back(l); } return locales; @@ -1105,81 +504,48 @@ StringName TranslationServer::translate(const StringName &p_message) const { return p_message; } - ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid."); - - // 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(locale); - bool near_match = false; + int best_score = 0; for (const Set>::Element *E = translations.front(); E; E = E->next()) { const Ref &t = E->get(); ERR_FAIL_COND_V(t.is_null(), p_message); String l = t->get_locale(); - bool exact_match = (l == locale); - if (!exact_match) { - if (near_match) { - continue; // Only near-match once, but keep looking for exact matches. + int score = compare_locales(locale, l); + if (score > 0 && score >= best_score) { + StringName r = t->get_message(p_message); + if (!r) { + continue; } - if (get_language_code(l) != lang) { - continue; // Language code does not match. + res = r; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. } } - - StringName r = t->get_message(p_message); - if (!r) { - continue; - } - res = r; - - if (exact_match) { - break; - } else { - near_match = true; - } } if (!res && fallback.length() >= 2) { - // Try again with the fallback locale. - String fallback_lang = get_language_code(fallback); - near_match = false; + best_score = 0; for (const Set>::Element *E = translations.front(); E; E = E->next()) { const Ref &t = E->get(); ERR_FAIL_COND_V(t.is_null(), p_message); String l = t->get_locale(); - bool exact_match = (l == fallback); - if (!exact_match) { - if (near_match) { - continue; // Only near-match once, but keep looking for exact matches. + int score = compare_locales(fallback, l); + if (score > 0 && score >= best_score) { + StringName r = t->get_message(p_message); + if (!r) { + continue; } - if (get_language_code(l) != fallback_lang) { - continue; // Language code does not match. + res = r; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. } } - - StringName r = t->get_message(p_message); - if (!r) { - continue; - } - res = r; - - if (exact_match) { - break; - } else { - near_match = true; - } } } @@ -1224,18 +590,7 @@ void TranslationServer::setup() { } 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)); - } + ProjectSettings::get_singleton()->set_custom_property_info("locale/fallback", PropertyInfo(Variant::STRING, "locale/fallback", PROPERTY_HINT_LOCALE_ID, "")); #endif } @@ -1271,6 +626,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"), &TranslationServer::translate); @@ -1298,7 +665,5 @@ TranslationServer::TranslationServer() : enabled(true) { 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(); } diff --git a/core/translation.h b/core/translation.h index 6bd98e1e23d..d975f32e389 100644 --- a/core/translation.h +++ b/core/translation.h @@ -87,8 +87,6 @@ class TranslationServer : public Object { Ref tool_translation; Ref doc_translation; - Map locale_name_map; - bool enabled; static TranslationServer *singleton; @@ -96,6 +94,23 @@ class TranslationServer : public Object { static void _bind_methods(); + struct LocaleScriptInfo { + String name; + String script; + String default_country; + Set supported_countries; + }; + static Vector locale_script_info; + + static Map language_map; + static Map script_map; + static Map locale_rename_map; + static Map country_name_map; + static Map country_rename_map; + static Map variant_map; + + void init_locale_info(); + public: _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; } @@ -105,6 +120,18 @@ public: void set_locale(const String &p_locale); String get_locale() const; + int compare_locales(const String &p_locale_a, const String &p_locale_b) const; + String standardize_locale(const String &p_locale) const; + + Vector get_all_languages() const; + String get_language_name(const String &p_language) const; + + Vector get_all_scripts() const; + String get_script_name(const String &p_script) const; + + Vector get_all_countries() const; + String get_country_name(const String &p_country) const; + String get_locale_name(const String &p_locale) const; Array get_loaded_locales() const; @@ -114,12 +141,6 @@ public: StringName translate(const StringName &p_message) const; - static Vector get_all_locales(); - static Vector get_all_locale_names(); - static bool is_locale_valid(const String &p_locale); - static String standardize_locale(const String &p_locale); - static String get_language_code(const String &p_locale); - void set_tool_translation(const Ref &p_translation); StringName tool_translate(const StringName &p_message, const StringName &p_context) const; void set_doc_translation(const Ref &p_translation); diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index bc664f13113..dd13931cac7 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -1485,6 +1485,9 @@ Hints that an image is compressed using lossless compression. + + Hints that a string property is a locale code. Editing it will show a locale dialog for picking language and country. + The property is serialized and saved in the scene file (default). diff --git a/doc/classes/TranslationServer.xml b/doc/classes/TranslationServer.xml index b1d4386879a..02da43b5ee9 100644 --- a/doc/classes/TranslationServer.xml +++ b/doc/classes/TranslationServer.xml @@ -24,6 +24,46 @@ Clears the server from all translations. + + + + + + Compares two locales and return similarity score between [code]0[/code](no match) and [code]10[/code](full match). + + + + + + Returns array of known country codes. + + + + + + Returns array of known language codes. + + + + + + Returns array of known script codes. + + + + + + + Returns readable country name for the [code]country[/code] code. + + + + + + + Returns readable language name for the [code]language[/code] code. + + @@ -44,6 +84,13 @@ Returns a locale's language and its variant (e.g. [code]"en_US"[/code] would return [code]"English (United States)"[/code]). + + + + + Returns readable script name for the [code]script[/code] code. + + @@ -59,6 +106,13 @@ If translations have been loaded beforehand for the new locale, they will be applied. + + + + + Retunrs [code]locale[/code] string standardized to match known locales (e.g. [code]en-US[/code] would be matched to [code]en_US[/code]). + + diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp new file mode 100644 index 00000000000..c635020426e --- /dev/null +++ b/editor/editor_locale_dialog.cpp @@ -0,0 +1,572 @@ +/*************************************************************************/ +/* editor_locale_dialog.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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 "editor_locale_dialog.h" + +#include "core/project_settings.h" +#include "editor/editor_node.h" +#include "editor/editor_scale.h" +#include "scene/gui/check_button.h" +#include "scene/gui/line_edit.h" +#include "scene/gui/option_button.h" +#include "scene/gui/tree.h" + +static _FORCE_INLINE_ bool is_ascii_upper_case(char32_t c) { + return (c >= 'A' && c <= 'Z'); +} + +static _FORCE_INLINE_ bool is_ascii_lower_case(char32_t c) { + return (c >= 'a' && c <= 'z'); +} + +void EditorLocaleDialog::_bind_methods() { + ClassDB::bind_method(D_METHOD("_filter_mode_changed"), &EditorLocaleDialog::_filter_mode_changed); + ClassDB::bind_method(D_METHOD("_edit_filters"), &EditorLocaleDialog::_edit_filters); + ClassDB::bind_method(D_METHOD("_toggle_advanced"), &EditorLocaleDialog::_toggle_advanced); + ClassDB::bind_method(D_METHOD("_item_selected"), &EditorLocaleDialog::_item_selected); + ClassDB::bind_method(D_METHOD("_filter_lang_option_changed"), &EditorLocaleDialog::_filter_lang_option_changed); + ClassDB::bind_method(D_METHOD("_filter_script_option_changed"), &EditorLocaleDialog::_filter_script_option_changed); + ClassDB::bind_method(D_METHOD("_filter_cnt_option_changed"), &EditorLocaleDialog::_filter_cnt_option_changed); + + ADD_SIGNAL(MethodInfo("locale_selected", PropertyInfo(Variant::STRING, "locale"))); +} + +void EditorLocaleDialog::ok_pressed() { + if (edit_filters->is_pressed()) { + return; // Do not update, if in filter edit mode. + } + + String locale; + if (lang_code->get_text().empty()) { + return; // Language code is required. + } + locale = lang_code->get_text(); + + if (!script_code->get_text().empty()) { + locale += "_" + script_code->get_text(); + } + if (!country_code->get_text().empty()) { + locale += "_" + country_code->get_text(); + } + if (!variant_code->get_text().empty()) { + locale += "_" + variant_code->get_text(); + } + + emit_signal("locale_selected", TranslationServer::get_singleton()->standardize_locale(locale)); + hide(); +} + +void EditorLocaleDialog::_item_selected() { + if (updating_lists) { + return; + } + + if (edit_filters->is_pressed()) { + return; // Do not update, if in filter edit mode. + } + + TreeItem *l = lang_list->get_selected(); + if (l) { + lang_code->set_text(l->get_metadata(0).operator String()); + } + + TreeItem *s = script_list->get_selected(); + if (s) { + script_code->set_text(s->get_metadata(0).operator String()); + } + + TreeItem *c = cnt_list->get_selected(); + if (c) { + country_code->set_text(c->get_metadata(0).operator String()); + } +} + +void EditorLocaleDialog::_toggle_advanced(bool p_checked) { + if (!p_checked) { + script_code->set_text(""); + variant_code->set_text(""); + } + _update_tree(); +} + +void EditorLocaleDialog::_post_popup() { + ConfirmationDialog::_post_popup(); + + if (!locale_set) { + lang_code->set_text(""); + script_code->set_text(""); + country_code->set_text(""); + variant_code->set_text(""); + } + edit_filters->set_pressed(false); + _update_tree(); +} + +void EditorLocaleDialog::_filter_lang_option_changed() { + TreeItem *t = lang_list->get_edited(); + String lang = t->get_metadata(0); + bool checked = t->is_checked(0); + + Variant prev; + Array f_lang_all; + + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) { + f_lang_all = ProjectSettings::get_singleton()->get("internationalization/locale/language_filter"); + prev = f_lang_all; + } + + int l_idx = f_lang_all.find(lang); + + if (checked) { + if (l_idx == -1) { + f_lang_all.append(lang); + } + } else { + if (l_idx != -1) { + f_lang_all.remove(l_idx); + } + } + + f_lang_all.sort(); + + undo_redo->create_action(TTR("Changed Locale Language Filter")); + undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", f_lang_all); + undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/language_filter", prev); + undo_redo->commit_action(); +} + +void EditorLocaleDialog::_filter_script_option_changed() { + TreeItem *t = script_list->get_edited(); + String script = t->get_metadata(0); + bool checked = t->is_checked(0); + + Variant prev; + Array f_script_all; + + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) { + f_script_all = ProjectSettings::get_singleton()->get("internationalization/locale/script_filter"); + prev = f_script_all; + } + + int l_idx = f_script_all.find(script); + + if (checked) { + if (l_idx == -1) { + f_script_all.append(script); + } + } else { + if (l_idx != -1) { + f_script_all.remove(l_idx); + } + } + + f_script_all.sort(); + + undo_redo->create_action(TTR("Changed Locale Script Filter")); + undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", f_script_all); + undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/script_filter", prev); + undo_redo->commit_action(); +} + +void EditorLocaleDialog::_filter_cnt_option_changed() { + TreeItem *t = cnt_list->get_edited(); + String cnt = t->get_metadata(0); + bool checked = t->is_checked(0); + + Variant prev; + Array f_cnt_all; + + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) { + f_cnt_all = ProjectSettings::get_singleton()->get("internationalization/locale/country_filter"); + prev = f_cnt_all; + } + + int l_idx = f_cnt_all.find(cnt); + + if (checked) { + if (l_idx == -1) { + f_cnt_all.append(cnt); + } + } else { + if (l_idx != -1) { + f_cnt_all.remove(l_idx); + } + } + + f_cnt_all.sort(); + + undo_redo->create_action(TTR("Changed Locale Country Filter")); + undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", f_cnt_all); + undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/country_filter", prev); + undo_redo->commit_action(); +} + +void EditorLocaleDialog::_filter_mode_changed(int p_mode) { + int f_mode = filter_mode->get_selected_id(); + Variant prev; + + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) { + prev = ProjectSettings::get_singleton()->get("internationalization/locale/locale_filter_mode"); + } + + undo_redo->create_action(TTR("Changed Locale Filter Mode")); + undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", f_mode); + undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/locale_filter_mode", prev); + undo_redo->commit_action(); + + _update_tree(); +} + +void EditorLocaleDialog::_edit_filters(bool p_checked) { + _update_tree(); +} + +void EditorLocaleDialog::_update_tree() { + updating_lists = true; + + int filter = SHOW_ALL_LOCALES; + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/locale_filter_mode")) { + filter = ProjectSettings::get_singleton()->get("internationalization/locale/locale_filter_mode"); + } + Array f_lang_all; + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/language_filter")) { + f_lang_all = ProjectSettings::get_singleton()->get("internationalization/locale/language_filter"); + } + Array f_cnt_all; + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/country_filter")) { + f_cnt_all = ProjectSettings::get_singleton()->get("internationalization/locale/country_filter"); + } + Array f_script_all; + if (ProjectSettings::get_singleton()->has_setting("internationalization/locale/script_filter")) { + f_script_all = ProjectSettings::get_singleton()->get("internationalization/locale/script_filter"); + } + bool is_edit_mode = edit_filters->is_pressed(); + + filter_mode->select(filter); + + // Hide text advanced edit and disable OK button if in filter edit mode. + advanced->set_visible(!is_edit_mode); + hb_locale->set_visible(!is_edit_mode && advanced->is_pressed()); + vb_script_list->set_visible(advanced->is_pressed()); + get_ok()->set_disabled(is_edit_mode); + + // Update language list. + lang_list->clear(); + TreeItem *l_root = lang_list->create_item(nullptr); + lang_list->set_hide_root(true); + + Vector languages = TranslationServer::get_singleton()->get_all_languages(); + for (int i = 0; i < languages.size(); i++) { + if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_lang_all.has(languages[i]) || f_lang_all.empty()) { + const String &lang = TranslationServer::get_singleton()->get_language_name(languages[i]); + TreeItem *t = lang_list->create_item(l_root); + if (is_edit_mode) { + t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + t->set_editable(0, true); + t->set_checked(0, f_lang_all.has(languages[i])); + } else if (lang_code->get_text() == languages[i]) { + t->select(0); + } + t->set_text(0, vformat("%s [%s]", lang, languages[i])); + t->set_metadata(0, languages[i]); + } + } + + // Update script list. + script_list->clear(); + TreeItem *s_root = script_list->create_item(nullptr); + script_list->set_hide_root(true); + + if (!is_edit_mode) { + TreeItem *t = script_list->create_item(s_root); + t->set_text(0, "[Default]"); + t->set_metadata(0, ""); + } + + Vector scripts = TranslationServer::get_singleton()->get_all_scripts(); + for (int i = 0; i < scripts.size(); i++) { + if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_script_all.has(scripts[i]) || f_script_all.empty()) { + const String &script = TranslationServer::get_singleton()->get_script_name(scripts[i]); + TreeItem *t = script_list->create_item(s_root); + if (is_edit_mode) { + t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + t->set_editable(0, true); + t->set_checked(0, f_script_all.has(scripts[i])); + } else if (script_code->get_text() == scripts[i]) { + t->select(0); + } + t->set_text(0, vformat("%s [%s]", script, scripts[i])); + t->set_metadata(0, scripts[i]); + } + } + + // Update country list. + cnt_list->clear(); + TreeItem *c_root = cnt_list->create_item(nullptr); + cnt_list->set_hide_root(true); + + if (!is_edit_mode) { + TreeItem *t = cnt_list->create_item(c_root); + t->set_text(0, "[Default]"); + t->set_metadata(0, ""); + } + + Vector countries = TranslationServer::get_singleton()->get_all_countries(); + for (int i = 0; i < countries.size(); i++) { + if (is_edit_mode || (filter == SHOW_ALL_LOCALES) || f_cnt_all.has(countries[i]) || f_cnt_all.empty()) { + const String &cnt = TranslationServer::get_singleton()->get_country_name(countries[i]); + TreeItem *t = cnt_list->create_item(c_root); + if (is_edit_mode) { + t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); + t->set_editable(0, true); + t->set_checked(0, f_cnt_all.has(countries[i])); + } else if (country_code->get_text() == countries[i]) { + t->select(0); + } + t->set_text(0, vformat("%s [%s]", cnt, countries[i])); + t->set_metadata(0, countries[i]); + } + } + updating_lists = false; +} + +void EditorLocaleDialog::set_locale(const String &p_locale) { + const String &locale = TranslationServer::get_singleton()->standardize_locale(p_locale); + if (locale.empty()) { + locale_set = false; + + lang_code->set_text(""); + script_code->set_text(""); + country_code->set_text(""); + variant_code->set_text(""); + } else { + locale_set = true; + + Vector locale_elements = p_locale.split("_"); + lang_code->set_text(locale_elements[0]); + if (locale_elements.size() >= 2) { + if (locale_elements[1].length() == 4 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_lower_case(locale_elements[1][1]) && is_ascii_lower_case(locale_elements[1][2]) && is_ascii_lower_case(locale_elements[1][3])) { + script_code->set_text(locale_elements[1]); + advanced->set_pressed(true); + } + if (locale_elements[1].length() == 2 && is_ascii_upper_case(locale_elements[1][0]) && is_ascii_upper_case(locale_elements[1][1])) { + country_code->set_text(locale_elements[1]); + } + } + if (locale_elements.size() >= 3) { + if (locale_elements[2].length() == 2 && is_ascii_upper_case(locale_elements[2][0]) && is_ascii_upper_case(locale_elements[2][1])) { + country_code->set_text(locale_elements[2]); + } else { + variant_code->set_text(locale_elements[2].to_lower()); + advanced->set_pressed(true); + } + } + if (locale_elements.size() >= 4) { + variant_code->set_text(locale_elements[3].to_lower()); + advanced->set_pressed(true); + } + } +} + +void EditorLocaleDialog::popup_locale_dialog() { + popup_centered_clamped(Size2(1050, 700) * EDSCALE, 0.8); +} + +EditorLocaleDialog::EditorLocaleDialog() { + undo_redo = EditorNode::get_undo_redo(); + + set_title(TTR("Select a Locale")); + + VBoxContainer *vb = memnew(VBoxContainer); + { + HBoxContainer *hb_filter = memnew(HBoxContainer); + { + filter_mode = memnew(OptionButton); + filter_mode->add_item(TTR("Show All Locales"), SHOW_ALL_LOCALES); + filter_mode->set_h_size_flags(Control::SIZE_EXPAND_FILL); + filter_mode->add_item(TTR("Show Selected Locales Only"), SHOW_ONLY_SELECTED_LOCALES); + filter_mode->select(0); + filter_mode->connect("item_selected", this, "_filter_mode_changed"); + hb_filter->add_child(filter_mode); + } + { + edit_filters = memnew(CheckButton); + edit_filters->set_text("Edit Filters"); + edit_filters->set_toggle_mode(true); + edit_filters->set_pressed(false); + edit_filters->connect("toggled", this, "_edit_filters"); + hb_filter->add_child(edit_filters); + } + { + advanced = memnew(CheckButton); + advanced->set_text("Advanced"); + advanced->set_toggle_mode(true); + advanced->set_pressed(false); + advanced->connect("toggled", this, "_toggle_advanced"); + hb_filter->add_child(advanced); + } + vb->add_child(hb_filter); + } + { + HBoxContainer *hb_lists = memnew(HBoxContainer); + hb_lists->set_v_size_flags(Control::SIZE_EXPAND_FILL); + { + VBoxContainer *vb_lang_list = memnew(VBoxContainer); + vb_lang_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + Label *lang_lbl = memnew(Label); + lang_lbl->set_text(TTR("Language:")); + vb_lang_list->add_child(lang_lbl); + } + { + lang_list = memnew(Tree); + lang_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + lang_list->connect("cell_selected", this, "_item_selected"); + lang_list->set_columns(1); + lang_list->connect("item_edited", this, "_filter_lang_option_changed"); + vb_lang_list->add_child(lang_list); + } + hb_lists->add_child(vb_lang_list); + } + { + vb_script_list = memnew(VBoxContainer); + vb_script_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + Label *script_lbl = memnew(Label); + script_lbl->set_text(TTR("Script:")); + vb_script_list->add_child(script_lbl); + } + { + script_list = memnew(Tree); + script_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + script_list->connect("cell_selected", this, "_item_selected"); + script_list->set_columns(1); + script_list->connect("item_edited", this, "_filter_script_option_changed"); + vb_script_list->add_child(script_list); + } + hb_lists->add_child(vb_script_list); + } + { + VBoxContainer *vb_cnt_list = memnew(VBoxContainer); + vb_cnt_list->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + Label *cnt_lbl = memnew(Label); + cnt_lbl->set_text(TTR("Country:")); + vb_cnt_list->add_child(cnt_lbl); + } + { + cnt_list = memnew(Tree); + cnt_list->set_v_size_flags(Control::SIZE_EXPAND_FILL); + cnt_list->connect("cell_selected", this, "_item_selected"); + cnt_list->set_columns(1); + cnt_list->connect("item_edited", this, "_filter_cnt_option_changed"); + vb_cnt_list->add_child(cnt_list); + } + hb_lists->add_child(vb_cnt_list); + } + vb->add_child(hb_lists); + } + { + hb_locale = memnew(HBoxContainer); + hb_locale->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + { + VBoxContainer *vb_language = memnew(VBoxContainer); + vb_language->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + Label *language_lbl = memnew(Label); + language_lbl->set_text(TTR("Language")); + vb_language->add_child(language_lbl); + } + { + lang_code = memnew(LineEdit); + lang_code->set_max_length(3); + lang_code->set_tooltip("Language"); + vb_language->add_child(lang_code); + } + hb_locale->add_child(vb_language); + } + { + VBoxContainer *vb_script = memnew(VBoxContainer); + vb_script->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + Label *script_lbl = memnew(Label); + script_lbl->set_text(TTR("Script")); + vb_script->add_child(script_lbl); + } + { + script_code = memnew(LineEdit); + script_code->set_max_length(4); + script_code->set_tooltip("Script"); + vb_script->add_child(script_code); + } + hb_locale->add_child(vb_script); + } + { + VBoxContainer *vb_country = memnew(VBoxContainer); + vb_country->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + Label *country_lbl = memnew(Label); + country_lbl->set_text(TTR("Country")); + vb_country->add_child(country_lbl); + } + { + country_code = memnew(LineEdit); + country_code->set_max_length(2); + country_code->set_tooltip("Country"); + vb_country->add_child(country_code); + } + hb_locale->add_child(vb_country); + } + { + VBoxContainer *vb_variant = memnew(VBoxContainer); + vb_variant->set_h_size_flags(Control::SIZE_EXPAND_FILL); + { + Label *variant_lbl = memnew(Label); + variant_lbl->set_text(TTR("Variant")); + vb_variant->add_child(variant_lbl); + } + { + variant_code = memnew(LineEdit); + variant_code->set_h_size_flags(Control::SIZE_EXPAND_FILL); + variant_code->set_placeholder("Variant"); + variant_code->set_tooltip("Variant"); + vb_variant->add_child(variant_code); + } + hb_locale->add_child(vb_variant); + } + } + vb->add_child(hb_locale); + } + add_child(vb); + _update_tree(); + + get_ok()->set_text(TTR("Select")); +} diff --git a/editor/editor_locale_dialog.h b/editor/editor_locale_dialog.h new file mode 100644 index 00000000000..85fb0e18447 --- /dev/null +++ b/editor/editor_locale_dialog.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* editor_locale_dialog.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2022 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. */ +/*************************************************************************/ + +#ifndef EDITOR_LOCALE_DIALOG_H +#define EDITOR_LOCALE_DIALOG_H + +#include "core/translation.h" +#include "scene/gui/dialogs.h" + +class Button; +class HBoxContainer; +class VBoxContainer; +class LineEdit; +class Tree; +class OptionButton; +class UndoRedo; + +class EditorLocaleDialog : public ConfirmationDialog { + GDCLASS(EditorLocaleDialog, ConfirmationDialog); + + enum LocaleFilter { + SHOW_ALL_LOCALES, + SHOW_ONLY_SELECTED_LOCALES, + }; + + HBoxContainer *hb_locale = nullptr; + VBoxContainer *vb_script_list = nullptr; + OptionButton *filter_mode = nullptr; + Button *edit_filters = nullptr; + Button *advanced = nullptr; + LineEdit *lang_code = nullptr; + LineEdit *script_code = nullptr; + LineEdit *country_code = nullptr; + LineEdit *variant_code = nullptr; + Tree *lang_list = nullptr; + Tree *script_list = nullptr; + Tree *cnt_list = nullptr; + + UndoRedo *undo_redo = nullptr; + + bool locale_set = false; + bool updating_lists = false; + +protected: + static void _bind_methods(); + virtual void _post_popup(); + virtual void ok_pressed(); + + void _item_selected(); + void _filter_lang_option_changed(); + void _filter_script_option_changed(); + void _filter_cnt_option_changed(); + void _filter_mode_changed(int p_mode); + void _edit_filters(bool p_checked); + void _toggle_advanced(bool p_checked); + + void _update_tree(); + +public: + EditorLocaleDialog(); + + void set_locale(const String &p_locale); + void popup_locale_dialog(); +}; + +#endif // EDITOR_LOCALE_DIALOG_H diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index b55b997de05..9892e000e41 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -320,6 +320,67 @@ EditorPropertyTextEnum::EditorPropertyTextEnum() { add_focusable(cancel_button); } +//////////////////// LOCALE //////////////////////// + +void EditorPropertyLocale::_locale_selected(const String &p_locale) { + emit_changed(get_edited_property(), p_locale); + update_property(); +} + +void EditorPropertyLocale::_locale_pressed() { + if (!dialog) { + dialog = memnew(EditorLocaleDialog); + dialog->connect("locale_selected", this, "_locale_selected"); + add_child(dialog); + } + + String locale_code = get_edited_object()->get(get_edited_property()); + dialog->set_locale(locale_code); + dialog->popup_locale_dialog(); +} + +void EditorPropertyLocale::update_property() { + String locale_code = get_edited_object()->get(get_edited_property()); + locale->set_text(locale_code); + locale->set_tooltip(locale_code); +} + +void EditorPropertyLocale::setup(const String &p_hint_text) { +} + +void EditorPropertyLocale::_notification(int p_what) { + if (p_what == NOTIFICATION_ENTER_TREE || p_what == NOTIFICATION_THEME_CHANGED) { + locale_edit->set_icon(get_icon("Translation", "EditorIcons")); + } +} + +void EditorPropertyLocale::_locale_focus_exited() { + _locale_selected(locale->get_text()); +} + +void EditorPropertyLocale::_bind_methods() { + ClassDB::bind_method(D_METHOD("_locale_selected"), &EditorPropertyLocale::_locale_selected); + ClassDB::bind_method(D_METHOD("_locale_pressed"), &EditorPropertyLocale::_locale_pressed); + ClassDB::bind_method(D_METHOD("_locale_focus_exited"), &EditorPropertyLocale::_locale_focus_exited); +} + +EditorPropertyLocale::EditorPropertyLocale() { + HBoxContainer *locale_hb = memnew(HBoxContainer); + add_child(locale_hb); + locale = memnew(LineEdit); + locale_hb->add_child(locale); + locale->connect("text_submitted", this, "_locale_selected"); + locale->connect("focus_exited", this, "_locale_focus_exited"); + locale->set_h_size_flags(SIZE_EXPAND_FILL); + + locale_edit = memnew(Button); + locale_edit->set_clip_text(true); + locale_hb->add_child(locale_edit); + add_focusable(locale); + dialog = nullptr; + locale_edit->connect("pressed", this, "_locale_pressed"); +} + ///////////////////// PATH ///////////////////////// void EditorPropertyPath::_path_selected(const String &p_path) { @@ -2881,6 +2942,10 @@ bool EditorInspectorDefaultPlugin::parse_property(Object *p_object, Variant::Typ EditorPropertyClassName *editor = memnew(EditorPropertyClassName); editor->setup("Object", p_hint_text); add_property_editor(p_path, editor); + } else if (p_hint == PROPERTY_HINT_LOCALE_ID) { + EditorPropertyLocale *editor = memnew(EditorPropertyLocale); + editor->setup(p_hint_text); + return editor; } else if (p_hint == PROPERTY_HINT_DIR || p_hint == PROPERTY_HINT_FILE || p_hint == PROPERTY_HINT_SAVE_FILE || p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE) { Vector extensions = p_hint_text.split(","); bool global = p_hint == PROPERTY_HINT_GLOBAL_DIR || p_hint == PROPERTY_HINT_GLOBAL_FILE; diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 383af9067b2..4633090a82d 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -33,6 +33,7 @@ #include "editor/create_dialog.h" #include "editor/editor_inspector.h" +#include "editor/editor_locale_dialog.h" #include "editor/editor_resource_picker.h" #include "editor/editor_spin_slider.h" #include "editor/property_selector.h" @@ -558,6 +559,26 @@ public: EditorPropertyNodePath(); }; +class EditorPropertyLocale : public EditorProperty { + GDCLASS(EditorPropertyLocale, EditorProperty); + EditorLocaleDialog *dialog; + LineEdit *locale; + Button *locale_edit; + + void _locale_selected(const String &p_locale); + void _locale_pressed(); + void _locale_focus_exited(); + +protected: + static void _bind_methods(); + void _notification(int p_what); + +public: + void setup(const String &p_hit_string); + virtual void update_property(); + EditorPropertyLocale(); +}; + class EditorPropertyRID : public EditorProperty { GDCLASS(EditorPropertyRID, EditorProperty); Label *label; diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 29663426e6c..e3b9f4e3849 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -256,7 +256,6 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { { String lang_hint = "en"; String host_lang = OS::get_singleton()->get_locale(); - host_lang = TranslationServer::standardize_locale(host_lang); // Some locales are not properly supported currently in Godot due to lack of font shaping // (e.g. Arabic or Hindi), so even though we have work in progress translations for them, @@ -264,6 +263,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { const Vector locales_to_skip = String("ar,bn,fa,he,hi,ml,si,ta,te,ur").split(","); String best; + int best_score = 0; const Vector &locales = get_editor_locales(); for (int i = 0; i < locales.size(); i++) { const String &locale = locales[i]; @@ -278,16 +278,17 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { lang_hint += ","; lang_hint += locale; - if (host_lang == locale) { - best = locale; - } - - if (best == String() && host_lang.begins_with(locale)) { + int score = TranslationServer::get_singleton()->compare_locales(host_lang, locale); + if (score > 0 && score >= best_score) { best = locale; + best_score = score; + if (score == 10) { + break; // Exact match, skip the rest. + } } } - if (best == String()) { + if (best_score == 0) { best = "en"; } diff --git a/editor/import/resource_importer_csv_translation.cpp b/editor/import/resource_importer_csv_translation.cpp index 2b3bf09f841..53d1be838af 100644 --- a/editor/import/resource_importer_csv_translation.cpp +++ b/editor/import/resource_importer_csv_translation.cpp @@ -97,8 +97,7 @@ Error ResourceImporterCSVTranslation::import(const String &p_source_file, const Vector> translations; for (int i = 1; i < line.size(); i++) { - String locale = line[i]; - ERR_FAIL_COND_V_MSG(!TranslationServer::is_locale_valid(locale), ERR_PARSE_ERROR, "Error importing CSV translation: '" + locale + "' is not a valid locale."); + String locale = TranslationServer::get_singleton()->standardize_locale(line[i]); locales.push_back(locale); Ref translation; diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 7e52b9bbd81..b06d66a44df 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -1331,6 +1331,24 @@ void ProjectSettingsEditor::_translation_res_select() { call_deferred("_update_translations"); } +void ProjectSettingsEditor::_translation_res_option_popup(bool p_arrow_clicked) { + TreeItem *ed = translation_remap_options->get_edited(); + ERR_FAIL_COND(!ed); + + locale_select->set_locale(ed->get_tooltip(1)); + locale_select->popup_locale_dialog(); +} + +void ProjectSettingsEditor::_translation_res_option_selected(const String &p_locale) { + TreeItem *ed = translation_remap_options->get_edited(); + ERR_FAIL_COND(!ed); + + ed->set_text(1, TranslationServer::get_singleton()->get_locale_name(p_locale)); + ed->set_tooltip(1, p_locale); + + ProjectSettingsEditor::_translation_res_option_changed(); +} + void ProjectSettingsEditor::_translation_res_option_changed() { if (updating_translations) { return; @@ -1350,20 +1368,11 @@ void ProjectSettingsEditor::_translation_res_option_changed() { String key = k->get_metadata(0); int idx = ed->get_metadata(0); String path = ed->get_metadata(1); - int which = ed->get_range(1); - - Vector langs = TranslationServer::get_all_locales(); - - ERR_FAIL_INDEX(which, langs.size()); + String locale = ed->get_tooltip(1); ERR_FAIL_COND(!remaps.has(key)); PoolStringArray r = remaps[key]; - ERR_FAIL_INDEX(idx, r.size()); - if (translation_locales_idxs_remap.size() > which) { - r.set(idx, path + ":" + langs[translation_locales_idxs_remap[which]]); - } else { - r.set(idx, path + ":" + langs[which]); - } + r.set(idx, path + ":" + locale); remaps[key] = r; updating_translations = true; @@ -1441,86 +1450,6 @@ void ProjectSettingsEditor::_translation_res_option_delete(Object *p_item, int p undo_redo->commit_action(); } -void ProjectSettingsEditor::_translation_filter_option_changed() { - int sel_id = translation_locale_filter_mode->get_selected_id(); - TreeItem *t = translation_filter->get_edited(); - String locale = t->get_tooltip(0); - bool checked = t->is_checked(0); - - Variant prev; - Array f_locales_all; - - if (ProjectSettings::get_singleton()->has_setting("locale/locale_filter")) { - f_locales_all = ProjectSettings::get_singleton()->get("locale/locale_filter"); - prev = f_locales_all; - - if (f_locales_all.size() != 2) { - f_locales_all.clear(); - f_locales_all.append(sel_id); - f_locales_all.append(Array()); - } - } else { - f_locales_all.append(sel_id); - f_locales_all.append(Array()); - } - - Array f_locales = f_locales_all[1]; - int l_idx = f_locales.find(locale); - - if (checked) { - if (l_idx == -1) { - f_locales.append(locale); - } - } else { - if (l_idx != -1) { - f_locales.remove(l_idx); - } - } - - f_locales = f_locales.sort(); - - undo_redo->create_action(TTR("Changed Locale Filter")); - undo_redo->add_do_property(ProjectSettings::get_singleton(), "locale/locale_filter", f_locales_all); - undo_redo->add_undo_property(ProjectSettings::get_singleton(), "locale/locale_filter", prev); - undo_redo->add_do_method(this, "_update_translations"); - undo_redo->add_undo_method(this, "_update_translations"); - undo_redo->add_do_method(this, "_settings_changed"); - undo_redo->add_undo_method(this, "_settings_changed"); - undo_redo->commit_action(); -} - -void ProjectSettingsEditor::_translation_filter_mode_changed(int p_mode) { - int sel_id = translation_locale_filter_mode->get_selected_id(); - - Variant prev; - Array f_locales_all; - - if (ProjectSettings::get_singleton()->has_setting("locale/locale_filter")) { - f_locales_all = ProjectSettings::get_singleton()->get("locale/locale_filter"); - prev = f_locales_all; - - if (f_locales_all.size() != 2) { - f_locales_all.clear(); - f_locales_all.append(sel_id); - f_locales_all.append(Array()); - } else { - f_locales_all[0] = sel_id; - } - } else { - f_locales_all.append(sel_id); - f_locales_all.append(Array()); - } - - undo_redo->create_action(TTR("Changed Locale Filter Mode")); - undo_redo->add_do_property(ProjectSettings::get_singleton(), "locale/locale_filter", f_locales_all); - undo_redo->add_undo_property(ProjectSettings::get_singleton(), "locale/locale_filter", prev); - undo_redo->add_do_method(this, "_update_translations"); - undo_redo->add_undo_method(this, "_update_translations"); - undo_redo->add_do_method(this, "_settings_changed"); - undo_redo->add_undo_method(this, "_settings_changed"); - undo_redo->commit_action(); -} - void ProjectSettingsEditor::_update_translations() { //update translations @@ -1545,64 +1474,6 @@ void ProjectSettingsEditor::_update_translations() { } } - Vector langs = TranslationServer::get_all_locales(); - Vector names = TranslationServer::get_all_locale_names(); - - //update filter tab - Array l_filter_all; - - bool is_arr_empty = true; - if (ProjectSettings::get_singleton()->has_setting("locale/locale_filter")) { - l_filter_all = ProjectSettings::get_singleton()->get("locale/locale_filter"); - - if (l_filter_all.size() == 2) { - translation_locale_filter_mode->select(l_filter_all[0]); - is_arr_empty = false; - } - } - if (is_arr_empty) { - l_filter_all.append(0); - l_filter_all.append(Array()); - translation_locale_filter_mode->select(0); - } - - int filter_mode = l_filter_all[0]; - Array l_filter = l_filter_all[1]; - - int s = names.size(); - bool is_short_list_when_show_all_selected = filter_mode == SHOW_ALL_LOCALES && translation_filter_treeitems.size() < s; - bool is_full_list_when_show_only_selected = filter_mode == SHOW_ONLY_SELECTED_LOCALES && translation_filter_treeitems.size() == s; - bool should_recreate_locales_list = is_short_list_when_show_all_selected || is_full_list_when_show_only_selected; - - if (!translation_locales_list_created || should_recreate_locales_list) { - translation_locales_list_created = true; - translation_filter->clear(); - root = translation_filter->create_item(nullptr); - translation_filter->set_hide_root(true); - translation_filter_treeitems.clear(); - for (int i = 0; i < s; i++) { - String n = names[i]; - String l = langs[i]; - bool is_checked = l_filter.has(l); - if (filter_mode == SHOW_ONLY_SELECTED_LOCALES && !is_checked) { - continue; - } - - TreeItem *t = translation_filter->create_item(root); - t->set_cell_mode(0, TreeItem::CELL_MODE_CHECK); - t->set_text(0, vformat("[%s] %s", l, n)); - t->set_editable(0, true); - t->set_tooltip(0, l); - t->set_checked(0, is_checked); - translation_filter_treeitems.push_back(t); - } - } else { - for (int i = 0; i < translation_filter_treeitems.size(); i++) { - TreeItem *t = translation_filter_treeitems[i]; - t->set_checked(0, l_filter.has(t->get_tooltip(0))); - } - } - //update translation remaps String remap_selected; @@ -1618,32 +1489,6 @@ void ProjectSettingsEditor::_update_translations() { translation_remap_options->set_hide_root(true); translation_res_option_add_button->set_disabled(true); - translation_locales_idxs_remap.clear(); - translation_locales_idxs_remap.resize(l_filter.size()); - int fl_idx_count = translation_locales_idxs_remap.size(); - - String langnames = ""; - int l_idx = 0; - for (int i = 0; i < names.size(); i++) { - if (filter_mode == SHOW_ONLY_SELECTED_LOCALES && fl_idx_count != 0) { - if (l_filter.size() > 0) { - if (l_filter.find(langs[i]) != -1) { - if (langnames.length() > 0) { - langnames += ","; - } - langnames += vformat("[%s] %s", langs[i], names[i]); - translation_locales_idxs_remap.write[l_idx] = i; - l_idx++; - } - } - } else { - if (i > 0) { - langnames += ","; - } - langnames += vformat("[%s] %s", langs[i], names[i]); - } - } - if (ProjectSettings::get_singleton()->has_setting("locale/translation_remaps")) { Dictionary remaps = ProjectSettings::get_singleton()->get("locale/translation_remaps"); List rk; @@ -1678,21 +1523,11 @@ void ProjectSettingsEditor::_update_translations() { t2->set_tooltip(0, path); t2->set_metadata(0, j); t2->add_button(0, get_icon("Remove", "EditorIcons"), 0, false, TTR("Remove")); - t2->set_cell_mode(1, TreeItem::CELL_MODE_RANGE); - t2->set_text(1, langnames); + t2->set_cell_mode(1, TreeItem::CELL_MODE_CUSTOM); + t2->set_text(1, TranslationServer::get_singleton()->get_locale_name(locale)); t2->set_editable(1, true); t2->set_metadata(1, path); - int idx = langs.find(locale); - if (idx < 0) { - idx = 0; - } - - int f_idx = translation_locales_idxs_remap.find(idx); - if (f_idx != -1 && fl_idx_count > 0 && filter_mode == SHOW_ONLY_SELECTED_LOCALES) { - t2->set_range(1, f_idx); - } else { - t2->set_range(1, idx); - } + t2->set_tooltip(1, locale); } } } @@ -1777,6 +1612,7 @@ void ProjectSettingsEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_settings_changed"), &ProjectSettingsEditor::_settings_changed); ClassDB::bind_method(D_METHOD("_translation_add"), &ProjectSettingsEditor::_translation_add); ClassDB::bind_method(D_METHOD("_translation_file_open"), &ProjectSettingsEditor::_translation_file_open); + ClassDB::bind_method(D_METHOD("_translation_res_option_selected"), &ProjectSettingsEditor::_translation_res_option_selected); ClassDB::bind_method(D_METHOD("_translation_res_add"), &ProjectSettingsEditor::_translation_res_add); ClassDB::bind_method(D_METHOD("_translation_res_file_open"), &ProjectSettingsEditor::_translation_res_file_open); @@ -1786,9 +1622,7 @@ void ProjectSettingsEditor::_bind_methods() { ClassDB::bind_method(D_METHOD("_translation_res_option_changed"), &ProjectSettingsEditor::_translation_res_option_changed); ClassDB::bind_method(D_METHOD("_translation_res_delete"), &ProjectSettingsEditor::_translation_res_delete); ClassDB::bind_method(D_METHOD("_translation_res_option_delete"), &ProjectSettingsEditor::_translation_res_option_delete); - - ClassDB::bind_method(D_METHOD("_translation_filter_option_changed"), &ProjectSettingsEditor::_translation_filter_option_changed); - ClassDB::bind_method(D_METHOD("_translation_filter_mode_changed"), &ProjectSettingsEditor::_translation_filter_mode_changed); + ClassDB::bind_method(D_METHOD("_translation_res_option_popup"), &ProjectSettingsEditor::_translation_res_option_popup); ClassDB::bind_method(D_METHOD("_toggle_search_bar"), &ProjectSettingsEditor::_toggle_search_bar); @@ -2039,9 +1873,6 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { translations->set_tab_align(TabContainer::ALIGN_LEFT); translations->set_name(TTR("Localization")); tab_container->add_child(translations); - //remap for properly select language in popup - translation_locales_idxs_remap = Vector(); - translation_locales_list_created = false; { VBoxContainer *tvb = memnew(VBoxContainer); @@ -2061,6 +1892,10 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { translation_list->set_v_size_flags(SIZE_EXPAND_FILL); tmc->add_child(translation_list); + locale_select = memnew(EditorLocaleDialog); + locale_select->connect("locale_selected", this, "_translation_res_option_selected"); + add_child(locale_select); + translation_file_open = memnew(EditorFileDialog); add_child(translation_file_open); translation_file_open->set_mode(EditorFileDialog::MODE_OPEN_FILES); @@ -2113,9 +1948,10 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { translation_remap_options->set_column_titles_visible(true); translation_remap_options->set_column_expand(0, true); translation_remap_options->set_column_expand(1, false); - translation_remap_options->set_column_min_width(1, 200); + translation_remap_options->set_column_min_width(1, 250 * EDSCALE); translation_remap_options->connect("item_edited", this, "_translation_res_option_changed"); translation_remap_options->connect("button_pressed", this, "_translation_res_option_delete"); + translation_remap_options->connect("custom_popup_edited", this, "_translation_res_option_popup"); translation_res_option_file_open = memnew(EditorFileDialog); add_child(translation_res_option_file_open); @@ -2123,29 +1959,6 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { translation_res_option_file_open->connect("files_selected", this, "_translation_res_option_add"); } - { - VBoxContainer *tvb = memnew(VBoxContainer); - translations->add_child(tvb); - tvb->set_name(TTR("Locales Filter")); - VBoxContainer *tmc = memnew(VBoxContainer); - tmc->set_v_size_flags(SIZE_EXPAND_FILL); - tvb->add_child(tmc); - - translation_locale_filter_mode = memnew(OptionButton); - translation_locale_filter_mode->add_item(TTR("Show All Locales"), SHOW_ALL_LOCALES); - translation_locale_filter_mode->add_item(TTR("Show Selected Locales Only"), SHOW_ONLY_SELECTED_LOCALES); - translation_locale_filter_mode->select(0); - tmc->add_margin_child(TTR("Filter mode:"), translation_locale_filter_mode); - translation_locale_filter_mode->connect("item_selected", this, "_translation_filter_mode_changed"); - - translation_filter = memnew(Tree); - translation_filter->set_v_size_flags(SIZE_EXPAND_FILL); - translation_filter->set_columns(1); - tmc->add_child(memnew(Label(TTR("Locales:")))); - tmc->add_child(translation_filter); - translation_filter->connect("item_edited", this, "_translation_filter_option_changed"); - } - autoload_settings = memnew(EditorAutoloadSettings); autoload_settings->set_name(TTR("AutoLoad")); tab_container->add_child(autoload_settings); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index d77634639b0..67e9cec9f2c 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -34,6 +34,7 @@ #include "core/undo_redo.h" #include "editor/editor_autoload_settings.h" #include "editor/editor_data.h" +#include "editor/editor_locale_dialog.h" #include "editor/editor_plugin_settings.h" #include "editor/editor_sectioned_inspector.h" #include "editor/import_defaults_editor.h" @@ -51,11 +52,6 @@ class ProjectSettingsEditor : public AcceptDialog { INPUT_MOUSE_BUTTON }; - enum LocaleFilter { - SHOW_ALL_LOCALES, - SHOW_ONLY_SELECTED_LOCALES, - }; - TabContainer *tab_container; Timer *timer; @@ -94,6 +90,7 @@ class ProjectSettingsEditor : public AcceptDialog { Ref last_wait_for_key; + EditorLocaleDialog *locale_select; EditorFileDialog *translation_file_open; Tree *translation_list; @@ -102,11 +99,6 @@ class ProjectSettingsEditor : public AcceptDialog { EditorFileDialog *translation_res_option_file_open; Tree *translation_remap; Tree *translation_remap_options; - Tree *translation_filter; - bool translation_locales_list_created; - OptionButton *translation_locale_filter_mode; - Vector translation_filter_treeitems; - Vector translation_locales_idxs_remap; EditorAutoloadSettings *autoload_settings; @@ -153,9 +145,8 @@ class ProjectSettingsEditor : public AcceptDialog { void _translation_res_option_add(const PoolStringArray &p_paths); void _translation_res_option_changed(); void _translation_res_option_delete(Object *p_item, int p_column, int p_button); - - void _translation_filter_option_changed(); - void _translation_filter_mode_changed(int p_mode); + void _translation_res_option_popup(bool p_arrow_clicked); + void _translation_res_option_selected(const String &p_locale); void _toggle_search_bar(bool p_pressed); diff --git a/editor/property_editor.cpp b/editor/property_editor.cpp index ec254d5cb16..5fb9eba57b0 100644 --- a/editor/property_editor.cpp +++ b/editor/property_editor.cpp @@ -514,7 +514,13 @@ bool CustomPropertyEditor::edit(Object *p_owner, const String &p_name, Variant:: } break; case Variant::STRING: { - if (hint == PROPERTY_HINT_FILE || hint == PROPERTY_HINT_GLOBAL_FILE) { + if (hint == PROPERTY_HINT_LOCALE_ID) { + List names; + names.push_back(TTR("Locale...")); + names.push_back(TTR("Clear")); + config_action_buttons(names); + + } else if (hint == PROPERTY_HINT_FILE || hint == PROPERTY_HINT_GLOBAL_FILE) { List names; names.push_back(TTR("File...")); names.push_back(TTR("Clear")); @@ -1050,6 +1056,14 @@ void CustomPropertyEditor::_file_selected(String p_file) { } } +void CustomPropertyEditor::_locale_selected(String p_locale) { + if (type == Variant::STRING && hint == PROPERTY_HINT_LOCALE_ID) { + v = p_locale; + emit_signal("variant_changed"); + hide(); + } +} + void CustomPropertyEditor::_type_create_selected(int p_idx) { if (type == Variant::INT || type == Variant::REAL) { float newval = 0; @@ -1188,7 +1202,8 @@ void CustomPropertyEditor::_action_pressed(int p_which) { case Variant::STRING: { if (hint == PROPERTY_HINT_MULTILINE_TEXT) { hide(); - + } else if (hint == PROPERTY_HINT_LOCALE_ID) { + locale->popup_locale_dialog(); } else if (hint == PROPERTY_HINT_FILE || hint == PROPERTY_HINT_GLOBAL_FILE) { if (p_which == 0) { if (hint == PROPERTY_HINT_FILE) { @@ -1746,6 +1761,7 @@ void CustomPropertyEditor::_bind_methods() { ClassDB::bind_method("_range_modified", &CustomPropertyEditor::_range_modified); ClassDB::bind_method("_action_pressed", &CustomPropertyEditor::_action_pressed); ClassDB::bind_method("_file_selected", &CustomPropertyEditor::_file_selected); + ClassDB::bind_method("_locale_selected", &CustomPropertyEditor::_locale_selected); ClassDB::bind_method("_type_create_selected", &CustomPropertyEditor::_type_create_selected); ClassDB::bind_method("_node_path_selected", &CustomPropertyEditor::_node_path_selected); ClassDB::bind_method("_color_changed", &CustomPropertyEditor::_color_changed); @@ -1835,6 +1851,12 @@ CustomPropertyEditor::CustomPropertyEditor() { file->connect("file_selected", this, "_file_selected"); file->connect("dir_selected", this, "_file_selected"); + locale = memnew(EditorLocaleDialog); + add_child(locale); + locale->hide(); + + locale->connect("locale_selected", this, "_locale_selected"); + error = memnew(ConfirmationDialog); error->set_title(TTR("Error!")); add_child(error); diff --git a/editor/property_editor.h b/editor/property_editor.h index 022362c3edc..abb5eb0a266 100644 --- a/editor/property_editor.h +++ b/editor/property_editor.h @@ -32,6 +32,7 @@ #define PROPERTY_EDITOR_H #include "editor/editor_file_dialog.h" +#include "editor/editor_locale_dialog.h" #include "editor/scene_tree_editor.h" #include "scene/gui/button.h" #include "scene/gui/check_box.h" @@ -92,6 +93,7 @@ class CustomPropertyEditor : public Popup { PopupMenu *menu; SceneTreeDialog *scene_tree; + EditorLocaleDialog *locale; EditorFileDialog *file; ConfirmationDialog *error; String name; @@ -128,6 +130,7 @@ class CustomPropertyEditor : public Popup { PropertyValueEvaluator *evaluator; void _text_edit_changed(); + void _locale_selected(String p_locale); void _file_selected(String p_file); void _modified(String p_string); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index 2e5d923a6af..3e1e5990de1 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -86,6 +86,11 @@ typedef enum { GODOT_PROPERTY_HINT_PROPERTY_OF_BASE_TYPE, ///< a property of a base type GODOT_PROPERTY_HINT_PROPERTY_OF_INSTANCE, ///< a property of an instance GODOT_PROPERTY_HINT_PROPERTY_OF_SCRIPT, ///< a property of a script & base + GODOT_PROPERTY_HINT_OBJECT_TOO_BIG, ///< object is too big to send + GODOT_PROPERTY_HINT_NODE_PATH_VALID_TYPES, + GODOT_PROPERTY_HINT_SAVE_FILE, ///< a file path must be passed, hint_text (optionally) is a filter "*.png,*.wav,*.doc,". This opens a save dialog + GODOT_PROPERTY_HINT_ENUM_SUGGESTION, ///< hint_text= "val1,val2,val3,etc" + GODOT_PROPERTY_HINT_LOCALE_ID, GODOT_PROPERTY_HINT_MAX, } godot_property_hint;