From 6f28b97ab523b0b48ff00e667e07e6fb3b778aa6 Mon Sep 17 00:00:00 2001 From: bruvzg <7645683+bruvzg@users.noreply.github.com> Date: Mon, 26 Aug 2024 11:20:29 +0300 Subject: [PATCH] Add editor pseudolocalization support. Moves localized number formatting methods from TextServer to TranslationServer. Adds support for localized numbers pseudolocalization. Adds editor pseudolocalization support. --- core/string/translation_server.compat.inc | 6 + core/string/translation_server.cpp | 502 +++++++++++++++++- core/string/translation_server.h | 48 +- doc/classes/EditorSettings.xml | 30 ++ doc/classes/ProjectSettings.xml | 3 + doc/classes/TextServer.xml | 6 +- doc/classes/TextServerExtension.xml | 26 - doc/classes/TranslationServer.xml | 25 +- editor/animation_bezier_editor.cpp | 7 +- editor/animation_track_editor.cpp | 3 +- .../debugger/editor_performance_profiler.cpp | 5 +- editor/debugger/editor_profiler.cpp | 9 +- editor/debugger/editor_visual_profiler.cpp | 5 +- editor/editor_node.cpp | 5 + editor/editor_properties.cpp | 5 +- editor/editor_settings.cpp | 13 + editor/gui/editor_spin_slider.cpp | 11 +- editor/gui/editor_zoom_widget.cpp | 7 +- editor/plugins/canvas_item_editor_plugin.cpp | 53 +- editor/project_manager.cpp | 2 + .../4.3-stable.expected | 11 + modules/text_server_adv/text_server_adv.cpp | 243 --------- modules/text_server_adv/text_server_adv.h | 14 - scene/gui/code_edit.cpp | 7 +- scene/gui/line_edit.cpp | 2 +- scene/gui/progress_bar.cpp | 7 +- scene/gui/rich_text_label.cpp | 7 +- scene/gui/spin_box.cpp | 19 +- servers/text/text_server_extension.cpp | 26 - servers/text/text_server_extension.h | 7 - servers/text_server.cpp | 13 + servers/text_server.h | 6 +- 32 files changed, 719 insertions(+), 414 deletions(-) diff --git a/core/string/translation_server.compat.inc b/core/string/translation_server.compat.inc index 9d1ee8b9df23..28b83285be06 100644 --- a/core/string/translation_server.compat.inc +++ b/core/string/translation_server.compat.inc @@ -30,9 +30,15 @@ #ifndef DISABLE_DEPRECATED +String TranslationServer::_get_tool_locale_bind_compat_96105() { + return get_tool_locale(); +} + void TranslationServer::_bind_compatibility_methods() { ClassDB::bind_compatibility_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL("")); ClassDB::bind_compatibility_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL("")); + + ClassDB::bind_compatibility_method(D_METHOD("get_tool_locale"), &TranslationServer::_get_tool_locale_bind_compat_96105); } #endif diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp index 6e784881d07d..6c6a133b85d9 100644 --- a/core/string/translation_server.cpp +++ b/core/string/translation_server.cpp @@ -37,6 +37,7 @@ #include "core/string/locales.h" #ifdef TOOLS_ENABLED +#include "editor/editor_settings.h" #include "main/main.h" #endif @@ -179,6 +180,189 @@ void TranslationServer::init_locale_info() { } } +void TranslationServer::init_num_systems() { + { + NumSystemData ar; + ar.lang.insert(StringName("ar")); // Arabic + ar.lang.insert(StringName("ar_AE")); + ar.lang.insert(StringName("ar_BH")); + ar.lang.insert(StringName("ar_DJ")); + ar.lang.insert(StringName("ar_EG")); + ar.lang.insert(StringName("ar_ER")); + ar.lang.insert(StringName("ar_IL")); + ar.lang.insert(StringName("ar_IQ")); + ar.lang.insert(StringName("ar_JO")); + ar.lang.insert(StringName("ar_KM")); + ar.lang.insert(StringName("ar_KW")); + ar.lang.insert(StringName("ar_LB")); + ar.lang.insert(StringName("ar_MR")); + ar.lang.insert(StringName("ar_OM")); + ar.lang.insert(StringName("ar_PS")); + ar.lang.insert(StringName("ar_QA")); + ar.lang.insert(StringName("ar_SA")); + ar.lang.insert(StringName("ar_SD")); + ar.lang.insert(StringName("ar_SO")); + ar.lang.insert(StringName("ar_SS")); + ar.lang.insert(StringName("ar_SY")); + ar.lang.insert(StringName("ar_TD")); + ar.lang.insert(StringName("ar_YE")); + ar.lang.insert(StringName("ckb")); // Central Kurdish + ar.lang.insert(StringName("ckb_IQ")); + ar.lang.insert(StringName("ckb_IR")); + ar.lang.insert(StringName("sd")); // Sindhi + ar.lang.insert(StringName("sd_PK")); + ar.lang.insert(StringName("sd_Arab")); + ar.lang.insert(StringName("sd_Arab_PK")); + ar.digits = U"٠١٢٣٤٥٦٧٨٩٫"; + ar.percent_sign = U"٪"; + ar.exp = U"اس"; + num_systems.push_back(ar); + } + + // Persian and Urdu numerals. + { + NumSystemData pr; + pr.lang.insert(StringName("fa")); // Persian + pr.lang.insert(StringName("fa_AF")); + pr.lang.insert(StringName("fa_IR")); + pr.lang.insert(StringName("ks")); // Kashmiri + pr.lang.insert(StringName("ks_IN")); + pr.lang.insert(StringName("ks_Arab")); + pr.lang.insert(StringName("ks_Arab_IN")); + pr.lang.insert(StringName("lrc")); // Northern Luri + pr.lang.insert(StringName("lrc_IQ")); + pr.lang.insert(StringName("lrc_IR")); + pr.lang.insert(StringName("mzn")); // Mazanderani + pr.lang.insert(StringName("mzn_IR")); + pr.lang.insert(StringName("pa_PK")); // Panjabi + pr.lang.insert(StringName("pa_Arab")); + pr.lang.insert(StringName("pa_Arab_PK")); + pr.lang.insert(StringName("ps")); // Pushto + pr.lang.insert(StringName("ps_AF")); + pr.lang.insert(StringName("ps_PK")); + pr.lang.insert(StringName("ur_IN")); // Urdu + pr.lang.insert(StringName("uz_AF")); // Uzbek + pr.lang.insert(StringName("uz_Arab")); + pr.lang.insert(StringName("uz_Arab_AF")); + pr.digits = U"۰۱۲۳۴۵۶۷۸۹٫"; + pr.percent_sign = U"٪"; + pr.exp = U"اس"; + num_systems.push_back(pr); + } + + // Bengali numerals. + { + NumSystemData bn; + bn.lang.insert(StringName("as")); // Assamese + bn.lang.insert(StringName("as_IN")); + bn.lang.insert(StringName("bn")); // Bengali + bn.lang.insert(StringName("bn_BD")); + bn.lang.insert(StringName("bn_IN")); + bn.lang.insert(StringName("mni")); // Manipuri + bn.lang.insert(StringName("mni_IN")); + bn.lang.insert(StringName("mni_Beng")); + bn.lang.insert(StringName("mni_Beng_IN")); + bn.digits = U"০১২৩৪৫৬৭৮৯."; + bn.percent_sign = U"%"; + bn.exp = U"e"; + num_systems.push_back(bn); + } + + // Devanagari numerals. + { + NumSystemData mr; + mr.lang.insert(StringName("mr")); // Marathi + mr.lang.insert(StringName("mr_IN")); + mr.lang.insert(StringName("ne")); // Nepali + mr.lang.insert(StringName("ne_IN")); + mr.lang.insert(StringName("ne_NP")); + mr.lang.insert(StringName("sa")); // Sanskrit + mr.lang.insert(StringName("sa_IN")); + mr.digits = U"०१२३४५६७८९."; + mr.percent_sign = U"%"; + mr.exp = U"e"; + num_systems.push_back(mr); + } + + // Dzongkha numerals. + { + NumSystemData dz; + dz.lang.insert(StringName("dz")); // Dzongkha + dz.lang.insert(StringName("dz_BT")); + dz.digits = U"༠༡༢༣༤༥༦༧༨༩."; + dz.percent_sign = U"%"; + dz.exp = U"e"; + num_systems.push_back(dz); + } + + // Santali numerals. + { + NumSystemData sat; + sat.lang.insert(StringName("sat")); // Santali + sat.lang.insert(StringName("sat_IN")); + sat.lang.insert(StringName("sat_Olck")); + sat.lang.insert(StringName("sat_Olck_IN")); + sat.digits = U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙."; + sat.percent_sign = U"%"; + sat.exp = U"e"; + num_systems.push_back(sat); + } + + // Burmese numerals. + { + NumSystemData my; + my.lang.insert(StringName("my")); // Burmese + my.lang.insert(StringName("my_MM")); + my.digits = U"၀၁၂၃၄၅၆၇၈၉."; + my.percent_sign = U"%"; + my.exp = U"e"; + num_systems.push_back(my); + } + + // Chakma numerals. + { + NumSystemData ccp; + ccp.lang.insert(StringName("ccp")); // Chakma + ccp.lang.insert(StringName("ccp_BD")); + ccp.lang.insert(StringName("ccp_IN")); + ccp.digits = U"𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿."; + ccp.percent_sign = U"%"; + ccp.exp = U"e"; + num_systems.push_back(ccp); + } + + // Adlam numerals. + { + NumSystemData ff; + ff.lang.insert(StringName("ff")); // Fulah + ff.lang.insert(StringName("ff_Adlm_BF")); + ff.lang.insert(StringName("ff_Adlm_CM")); + ff.lang.insert(StringName("ff_Adlm_GH")); + ff.lang.insert(StringName("ff_Adlm_GM")); + ff.lang.insert(StringName("ff_Adlm_GN")); + ff.lang.insert(StringName("ff_Adlm_GW")); + ff.lang.insert(StringName("ff_Adlm_LR")); + ff.lang.insert(StringName("ff_Adlm_MR")); + ff.lang.insert(StringName("ff_Adlm_NE")); + ff.lang.insert(StringName("ff_Adlm_NG")); + ff.lang.insert(StringName("ff_Adlm_SL")); + ff.lang.insert(StringName("ff_Adlm_SN")); + ff.digits = U"𞥐𞥑𞥒𞥓𞥔𞥕𞥖𞥗𞥘𞥙."; + ff.percent_sign = U"%"; + ff.exp = U"e"; + num_systems.push_back(ff); + } + + // Pseudolocalization. + { + NumSystemData ps; + ps.digits = U"ѻїԇӡһѵδґβя'"; + ps.percent_sign = U"ϖ"; + ps.exp = U"Σ"; + num_systems.push_back(ps); + } +} + String TranslationServer::standardize_locale(const String &p_locale) const { return _standardize_locale(p_locale, false); } @@ -453,6 +637,10 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin return p_message; } + if (p_message == StringName()) { + return p_message; + } + StringName res = _get_message_from_translations(p_message, p_context, locale, false); if (!res && fallback.length() >= 2) { @@ -474,6 +662,10 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons return p_message_plural; } + if (p_message == StringName() && p_message_plural == StringName()) { + return p_message; + } + StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n); if (!res && fallback.length() >= 2) { @@ -482,12 +674,12 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons if (!res) { if (p_n == 1) { - return p_message; + return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message; } - return p_message_plural; + return pseudolocalization_enabled ? pseudolocalize(p_message_plural) : p_message_plural; } - return res; + return pseudolocalization_enabled ? pseudolocalize(res) : res; } StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const { @@ -560,6 +752,7 @@ void TranslationServer::setup() { pseudolocalization_double_vowels_enabled = GLOBAL_DEF("internationalization/pseudolocalization/double_vowels", false); pseudolocalization_fake_bidi_enabled = GLOBAL_DEF("internationalization/pseudolocalization/fake_bidi", false); pseudolocalization_override_enabled = GLOBAL_DEF("internationalization/pseudolocalization/override", false); + pseudolocalization_numbers_enabled = GLOBAL_DEF("internationalization/pseudolocalization/numbers", true); expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0); pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "["); pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]"); @@ -570,6 +763,207 @@ void TranslationServer::setup() { #endif } +String TranslationServer::format_number(const String &p_number, const String &p_language) const { + StringName lang = p_language.is_empty() ? get_locale() : p_language; + String res = p_number; + if (pseudolocalization_enabled && pseudolocalization_numbers_enabled) { + res.replace("e", num_systems[num_systems.size() - 1].exp); + res.replace("E", num_systems[num_systems.size() - 1].exp); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] >= 0x30 && data[j] <= 0x39) { + data[j] = num_systems[num_systems.size() - 1].digits[data[j] - 0x30]; + } else if (data[j] == '.' || data[j] == ',') { + data[j] = num_systems[num_systems.size() - 1].digits[10]; + } + } + return pseudolocalization_prefix + res + pseudolocalization_suffix; + } else { + for (int i = 0; i < num_systems.size() - 1; i++) { + if (num_systems[i].lang.has(lang)) { + if (num_systems[i].digits.is_empty()) { + return p_number; + } + res.replace("e", num_systems[i].exp); + res.replace("E", num_systems[i].exp); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] >= 0x30 && data[j] <= 0x39) { + data[j] = num_systems[i].digits[data[j] - 0x30]; + } else if (data[j] == '.' || data[j] == ',') { + data[j] = num_systems[i].digits[10]; + } + } + break; + } + } + } + return res; +} + +String TranslationServer::tool_format_number(const String &p_number) const { + StringName lang = get_tool_locale(); + String res = p_number; + if (ed_pseudolocalization_enabled && ed_pseudolocalization_numbers_enabled) { + res.replace("e", num_systems[num_systems.size() - 1].exp); + res.replace("E", num_systems[num_systems.size() - 1].exp); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] >= 0x30 && data[j] <= 0x39) { + data[j] = num_systems[num_systems.size() - 1].digits[data[j] - 0x30]; + } else if (data[j] == '.' || data[j] == ',') { + data[j] = num_systems[num_systems.size() - 1].digits[10]; + } + } + return ed_pseudolocalization_prefix + res + ed_pseudolocalization_suffix; + } else { + for (int i = 0; i < num_systems.size() - 1; i++) { + if (num_systems[i].lang.has(lang)) { + if (num_systems[i].digits.is_empty()) { + return p_number; + } + res.replace("e", num_systems[i].exp); + res.replace("E", num_systems[i].exp); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] >= 0x30 && data[j] <= 0x39) { + data[j] = num_systems[i].digits[data[j] - 0x30]; + } else if (data[j] == '.' || data[j] == ',') { + data[j] = num_systems[i].digits[10]; + } + } + break; + } + } + } + return res; +} + +String TranslationServer::get_percent_sign(const String &p_language) const { + if (pseudolocalization_enabled && pseudolocalization_numbers_enabled) { + return pseudolocalization_prefix + num_systems[num_systems.size() - 1].percent_sign + pseudolocalization_suffix; + } else { + const StringName lang = (p_language.is_empty()) ? get_locale() : p_language; + + for (int i = 0; i < num_systems.size(); i++) { + if (num_systems[i].lang.has(lang)) { + if (num_systems[i].percent_sign.is_empty()) { + return "%"; + } + return num_systems[i].percent_sign; + } + } + return "%"; + } +} + +String TranslationServer::get_tool_percent_sign() const { + if (ed_pseudolocalization_enabled && ed_pseudolocalization_numbers_enabled) { + return ed_pseudolocalization_prefix + num_systems[num_systems.size() - 1].percent_sign + ed_pseudolocalization_suffix; + } else { + const StringName lang = get_tool_locale(); + + for (int i = 0; i < num_systems.size(); i++) { + if (num_systems[i].lang.has(lang)) { + if (num_systems[i].percent_sign.is_empty()) { + return "%"; + } + return num_systems[i].percent_sign; + } + } + return "%"; + } +} + +String TranslationServer::parse_number(const String &p_string, const String &p_language) const { + String res = p_string; + if (pseudolocalization_enabled && pseudolocalization_numbers_enabled) { + res = res.trim_prefix(pseudolocalization_prefix).trim_suffix(pseudolocalization_suffix); + res.replace(num_systems[num_systems.size() - 1].exp, "e"); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] == num_systems[num_systems.size() - 1].digits[10]) { + data[j] = '.'; + } else { + for (int k = 0; k < 10; k++) { + if (data[j] == num_systems[num_systems.size() - 1].digits[k]) { + data[j] = 0x30 + k; + } + } + } + } + } else { + StringName lang = p_language.is_empty() ? get_locale() : p_language; + for (int i = 0; i < num_systems.size() - 1; i++) { + if (num_systems[i].lang.has(lang)) { + if (num_systems[i].digits.is_empty()) { + return p_string; + } + res.replace(num_systems[i].exp, "e"); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] == num_systems[i].digits[10]) { + data[j] = '.'; + } else { + for (int k = 0; k < 10; k++) { + if (data[j] == num_systems[i].digits[k]) { + data[j] = 0x30 + k; + } + } + } + } + break; + } + } + } + return res; +} + +String TranslationServer::tool_parse_number(const String &p_string) const { + StringName lang = get_tool_locale(); + + String res = p_string; + if (ed_pseudolocalization_enabled && ed_pseudolocalization_numbers_enabled) { + res = res.trim_prefix(ed_pseudolocalization_prefix).trim_suffix(ed_pseudolocalization_suffix); + res.replace(num_systems[num_systems.size() - 1].exp, "e"); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] == num_systems[num_systems.size() - 1].digits[10]) { + data[j] = '.'; + } else { + for (int k = 0; k < 10; k++) { + if (data[j] == num_systems[num_systems.size() - 1].digits[k]) { + data[j] = 0x30 + k; + } + } + } + } + } else { + for (int i = 0; i < num_systems.size() - 1; i++) { + if (num_systems[i].lang.has(lang)) { + if (num_systems[i].digits.is_empty()) { + return p_string; + } + res.replace(num_systems[i].exp, "e"); + char32_t *data = res.ptrw(); + for (int j = 0; j < res.length(); j++) { + if (data[j] == num_systems[i].digits[10]) { + data[j] = '.'; + } else { + for (int k = 0; k < 10; k++) { + if (data[j] == num_systems[i].digits[k]) { + data[j] = 0x30 + k; + } + } + } + } + break; + } + } + } + return res; +} + void TranslationServer::set_tool_translation(const Ref &p_translation) { tool_translation = p_translation; } @@ -578,7 +972,7 @@ Ref TranslationServer::get_tool_translation() const { return tool_translation; } -String TranslationServer::get_tool_locale() { +String TranslationServer::get_tool_locale() const { #ifdef TOOLS_ENABLED if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) { if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) { @@ -613,27 +1007,33 @@ String TranslationServer::get_tool_locale() { } StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const { + if (p_message == StringName()) { + return p_message; + } if (tool_translation.is_valid()) { StringName r = tool_translation->get_message(p_message, p_context); if (r) { - return r; + return ed_pseudolocalization_enabled ? tool_pseudolocalize(r) : r; } } - return p_message; + return ed_pseudolocalization_enabled ? tool_pseudolocalize(p_message) : p_message; } StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const { + if (p_message == StringName() && p_message_plural == StringName()) { + return p_message; + } if (tool_translation.is_valid()) { StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context); if (r) { - return r; + return ed_pseudolocalization_enabled ? tool_pseudolocalize(r) : r; } } if (p_n == 1) { - return p_message; + return ed_pseudolocalization_enabled ? tool_pseudolocalize(p_message) : p_message; } - return p_message_plural; + return ed_pseudolocalization_enabled ? tool_pseudolocalize(p_message_plural) : p_message_plural; } void TranslationServer::set_property_translation(const Ref &p_translation) { @@ -720,11 +1120,31 @@ void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) { } } +#ifdef TOOLS_ENABLED +void TranslationServer::reload_editor_pseudolocalization() { + ed_pseudolocalization_enabled = EDITOR_GET("interface/debug/pseudolocalization/use_pseudolocalization"); + ed_pseudolocalization_accents_enabled = EDITOR_GET("interface/debug/pseudolocalization/replace_with_accents"); + ed_pseudolocalization_double_vowels_enabled = EDITOR_GET("interface/debug/pseudolocalization/double_vowels"); + ed_pseudolocalization_fake_bidi_enabled = EDITOR_GET("interface/debug/pseudolocalization/fake_bidi"); + ed_pseudolocalization_override_enabled = EDITOR_GET("interface/debug/pseudolocalization/override"); + ed_pseudolocalization_numbers_enabled = EDITOR_GET("interface/debug/pseudolocalization/numbers"); + ed_expansion_ratio = EDITOR_GET("interface/debug/pseudolocalization/expansion_ratio"); + ed_pseudolocalization_prefix = EDITOR_GET("interface/debug/pseudolocalization/prefix"); + ed_pseudolocalization_suffix = EDITOR_GET("interface/debug/pseudolocalization/suffix"); + ed_pseudolocalization_skip_placeholders_enabled = EDITOR_GET("interface/debug/pseudolocalization/skip_placeholders"); + + if (OS::get_singleton()->get_main_loop()) { + OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED); + } +} +#endif + void TranslationServer::reload_pseudolocalization() { pseudolocalization_accents_enabled = GLOBAL_GET("internationalization/pseudolocalization/replace_with_accents"); pseudolocalization_double_vowels_enabled = GLOBAL_GET("internationalization/pseudolocalization/double_vowels"); pseudolocalization_fake_bidi_enabled = GLOBAL_GET("internationalization/pseudolocalization/fake_bidi"); pseudolocalization_override_enabled = GLOBAL_GET("internationalization/pseudolocalization/override"); + pseudolocalization_numbers_enabled = GLOBAL_GET("internationalization/pseudolocalization/numbers"); expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio"); pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix"); pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix"); @@ -741,37 +1161,53 @@ StringName TranslationServer::pseudolocalize(const StringName &p_message) const String message = p_message; int length = message.length(); if (pseudolocalization_override_enabled) { - message = get_override_string(message); + message = get_override_string(message, false); } if (pseudolocalization_double_vowels_enabled) { - message = double_vowels(message); + message = double_vowels(message, false); } if (pseudolocalization_accents_enabled) { - message = replace_with_accented_string(message); + message = replace_with_accented_string(message, false); } if (pseudolocalization_fake_bidi_enabled) { - message = wrap_with_fakebidi_characters(message); + message = wrap_with_fakebidi_characters(message, false); } - StringName res = add_padding(message, length); + StringName res = add_padding(message, length, false); return res; } StringName TranslationServer::tool_pseudolocalize(const StringName &p_message) const { String message = p_message; - message = double_vowels(message); - message = replace_with_accented_string(message); - StringName res = "[!!! " + message + " !!!]"; + int length = message.length(); + if (ed_pseudolocalization_override_enabled) { + message = get_override_string(message, true); + } + + if (ed_pseudolocalization_double_vowels_enabled) { + message = double_vowels(message, true); + } + + if (ed_pseudolocalization_accents_enabled) { + message = replace_with_accented_string(message, true); + } + + if (ed_pseudolocalization_fake_bidi_enabled) { + message = wrap_with_fakebidi_characters(message, true); + } + + StringName res = add_padding(message, length, true); return res; } -String TranslationServer::get_override_string(String &p_message) const { +String TranslationServer::get_override_string(String &p_message, bool p_tool) const { String res; + bool skip = p_tool ? ed_pseudolocalization_skip_placeholders_enabled : pseudolocalization_skip_placeholders_enabled; for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + if (skip && is_placeholder(p_message, i)) { res += p_message[i]; res += p_message[i + 1]; i++; @@ -782,10 +1218,11 @@ String TranslationServer::get_override_string(String &p_message) const { return res; } -String TranslationServer::double_vowels(String &p_message) const { +String TranslationServer::double_vowels(String &p_message, bool p_tool) const { String res; + bool skip = p_tool ? ed_pseudolocalization_skip_placeholders_enabled : pseudolocalization_skip_placeholders_enabled; for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + if (skip && is_placeholder(p_message, i)) { res += p_message[i]; res += p_message[i + 1]; i++; @@ -800,10 +1237,11 @@ String TranslationServer::double_vowels(String &p_message) const { return res; }; -String TranslationServer::replace_with_accented_string(String &p_message) const { +String TranslationServer::replace_with_accented_string(String &p_message, bool p_tool) const { String res; + bool skip = p_tool ? ed_pseudolocalization_skip_placeholders_enabled : pseudolocalization_skip_placeholders_enabled; for (int i = 0; i < p_message.length(); i++) { - if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + if (skip && is_placeholder(p_message, i)) { res += p_message[i]; res += p_message[i + 1]; i++; @@ -819,18 +1257,19 @@ String TranslationServer::replace_with_accented_string(String &p_message) const return res; } -String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const { +String TranslationServer::wrap_with_fakebidi_characters(String &p_message, bool p_tool) const { String res; char32_t fakebidiprefix = U'\u202e'; char32_t fakebidisuffix = U'\u202c'; res += fakebidiprefix; + bool skip = p_tool ? ed_pseudolocalization_skip_placeholders_enabled : pseudolocalization_skip_placeholders_enabled; // The fake bidi unicode gets popped at every newline so pushing it back at every newline. for (int i = 0; i < p_message.length(); i++) { if (p_message[i] == '\n') { res += fakebidisuffix; res += p_message[i]; res += fakebidiprefix; - } else if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) { + } else if (skip && is_placeholder(p_message, i)) { res += fakebidisuffix; res += p_message[i]; res += p_message[i + 1]; @@ -844,10 +1283,10 @@ String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const return res; } -String TranslationServer::add_padding(const String &p_message, int p_length) const { - String underscores = String("_").repeat(p_length * expansion_ratio / 2); - String prefix = pseudolocalization_prefix + underscores; - String suffix = underscores + pseudolocalization_suffix; +String TranslationServer::add_padding(const String &p_message, int p_length, bool p_tool) const { + String underscores = String("_").repeat(p_length * (p_tool ? ed_expansion_ratio : expansion_ratio) / 2); + String prefix = (p_tool ? ed_pseudolocalization_prefix : pseudolocalization_prefix) + underscores; + String suffix = underscores + (p_tool ? ed_pseudolocalization_suffix : pseudolocalization_suffix); return prefix + p_message + suffix; } @@ -923,6 +1362,10 @@ void TranslationServer::_bind_methods() { ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear); + ClassDB::bind_method(D_METHOD("format_number", "number", "language"), &TranslationServer::format_number, DEFVAL(String())); + ClassDB::bind_method(D_METHOD("get_percent_sign", "language"), &TranslationServer::get_percent_sign, DEFVAL(String())); + ClassDB::bind_method(D_METHOD("parse_number", "number", "language"), &TranslationServer::parse_number, DEFVAL(String())); + ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales); ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled); @@ -944,4 +1387,5 @@ void TranslationServer::load_translations() { TranslationServer::TranslationServer() { singleton = this; init_locale_info(); + init_num_systems(); } diff --git a/core/string/translation_server.h b/core/string/translation_server.h index ebe81d97124c..22ad0a7934d7 100644 --- a/core/string/translation_server.h +++ b/core/string/translation_server.h @@ -45,6 +45,16 @@ class TranslationServer : public Object { Ref doc_translation; Ref extractable_translation; + struct NumSystemData { + HashSet lang; + String digits; + String percent_sign; + String exp; + }; + + Vector num_systems; + void init_num_systems(); + bool enabled = true; bool pseudolocalization_enabled = false; @@ -53,16 +63,27 @@ class TranslationServer : public Object { bool pseudolocalization_fake_bidi_enabled = false; bool pseudolocalization_override_enabled = false; bool pseudolocalization_skip_placeholders_enabled = false; + bool pseudolocalization_numbers_enabled = false; float expansion_ratio = 0.0; String pseudolocalization_prefix; String pseudolocalization_suffix; - StringName tool_pseudolocalize(const StringName &p_message) const; - String get_override_string(String &p_message) const; - String double_vowels(String &p_message) const; - String replace_with_accented_string(String &p_message) const; - String wrap_with_fakebidi_characters(String &p_message) const; - String add_padding(const String &p_message, int p_length) const; + bool ed_pseudolocalization_enabled = false; + bool ed_pseudolocalization_accents_enabled = false; + bool ed_pseudolocalization_double_vowels_enabled = false; + bool ed_pseudolocalization_fake_bidi_enabled = false; + bool ed_pseudolocalization_override_enabled = false; + bool ed_pseudolocalization_skip_placeholders_enabled = false; + bool ed_pseudolocalization_numbers_enabled = false; + float ed_expansion_ratio = 0.0; + String ed_pseudolocalization_prefix; + String ed_pseudolocalization_suffix; + + String get_override_string(String &p_message, bool p_tool) const; + String double_vowels(String &p_message, bool p_tool) const; + String replace_with_accented_string(String &p_message, bool p_tool) const; + String wrap_with_fakebidi_characters(String &p_message, bool p_tool) const; + String add_padding(const String &p_message, int p_length, bool p_tool) const; const char32_t *get_accented_version(char32_t p_character) const; bool is_placeholder(String &p_message, int p_index) const; @@ -75,6 +96,8 @@ class TranslationServer : public Object { static void _bind_methods(); #ifndef DISABLE_DEPRECATED + String _get_tool_locale_bind_compat_96105(); + static void _bind_compatibility_methods(); #endif @@ -125,16 +148,27 @@ class TranslationServer : public Object { StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const; StringName pseudolocalize(const StringName &p_message) const; + StringName tool_pseudolocalize(const StringName &p_message) const; bool is_pseudolocalization_enabled() const; void set_pseudolocalization_enabled(bool p_enabled); void reload_pseudolocalization(); +#ifdef TOOLS_ENABLED + void reload_editor_pseudolocalization(); +#endif + + String format_number(const String &p_number, const String &p_language = "") const; + String tool_format_number(const String &p_number) const; + String parse_number(const String &p_string, const String &p_language = "") const; + String tool_parse_number(const String &p_string) const; + String get_percent_sign(const String &p_language = "") const; + String get_tool_percent_sign() const; String standardize_locale(const String &p_locale) const; int compare_locales(const String &p_locale_a, const String &p_locale_b) const; - String get_tool_locale(); + String get_tool_locale() const; void set_tool_translation(const Ref &p_translation); Ref get_tool_translation() const; StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const; diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 320b119b6a8e..dfe5aecc92a6 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -606,6 +606,36 @@ Input accumulation can be disabled to get slightly more precise/reactive input at the cost of increased CPU usage. [b]Note:[/b] Input accumulation is [i]enabled[/i] by default. + + Double vowels in strings during pseudolocalization to simulate the lengthening of text due to localization. + + + The expansion ratio to use during pseudolocalization. A value of [code]0.3[/code] is sufficient for most practical purposes, and will increase the length of each string by 30%. + + + If [code]true[/code], emulate bidirectional (right-to-left) text when pseudolocalization is enabled. This can be used to spot issues with RTL layout and UI mirroring that will crop up if the project is localized to RTL languages such as Arabic or Hebrew. + + + If [code]true[/code], pseudolocalization for numbers formatted by [method TranslationServer.format_number] is enabled. + + + Replace all characters in the string with [code]*[/code]. Useful for finding non-localizable strings. + + + Prefix that will be prepended to the pseudolocalized string. + + + Replace all characters with their accented variants during pseudolocalization. + + + Skip placeholders for string formatting like [code]%s[/code] or [code]%f[/code] during pseudolocalization. Useful to identify strings which need additional control characters to display correctly. + + + Suffix that will be appended to the pseudolocalized string. + + + If [code]true[/code], enables pseudolocalization for the editor. This can be used to spot untranslatable strings or layout issues that may occur once the project is localized to languages that have longer strings than the source language. + How to position the Cancel and OK buttons in the editor's [AcceptDialog]s. Different platforms have different standard behaviors for this, which can be overridden using this setting. This is useful if you use Godot both on Windows and macOS/Linux and your Godot muscle memory is stronger than your OS specific one. - [b]Auto[/b] follows the platform convention: Cancel first on macOS and Linux, OK first on Windows. diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index efca7adb9253..d57d207701a2 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1460,6 +1460,9 @@ If [code]true[/code], emulate bidirectional (right-to-left) text when pseudolocalization is enabled. This can be used to spot issues with RTL layout and UI mirroring that will crop up if the project is localized to RTL languages such as Arabic or Hebrew. + + If [code]true[/code], pseudolocalization for numbers formatted by [method TranslationServer.format_number] is enabled. + Replace all characters in the string with [code]*[/code]. Useful for finding non-localizable strings. diff --git a/doc/classes/TextServer.xml b/doc/classes/TextServer.xml index 9d476691bfdc..a9da021ab908 100644 --- a/doc/classes/TextServer.xml +++ b/doc/classes/TextServer.xml @@ -1026,7 +1026,7 @@ Returns the dictionary of the supported OpenType variation coordinates. - + @@ -1142,7 +1142,7 @@ Converts readable feature, variation, script, or language name to OpenType tag. - + @@ -1159,7 +1159,7 @@ Default implementation of the BiDi algorithm override function. See [enum StructuredTextParser] for more info. - + diff --git a/doc/classes/TextServerExtension.xml b/doc/classes/TextServerExtension.xml index 3c27404f8e38..b5ea437cf92e 100644 --- a/doc/classes/TextServerExtension.xml +++ b/doc/classes/TextServerExtension.xml @@ -1130,15 +1130,6 @@ Returns the dictionary of the supported OpenType variation coordinates. - - - - - - [b]Optional.[/b] - Converts a number from the Western Arabic (0..9) to the numeral systems used in [param language]. - - @@ -1247,15 +1238,6 @@ Converts readable feature, variation, script, or language name to OpenType tag. - - - - - - [b]Optional.[/b] - Converts [param number] from the numeral systems used in [param language] to Western Arabic (0..9). - - @@ -1266,14 +1248,6 @@ Default implementation of the BiDi algorithm override function. See [enum TextServer.StructuredTextParser] for more info. - - - - - [b]Optional.[/b] - Returns percent sign used in the [param language]. - - diff --git a/doc/classes/TranslationServer.xml b/doc/classes/TranslationServer.xml index db1a65278c91..e185044b2768 100644 --- a/doc/classes/TranslationServer.xml +++ b/doc/classes/TranslationServer.xml @@ -32,6 +32,14 @@ Compares two locales and returns a similarity score between [code]0[/code] (no match) and [code]10[/code] (full match). + + + + + + Converts a number from the Western Arabic (0..9) to the numeral systems used in [param language]. + + @@ -84,6 +92,13 @@ Returns a locale's language and its variant (e.g. [code]"en_US"[/code] would return [code]"English (United States)"[/code]). + + + + + Returns percent sign used in the [param language]. + + @@ -91,7 +106,7 @@ Returns a readable script name for the [param script] code. - + Returns the current locale of the editor. @@ -106,6 +121,14 @@ It will return [code]null[/code] if there is no [Translation] instance that matches the [param locale]. + + + + + + Converts [param number] from the numeral systems used in [param language] to Western Arabic (0..9). + + diff --git a/editor/animation_bezier_editor.cpp b/editor/animation_bezier_editor.cpp index 5196857240c0..698e51467507 100644 --- a/editor/animation_bezier_editor.cpp +++ b/editor/animation_bezier_editor.cpp @@ -30,6 +30,7 @@ #include "animation_bezier_editor.h" +#include "core/string/translation_server.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -468,7 +469,7 @@ void AnimationBezierTrackEdit::_notification(int p_what) { draw_line(Point2(limit, i), Point2(right_limit, i), lc, Math::round(EDSCALE)); Color c = color; c.a *= 0.5; - draw_string(font, Point2(limit + 8, i - 2), TS->format_number(rtos(Math::snapped((iv + 1) * scale, step))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, c); + draw_string(font, Point2(limit + 8, i - 2), TranslationServer::get_singleton()->tool_format_number(rtos(Math::snapped((iv + 1) * scale, step))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, c); } first = false; @@ -555,8 +556,8 @@ void AnimationBezierTrackEdit::_notification(int p_what) { ep.point_rect.size = bezier_icon->get_size(); if (selection.has(IntPair(i, j))) { draw_texture(selected_icon, ep.point_rect.position); - draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TS->format_number(rtos(Math::snapped(offset, 0.0001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); - draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TS->format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); + draw_string(font, ep.point_rect.position + Vector2(8, -font->get_height(font_size) - 8), TTR("Time:") + " " + TranslationServer::get_singleton()->tool_format_number(rtos(Math::snapped(offset, 0.0001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); + draw_string(font, ep.point_rect.position + Vector2(8, -8), TTR("Value:") + " " + TranslationServer::get_singleton()->tool_format_number(rtos(Math::snapped(value, 0.001))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, accent); } else { Color track_color = Color(1, 1, 1, 1); if (i != selected_track) { diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp index 5706853b2a75..8a26a2786679 100644 --- a/editor/animation_track_editor.cpp +++ b/editor/animation_track_editor.cpp @@ -33,6 +33,7 @@ #include "animation_track_editor_plugins.h" #include "core/error/error_macros.h" #include "core/input/input.h" +#include "core/string/translation_server.h" #include "editor/animation_bezier_editor.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" @@ -2676,7 +2677,7 @@ String AnimationTrackEdit::get_tooltip(const Point2 &p_pos) const { } if (key_idx != -1) { - String text = TTR("Time (s):") + " " + TS->format_number(rtos(Math::snapped(animation->track_get_key_time(track, key_idx), SECOND_DECIMAL))) + "\n"; + String text = TTR("Time (s):") + " " + TranslationServer::get_singleton()->tool_format_number(rtos(Math::snapped(animation->track_get_key_time(track, key_idx), SECOND_DECIMAL))) + "\n"; switch (animation->track_get_type(track)) { case Animation::TYPE_POSITION_3D: { Vector3 t = animation->track_get_key_value(track, key_idx); diff --git a/editor/debugger/editor_performance_profiler.cpp b/editor/debugger/editor_performance_profiler.cpp index 1ea9a6653478..b8f23953dc78 100644 --- a/editor/debugger/editor_performance_profiler.cpp +++ b/editor/debugger/editor_performance_profiler.cpp @@ -30,6 +30,7 @@ #include "editor_performance_profiler.h" +#include "core/string/translation_server.h" #include "editor/editor_property_name_processor.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" @@ -85,10 +86,10 @@ String EditorPerformanceProfiler::_create_label(float p_value, Performance::Moni return String::humanize_size(p_value); } case Performance::MONITOR_TYPE_TIME: { - return TS->format_number(rtos(p_value * 1000).pad_decimals(2)) + " " + TTR("ms"); + return TranslationServer::get_singleton()->tool_format_number(rtos(p_value * 1000).pad_decimals(2)) + " " + TTR("ms"); } default: { - return TS->format_number(rtos(p_value)); + return TranslationServer::get_singleton()->tool_format_number(rtos(p_value)); } } } diff --git a/editor/debugger/editor_profiler.cpp b/editor/debugger/editor_profiler.cpp index 24bb6948608d..28698ea2e27e 100644 --- a/editor/debugger/editor_profiler.cpp +++ b/editor/debugger/editor_profiler.cpp @@ -31,6 +31,7 @@ #include "editor_profiler.h" #include "core/os/os.h" +#include "core/string/translation_server.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" @@ -119,19 +120,19 @@ static String _get_percent_txt(float p_value, float p_total) { p_total = 0.00001; } - return TS->format_number(String::num((p_value / p_total) * 100, 1)) + TS->percent_sign(); + return TranslationServer::get_singleton()->tool_format_number(String::num((p_value / p_total) * 100, 1)) + TranslationServer::get_singleton()->get_tool_percent_sign(); } String EditorProfiler::_get_time_as_text(const Metric &m, float p_time, int p_calls) { const int dmode = display_mode->get_selected(); if (dmode == DISPLAY_FRAME_TIME) { - return TS->format_number(rtos(p_time * 1000).pad_decimals(2)) + " " + TTR("ms"); + return TranslationServer::get_singleton()->tool_format_number(rtos(p_time * 1000).pad_decimals(2)) + " " + TTR("ms"); } else if (dmode == DISPLAY_AVERAGE_TIME) { if (p_calls == 0) { - return TS->format_number("0.00") + " " + TTR("ms"); + return TranslationServer::get_singleton()->tool_format_number("0.00") + " " + TTR("ms"); } else { - return TS->format_number(rtos((p_time / p_calls) * 1000).pad_decimals(2)) + " " + TTR("ms"); + return TranslationServer::get_singleton()->tool_format_number(rtos((p_time / p_calls) * 1000).pad_decimals(2)) + " " + TTR("ms"); } } else if (dmode == DISPLAY_FRAME_PERCENT) { return _get_percent_txt(p_time, m.frame_time); diff --git a/editor/debugger/editor_visual_profiler.cpp b/editor/debugger/editor_visual_profiler.cpp index 17977fcb29ba..e31a01d11972 100644 --- a/editor/debugger/editor_visual_profiler.cpp +++ b/editor/debugger/editor_visual_profiler.cpp @@ -31,6 +31,7 @@ #include "editor_visual_profiler.h" #include "core/os/os.h" +#include "core/string/translation_server.h" #include "editor/editor_settings.h" #include "editor/editor_string_names.h" #include "editor/themes/editor_scale.h" @@ -116,9 +117,9 @@ String EditorVisualProfiler::_get_time_as_text(float p_time) { int dmode = display_mode->get_selected(); if (dmode == DISPLAY_FRAME_TIME) { - return TS->format_number(String::num(p_time, 2)) + " " + TTR("ms"); + return TranslationServer::get_singleton()->tool_format_number(String::num(p_time, 2)) + " " + TTR("ms"); } else if (dmode == DISPLAY_FRAME_PERCENT) { - return TS->format_number(String::num(p_time * 100 / graph_limit, 2)) + " " + TS->percent_sign(); + return TranslationServer::get_singleton()->tool_format_number(String::num(p_time * 100 / graph_limit, 2)) + " " + TranslationServer::get_singleton()->get_tool_percent_sign(); } return "err"; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index a8f8edaae882..952bc222128e 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -832,6 +832,10 @@ void EditorNode::_notification(int p_what) { } } + if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/debug/pseudolocalization")) { + TranslationServer::get_singleton()->reload_editor_pseudolocalization(); + } + if (EditorSettings::get_singleton()->check_changed_settings_in_group("interface/editor")) { _update_update_spinner(); _update_vsync_mode(); @@ -6678,6 +6682,7 @@ EditorNode::EditorNode() { if (!EditorSettings::get_singleton()) { EditorSettings::create(); } + TranslationServer::get_singleton()->reload_editor_pseudolocalization(); ED_SHORTCUT("editor/lock_selected_nodes", TTR("Lock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | Key::L); ED_SHORTCUT("editor/unlock_selected_nodes", TTR("Unlock Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::L); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 19a4165041d2..cf5e65702684 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -31,6 +31,7 @@ #include "editor_properties.h" #include "core/config/project_settings.h" +#include "core/string/translation_server.h" #include "editor/create_dialog.h" #include "editor/editor_node.h" #include "editor/editor_properties_array_dict.h" @@ -1560,7 +1561,7 @@ void EditorPropertyEasing::_draw_easing() { } else { decimals = 1; } - f->draw_string(ci, Point2(10, 10 + f->get_ascent(font_size)), TS->format_number(rtos(exp).pad_decimals(decimals)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + f->draw_string(ci, Point2(10, 10 + f->get_ascent(font_size)), TranslationServer::get_singleton()->tool_format_number(rtos(exp).pad_decimals(decimals)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } void EditorPropertyEasing::update_property() { @@ -1576,7 +1577,7 @@ void EditorPropertyEasing::_set_preset(int p_preset) { void EditorPropertyEasing::_setup_spin() { spin->setup_and_show(); - spin->get_line_edit()->set_text(TS->format_number(rtos(get_edited_property_value()))); + spin->get_line_edit()->set_text(TranslationServer::get_singleton()->tool_format_number(rtos(get_edited_property_value()))); spin->show(); } diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index b9d530353c79..f042e9cf321a 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -547,6 +547,19 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/multi_window/maximize_window", false, ""); set_restart_if_changed("interface/multi_window/enable", true); + // Editor pseudolocalization + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/use_pseudolocalization", false, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/replace_with_accents", true, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/double_vowels", false, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/fake_bidi", false, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/override", false, ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/numbers", true, ""); + EDITOR_SETTING(Variant::FLOAT, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/expansion_ratio", 0.0, ""); + EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/prefix", "[", ""); + EDITOR_SETTING(Variant::STRING, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/suffix", "]", ""); + EDITOR_SETTING(Variant::BOOL, PROPERTY_HINT_NONE, "interface/debug/pseudolocalization/skip_placeholders", true, ""); + set_restart_if_changed("interface/debug/pseudolocalization/use_pseudolocalization", true); + /* Filesystem */ // External Programs diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 9f9bdb37b36b..5270926f89b4 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -33,6 +33,7 @@ #include "core/input/input.h" #include "core/math/expression.h" #include "core/os/keyboard.h" +#include "core/string/translation_server.h" #include "editor/editor_settings.h" #include "editor/themes/editor_scale.h" @@ -43,13 +44,13 @@ bool EditorSpinSlider::is_text_field() const { String EditorSpinSlider::get_tooltip(const Point2 &p_pos) const { if (!read_only && grabber->is_visible()) { Key key = (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) ? Key::META : Key::CTRL; - return TS->format_number(rtos(get_value())) + "\n\n" + vformat(TTR("Hold %s to round to integers.\nHold Shift for more precise changes."), find_keycode_name(key)); + return TranslationServer::get_singleton()->tool_format_number(rtos(get_value())) + "\n\n" + vformat(TTR("Hold %s to round to integers.\nHold Shift for more precise changes."), find_keycode_name(key)); } - return TS->format_number(rtos(get_value())); + return TranslationServer::get_singleton()->tool_format_number(rtos(get_value())); } String EditorSpinSlider::get_text_value() const { - return TS->format_number(String::num(get_value(), Math::range_step_decimals(get_step()))); + return TranslationServer::get_singleton()->tool_format_number(String::num(get_value(), Math::range_step_decimals(get_step()))); } void EditorSpinSlider::gui_input(const Ref &p_event) { @@ -561,13 +562,13 @@ void EditorSpinSlider::_evaluate_input_text() { // Convert commas ',' to dots '.' for French/German etc. keyboard layouts. String text = value_input->get_text().replace(",", "."); text = text.replace(";", ","); - text = TS->parse_number(text); + text = TranslationServer::get_singleton()->tool_parse_number(text); Error err = expr->parse(text); if (err != OK) { // If the expression failed try without converting commas to dots - they might have been for parameter separation. text = value_input->get_text(); - text = TS->parse_number(text); + text = TranslationServer::get_singleton()->tool_parse_number(text); err = expr->parse(text); if (err != OK) { diff --git a/editor/gui/editor_zoom_widget.cpp b/editor/gui/editor_zoom_widget.cpp index 341da7bfaf4a..67ba753d50a8 100644 --- a/editor/gui/editor_zoom_widget.cpp +++ b/editor/gui/editor_zoom_widget.cpp @@ -31,6 +31,7 @@ #include "editor_zoom_widget.h" #include "core/os/keyboard.h" +#include "core/string/translation_server.h" #include "editor/editor_settings.h" #include "editor/themes/editor_scale.h" @@ -41,12 +42,12 @@ void EditorZoomWidget::_update_zoom_label() { // lower the editor scale to increase the available real estate, // even if their display doesn't have a particularly low DPI. if (zoom >= 10) { - zoom_text = TS->format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100))); + zoom_text = TranslationServer::get_singleton()->tool_format_number(rtos(Math::round((zoom / MAX(1, EDSCALE)) * 100))); } else { // 2 decimal places if the zoom is below 10%, 1 decimal place if it's below 1000%. - zoom_text = TS->format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, (zoom >= 0.1) ? 0.1 : 0.01))); + zoom_text = TranslationServer::get_singleton()->tool_format_number(rtos(Math::snapped((zoom / MAX(1, EDSCALE)) * 100, (zoom >= 0.1) ? 0.1 : 0.01))); } - zoom_text += " " + TS->percent_sign(); + zoom_text += " " + TranslationServer::get_singleton()->get_tool_percent_sign(); zoom_reset->set_text(zoom_text); } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 1afe2ddda71f..5838a3d50b0b 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "core/config/project_settings.h" #include "core/input/input.h" #include "core/os/keyboard.h" +#include "core/string/translation_server.h" #include "editor/debugger/editor_debugger_node.h" #include "editor/editor_node.h" #include "editor/editor_settings.h" @@ -2842,14 +2843,14 @@ void CanvasItemEditor::_draw_text_at_position(Point2 p_position, const String &p } void CanvasItemEditor::_draw_margin_at_position(int p_value, Point2 p_position, Side p_side) { - String str = TS->format_number(vformat("%d " + TTR("px"), p_value)); + String str = TranslationServer::get_singleton()->tool_format_number(vformat("%d " + TTR("px"), p_value)); if (p_value != 0) { _draw_text_at_position(p_position, str, p_side); } } void CanvasItemEditor::_draw_percentage_at_position(real_t p_value, Point2 p_position, Side p_side) { - String str = TS->format_number(vformat("%.1f ", p_value * 100.0)) + TS->percent_sign(); + String str = TranslationServer::get_singleton()->tool_format_number(vformat("%.1f ", p_value * 100.0)) + TranslationServer::get_singleton()->get_tool_percent_sign(); if (p_value != 0) { _draw_text_at_position(p_position, str, p_side); } @@ -2892,7 +2893,7 @@ void CanvasItemEditor::_draw_guides() { Color outline_color = text_color.inverted(); const float outline_size = 2; if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_V_GUIDE) { - String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).x))); + String str = TranslationServer::get_singleton()->tool_format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).x))); Ref font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)); int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)); Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); @@ -2901,7 +2902,7 @@ void CanvasItemEditor::_draw_guides() { viewport->draw_line(Point2(dragged_guide_pos.x, 0), Point2(dragged_guide_pos.x, viewport->get_size().y), guide_color, Math::round(EDSCALE)); } if (drag_type == DRAG_DOUBLE_GUIDE || drag_type == DRAG_H_GUIDE) { - String str = TS->format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).y))); + String str = TranslationServer::get_singleton()->tool_format_number(vformat("%d px", Math::round(xform.affine_inverse().xform(dragged_guide_pos).y))); Ref font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts)); int font_size = 1.3 * get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts)); Size2 text_size = font->get_string_size(str, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size); @@ -2978,7 +2979,7 @@ void CanvasItemEditor::_draw_rulers() { if (i % (major_subdivision * minor_subdivision) == 0) { viewport->draw_line(Point2(position.x, 0), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE)); real_t val = (ruler_transform * major_subdivide * minor_subdivide).xform(Point2(i, 0)).x; - viewport->draw_string(font, Point2(position.x + 2, font->get_height(font_size)), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + viewport->draw_string(font, Point2(position.x + 2, font->get_height(font_size)), TranslationServer::get_singleton()->tool_format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } else { if (i % minor_subdivision == 0) { viewport->draw_line(Point2(position.x, RULER_WIDTH * 0.33), Point2(position.x, RULER_WIDTH), graduation_color, Math::round(EDSCALE)); @@ -2998,7 +2999,7 @@ void CanvasItemEditor::_draw_rulers() { Transform2D text_xform = Transform2D(-Math_PI / 2.0, Point2(font->get_height(font_size), position.y - 2)); viewport->draw_set_transform_matrix(viewport->get_transform() * text_xform); - viewport->draw_string(font, Point2(), TS->format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + viewport->draw_string(font, Point2(), TranslationServer::get_singleton()->tool_format_number(vformat(((int)val == val) ? "%d" : "%.1f", val)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); viewport->draw_set_transform_matrix(viewport->get_transform()); } else { @@ -3164,8 +3165,8 @@ void CanvasItemEditor::_draw_ruler_tool() { return; } - viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, text_pos, TS->format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + viewport->draw_string_outline(font, text_pos, TranslationServer::get_singleton()->tool_format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos, TranslationServer::get_singleton()->tool_format_number(vformat("%.1f px", length_vector.length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); if (draw_secondary_lines) { const int horizontal_angle = round(180 * horizontal_angle_rad / Math_PI); @@ -3173,19 +3174,19 @@ void CanvasItemEditor::_draw_ruler_tool() { Point2 text_pos2 = text_pos; text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2); - viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); + viewport->draw_string_outline(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%.1f px", length_vector.y)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); Point2 v_angle_text_pos; v_angle_text_pos.x = CLAMP(begin.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); v_angle_text_pos.y = begin.y < end.y ? MIN(text_pos2.y - 2 * text_height, begin.y - text_height * 0.5) : MAX(text_pos2.y + text_height * 3, begin.y + text_height * 1.5); - viewport->draw_string_outline(font, v_angle_text_pos, TS->format_number(vformat(U"%d°", vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, v_angle_text_pos, TS->format_number(vformat(U"%d°", vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); + viewport->draw_string_outline(font, v_angle_text_pos, TranslationServer::get_singleton()->tool_format_number(vformat(U"%d°", vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, v_angle_text_pos, TranslationServer::get_singleton()->tool_format_number(vformat(U"%d°", vertical_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); text_pos2 = text_pos; text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y - text_height / 2) : MAX(text_pos.y + text_height * 2, end.y - text_height / 2); - viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); + viewport->draw_string_outline(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%.1f px", length_vector.x)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); Point2 h_angle_text_pos; h_angle_text_pos.x = CLAMP(end.x - angle_text_width / 2, angle_text_width / 2, viewport->get_rect().size.x - angle_text_width); @@ -3202,8 +3203,8 @@ void CanvasItemEditor::_draw_ruler_tool() { h_angle_text_pos.y = MIN(text_pos.y - height_multiplier * text_height, MIN(end.y - text_height * 0.5, text_pos2.y - height_multiplier * text_height)); } } - viewport->draw_string_outline(font, h_angle_text_pos, TS->format_number(vformat(U"%d°", horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, h_angle_text_pos, TS->format_number(vformat(U"%d°", horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); + viewport->draw_string_outline(font, h_angle_text_pos, TranslationServer::get_singleton()->tool_format_number(vformat(U"%d°", horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, h_angle_text_pos, TranslationServer::get_singleton()->tool_format_number(vformat(U"%d°", horizontal_angle)), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); } if (grid_snap_active) { @@ -3212,21 +3213,21 @@ void CanvasItemEditor::_draw_ruler_tool() { text_pos.y = CLAMP(text_pos.y, text_height * 2.5, viewport->get_rect().size.y - text_height / 2); if (draw_secondary_lines) { - viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, text_pos, TS->format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + viewport->draw_string_outline(font, text_pos, TranslationServer::get_singleton()->tool_format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos, TranslationServer::get_singleton()->tool_format_number(vformat("%.2f " + TTR("units"), (length_vector / grid_step).length())), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); Point2 text_pos2 = text_pos; text_pos2.x = begin.x < text_pos.x ? MIN(text_pos.x - text_width, begin.x - text_width / 2) : MAX(text_pos.x + text_width, begin.x - text_width / 2); - viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); + viewport->draw_string_outline(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%d " + TTR("units"), roundf(length_vector.y / grid_step.y))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); text_pos2 = text_pos; text_pos2.y = end.y < text_pos.y ? MIN(text_pos.y - text_height * 2, end.y + text_height / 2) : MAX(text_pos.y + text_height * 2, end.y + text_height / 2); - viewport->draw_string_outline(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, text_pos2, TS->format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); + viewport->draw_string_outline(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos2, TranslationServer::get_singleton()->tool_format_number(vformat("%d " + TTR("units"), roundf(length_vector.x / grid_step.x))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_secondary_color); } else { - viewport->draw_string_outline(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); - viewport->draw_string(font, text_pos, TS->format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); + viewport->draw_string_outline(font, text_pos, TranslationServer::get_singleton()->tool_format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, outline_size, outline_color); + viewport->draw_string(font, text_pos, TranslationServer::get_singleton()->tool_format_number(vformat("%d " + TTR("units"), roundf((length_vector / grid_step).length()))), HORIZONTAL_ALIGNMENT_LEFT, -1, font_size, font_color); } } } else { @@ -3803,7 +3804,7 @@ void CanvasItemEditor::_draw_message() { double snap = EDITOR_GET("interface/inspector/default_float_step"); int snap_step_decimals = Math::range_step_decimals(snap); -#define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals))) +#define FORMAT(value) (TranslationServer::get_singleton()->tool_format_number(String::num(value, snap_step_decimals))) switch (drag_type) { case DRAG_MOVE: @@ -6144,7 +6145,7 @@ bool CanvasItemEditorViewport::can_drop_data(const Point2 &p_point, const Varian } else { double snap = EDITOR_GET("interface/inspector/default_float_step"); int snap_step_decimals = Math::range_step_decimals(snap); -#define FORMAT(value) (TS->format_number(String::num(value, snap_step_decimals))) +#define FORMAT(value) (TranslationServer::get_singleton()->tool_format_number(String::num(value, snap_step_decimals))) Vector2 preview_node_pos = preview_node->get_global_position(); canvas_item_editor->message = TTR("Instantiating: ") + "(" + FORMAT(preview_node_pos.x) + ", " + FORMAT(preview_node_pos.y) + ") px"; } diff --git a/editor/project_manager.cpp b/editor/project_manager.cpp index 3de9ebcfddf3..a60114a39794 100644 --- a/editor/project_manager.cpp +++ b/editor/project_manager.cpp @@ -39,6 +39,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/os/time.h" +#include "core/string/translation_server.h" #include "core/version.h" #include "editor/editor_about.h" #include "editor/editor_settings.h" @@ -1052,6 +1053,7 @@ ProjectManager::ProjectManager() { EditorSettings::create(); } EditorSettings::get_singleton()->set_optimize_save(false); // Just write settings as they come. + TranslationServer::get_singleton()->reload_editor_pseudolocalization(); { bool agile_input_event_flushing = EDITOR_GET("input/buffering/agile_event_flushing"); diff --git a/misc/extension_api_validation/4.3-stable.expected b/misc/extension_api_validation/4.3-stable.expected index 733d85c46e74..2d2c10c29d2e 100644 --- a/misc/extension_api_validation/4.3-stable.expected +++ b/misc/extension_api_validation/4.3-stable.expected @@ -26,3 +26,14 @@ Validate extension JSON: Error: Field 'classes/RenderingDevice/methods/draw_list draw_list_begin added a new optional debug argument called breadcrumb. There used to be an Array argument as arg #9 initially, then changed to typedarray::RID in 4.1, and finally removed in 4.3. Since we're adding a new one at the same location, we need to silence those warnings for 4.1 and 4.3. + + +GH-96105 +-------- +Validate extension JSON: API was removed: classes/TextServerExtension/methods/_format_number +Validate extension JSON: API was removed: classes/TextServerExtension/methods/_parse_number +Validate extension JSON: API was removed: classes/TextServerExtension/methods/_percent_sign +Validate extension JSON: Error: Field 'classes/TranslationServer/methods/get_tool_locale': is_const changed value in new API, from false to true. + +Number formatting methods moved to TranslationServer. Compatibility methods added to the base TextServer. +TranslationServer.get_tool_locale changed to const method. Compatibility methods registered. diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index d0c22e9e4da7..fe001eed6ee5 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -6633,248 +6633,6 @@ double TextServerAdvanced::_shaped_text_get_underline_thickness(const RID &p_sha return sd->uthk; } -void TextServerAdvanced::_insert_num_systems_lang() { - // Eastern Arabic numerals. - { - NumSystemData ar; - ar.lang.insert(StringName("ar")); // Arabic - ar.lang.insert(StringName("ar_AE")); - ar.lang.insert(StringName("ar_BH")); - ar.lang.insert(StringName("ar_DJ")); - ar.lang.insert(StringName("ar_EG")); - ar.lang.insert(StringName("ar_ER")); - ar.lang.insert(StringName("ar_IL")); - ar.lang.insert(StringName("ar_IQ")); - ar.lang.insert(StringName("ar_JO")); - ar.lang.insert(StringName("ar_KM")); - ar.lang.insert(StringName("ar_KW")); - ar.lang.insert(StringName("ar_LB")); - ar.lang.insert(StringName("ar_MR")); - ar.lang.insert(StringName("ar_OM")); - ar.lang.insert(StringName("ar_PS")); - ar.lang.insert(StringName("ar_QA")); - ar.lang.insert(StringName("ar_SA")); - ar.lang.insert(StringName("ar_SD")); - ar.lang.insert(StringName("ar_SO")); - ar.lang.insert(StringName("ar_SS")); - ar.lang.insert(StringName("ar_SY")); - ar.lang.insert(StringName("ar_TD")); - ar.lang.insert(StringName("ar_YE")); - ar.lang.insert(StringName("ckb")); // Central Kurdish - ar.lang.insert(StringName("ckb_IQ")); - ar.lang.insert(StringName("ckb_IR")); - ar.lang.insert(StringName("sd")); // Sindhi - ar.lang.insert(StringName("sd_PK")); - ar.lang.insert(StringName("sd_Arab")); - ar.lang.insert(StringName("sd_Arab_PK")); - ar.digits = U"٠١٢٣٤٥٦٧٨٩٫"; - ar.percent_sign = U"٪"; - ar.exp = U"اس"; - num_systems.push_back(ar); - } - - // Persian and Urdu numerals. - { - NumSystemData pr; - pr.lang.insert(StringName("fa")); // Persian - pr.lang.insert(StringName("fa_AF")); - pr.lang.insert(StringName("fa_IR")); - pr.lang.insert(StringName("ks")); // Kashmiri - pr.lang.insert(StringName("ks_IN")); - pr.lang.insert(StringName("ks_Arab")); - pr.lang.insert(StringName("ks_Arab_IN")); - pr.lang.insert(StringName("lrc")); // Northern Luri - pr.lang.insert(StringName("lrc_IQ")); - pr.lang.insert(StringName("lrc_IR")); - pr.lang.insert(StringName("mzn")); // Mazanderani - pr.lang.insert(StringName("mzn_IR")); - pr.lang.insert(StringName("pa_PK")); // Panjabi - pr.lang.insert(StringName("pa_Arab")); - pr.lang.insert(StringName("pa_Arab_PK")); - pr.lang.insert(StringName("ps")); // Pushto - pr.lang.insert(StringName("ps_AF")); - pr.lang.insert(StringName("ps_PK")); - pr.lang.insert(StringName("ur_IN")); // Urdu - pr.lang.insert(StringName("uz_AF")); // Uzbek - pr.lang.insert(StringName("uz_Arab")); - pr.lang.insert(StringName("uz_Arab_AF")); - pr.digits = U"۰۱۲۳۴۵۶۷۸۹٫"; - pr.percent_sign = U"٪"; - pr.exp = U"اس"; - num_systems.push_back(pr); - } - - // Bengali numerals. - { - NumSystemData bn; - bn.lang.insert(StringName("as")); // Assamese - bn.lang.insert(StringName("as_IN")); - bn.lang.insert(StringName("bn")); // Bengali - bn.lang.insert(StringName("bn_BD")); - bn.lang.insert(StringName("bn_IN")); - bn.lang.insert(StringName("mni")); // Manipuri - bn.lang.insert(StringName("mni_IN")); - bn.lang.insert(StringName("mni_Beng")); - bn.lang.insert(StringName("mni_Beng_IN")); - bn.digits = U"০১২৩৪৫৬৭৮৯."; - bn.percent_sign = U"%"; - bn.exp = U"e"; - num_systems.push_back(bn); - } - - // Devanagari numerals. - { - NumSystemData mr; - mr.lang.insert(StringName("mr")); // Marathi - mr.lang.insert(StringName("mr_IN")); - mr.lang.insert(StringName("ne")); // Nepali - mr.lang.insert(StringName("ne_IN")); - mr.lang.insert(StringName("ne_NP")); - mr.lang.insert(StringName("sa")); // Sanskrit - mr.lang.insert(StringName("sa_IN")); - mr.digits = U"०१२३४५६७८९."; - mr.percent_sign = U"%"; - mr.exp = U"e"; - num_systems.push_back(mr); - } - - // Dzongkha numerals. - { - NumSystemData dz; - dz.lang.insert(StringName("dz")); // Dzongkha - dz.lang.insert(StringName("dz_BT")); - dz.digits = U"༠༡༢༣༤༥༦༧༨༩."; - dz.percent_sign = U"%"; - dz.exp = U"e"; - num_systems.push_back(dz); - } - - // Santali numerals. - { - NumSystemData sat; - sat.lang.insert(StringName("sat")); // Santali - sat.lang.insert(StringName("sat_IN")); - sat.lang.insert(StringName("sat_Olck")); - sat.lang.insert(StringName("sat_Olck_IN")); - sat.digits = U"᱐᱑᱒᱓᱔᱕᱖᱗᱘᱙."; - sat.percent_sign = U"%"; - sat.exp = U"e"; - num_systems.push_back(sat); - } - - // Burmese numerals. - { - NumSystemData my; - my.lang.insert(StringName("my")); // Burmese - my.lang.insert(StringName("my_MM")); - my.digits = U"၀၁၂၃၄၅၆၇၈၉."; - my.percent_sign = U"%"; - my.exp = U"e"; - num_systems.push_back(my); - } - - // Chakma numerals. - { - NumSystemData ccp; - ccp.lang.insert(StringName("ccp")); // Chakma - ccp.lang.insert(StringName("ccp_BD")); - ccp.lang.insert(StringName("ccp_IN")); - ccp.digits = U"𑄶𑄷𑄸𑄹𑄺𑄻𑄼𑄽𑄾𑄿."; - ccp.percent_sign = U"%"; - ccp.exp = U"e"; - num_systems.push_back(ccp); - } - - // Adlam numerals. - { - NumSystemData ff; - ff.lang.insert(StringName("ff")); // Fulah - ff.lang.insert(StringName("ff_Adlm_BF")); - ff.lang.insert(StringName("ff_Adlm_CM")); - ff.lang.insert(StringName("ff_Adlm_GH")); - ff.lang.insert(StringName("ff_Adlm_GM")); - ff.lang.insert(StringName("ff_Adlm_GN")); - ff.lang.insert(StringName("ff_Adlm_GW")); - ff.lang.insert(StringName("ff_Adlm_LR")); - ff.lang.insert(StringName("ff_Adlm_MR")); - ff.lang.insert(StringName("ff_Adlm_NE")); - ff.lang.insert(StringName("ff_Adlm_NG")); - ff.lang.insert(StringName("ff_Adlm_SL")); - ff.lang.insert(StringName("ff_Adlm_SN")); - ff.digits = U"𞥐𞥑𞥒𞥓𞥔𞥕𞥖𞥗𞥘𞥙."; - ff.percent_sign = U"%"; - ff.exp = U"e"; - num_systems.push_back(ff); - } -} - -String TextServerAdvanced::_format_number(const String &p_string, const String &p_language) const { - const StringName lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language; - - String res = p_string; - for (int i = 0; i < num_systems.size(); i++) { - if (num_systems[i].lang.has(lang)) { - if (num_systems[i].digits.is_empty()) { - return p_string; - } - res.replace("e", num_systems[i].exp); - res.replace("E", num_systems[i].exp); - char32_t *data = res.ptrw(); - for (int j = 0; j < res.length(); j++) { - if (data[j] >= 0x30 && data[j] <= 0x39) { - data[j] = num_systems[i].digits[data[j] - 0x30]; - } else if (data[j] == '.' || data[j] == ',') { - data[j] = num_systems[i].digits[10]; - } - } - break; - } - } - return res; -} - -String TextServerAdvanced::_parse_number(const String &p_string, const String &p_language) const { - const StringName lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language; - - String res = p_string; - for (int i = 0; i < num_systems.size(); i++) { - if (num_systems[i].lang.has(lang)) { - if (num_systems[i].digits.is_empty()) { - return p_string; - } - res.replace(num_systems[i].exp, "e"); - char32_t *data = res.ptrw(); - for (int j = 0; j < res.length(); j++) { - if (data[j] == num_systems[i].digits[10]) { - data[j] = '.'; - } else { - for (int k = 0; k < 10; k++) { - if (data[j] == num_systems[i].digits[k]) { - data[j] = 0x30 + k; - } - } - } - } - break; - } - } - return res; -} - -String TextServerAdvanced::_percent_sign(const String &p_language) const { - const StringName lang = (p_language.is_empty()) ? TranslationServer::get_singleton()->get_tool_locale() : p_language; - - for (int i = 0; i < num_systems.size(); i++) { - if (num_systems[i].lang.has(lang)) { - if (num_systems[i].percent_sign.is_empty()) { - return "%"; - } - return num_systems[i].percent_sign; - } - } - return "%"; -} - int64_t TextServerAdvanced::_is_confusable(const String &p_string, const PackedStringArray &p_dict) const { #ifndef ICU_STATIC_DATA if (!icu_data_loaded) { @@ -7425,7 +7183,6 @@ bool TextServerAdvanced::_is_valid_letter(uint64_t p_unicode) const { } TextServerAdvanced::TextServerAdvanced() { - _insert_num_systems_lang(); _insert_feature_sets(); _bmp_create_font_funcs(); } diff --git a/modules/text_server_adv/text_server_adv.h b/modules/text_server_adv/text_server_adv.h index fdebb8e4cd37..1544b2f0dda3 100644 --- a/modules/text_server_adv/text_server_adv.h +++ b/modules/text_server_adv/text_server_adv.h @@ -133,15 +133,6 @@ class TextServerAdvanced : public TextServerExtension { GDCLASS(TextServerAdvanced, TextServerExtension); _THREAD_SAFE_CLASS_ - struct NumSystemData { - HashSet lang; - String digits; - String percent_sign; - String exp; - }; - - Vector num_systems; - struct FeatureInfo { StringName name; Variant::Type vtype = Variant::INT; @@ -151,7 +142,6 @@ class TextServerAdvanced : public TextServerExtension { HashMap feature_sets; HashMap feature_sets_inv; - void _insert_num_systems_lang(); void _insert_feature_sets(); _FORCE_INLINE_ void _insert_feature(const StringName &p_name, int32_t p_tag, Variant::Type p_vtype = Variant::INT, bool p_hidden = false); @@ -977,10 +967,6 @@ class TextServerAdvanced : public TextServerExtension { MODBIND1RC(PackedInt32Array, shaped_text_get_character_breaks, const RID &); - MODBIND2RC(String, format_number, const String &, const String &); - MODBIND2RC(String, parse_number, const String &, const String &); - MODBIND1RC(String, percent_sign, const String &); - MODBIND3RC(PackedInt32Array, string_get_word_breaks, const String &, const String &, int64_t); MODBIND2RC(PackedInt32Array, string_get_character_breaks, const String &, const String &); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index 412eb8351525..dc8b936f992a 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -33,6 +33,7 @@ #include "core/os/keyboard.h" #include "core/string/string_builder.h" +#include "core/string/translation_server.h" #include "core/string/ustring.h" #include "scene/theme/theme_db.h" @@ -1447,7 +1448,11 @@ bool CodeEdit::is_line_numbers_zero_padded() const { void CodeEdit::_line_number_draw_callback(int p_line, int p_gutter, const Rect2 &p_region) { String fc = String::num(p_line + 1).lpad(line_number_digits, line_number_padding); if (is_localizing_numeral_system()) { - fc = TS->format_number(fc); + if ((Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) && !is_part_of_edited_scene()) { + fc = TranslationServer::get_singleton()->tool_format_number(fc); + } else { + fc = TranslationServer::get_singleton()->format_number(fc); + } } Ref tl; tl.instantiate(); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 0006204ae35d..21c2fc298f3d 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -771,7 +771,7 @@ void LineEdit::_notification(int p_what) { switch (p_what) { #ifdef TOOLS_ENABLED case NOTIFICATION_ENTER_TREE: { - if (Engine::get_singleton()->is_editor_hint() && !is_part_of_edited_scene()) { + if ((Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) && !is_part_of_edited_scene()) { set_caret_blink_enabled(EDITOR_GET("text_editor/appearance/caret/caret_blink")); set_caret_blink_interval(EDITOR_GET("text_editor/appearance/caret/caret_blink_interval")); diff --git a/scene/gui/progress_bar.cpp b/scene/gui/progress_bar.cpp index 90ce01e38377..31123009e14a 100644 --- a/scene/gui/progress_bar.cpp +++ b/scene/gui/progress_bar.cpp @@ -30,6 +30,7 @@ #include "progress_bar.h" +#include "core/string/translation_server.h" #include "scene/resources/text_line.h" #include "scene/theme/theme_db.h" @@ -159,7 +160,11 @@ void ProgressBar::_notification(int p_what) { String txt = itos(int(ratio * 100)); if (is_localizing_numeral_system()) { - txt = TS->format_number(txt) + TS->percent_sign(); + if ((Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) && !is_part_of_edited_scene()) { + txt = TranslationServer::get_singleton()->tool_format_number(txt) + TranslationServer::get_singleton()->get_tool_percent_sign(); + } else { + txt = TranslationServer::get_singleton()->format_number(txt) + TranslationServer::get_singleton()->get_percent_sign(); + } } else { txt += String("%"); } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index e9fe78e1629d..033225dbfbe6 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -36,6 +36,7 @@ #include "core/os/keyboard.h" #include "core/os/os.h" #include "core/string/translation.h" +#include "core/string/translation_server.h" #include "scene/gui/label.h" #include "scene/gui/rich_text_effect.h" #include "scene/resources/atlas_texture.h" @@ -243,7 +244,11 @@ String RichTextLabel::_get_prefix(Item *p_item, const Vector &p_list_index, if (p_list_items[i]->list_type == LIST_NUMBERS) { segment = itos(p_list_index[i]); if (is_localizing_numeral_system()) { - segment = TS->format_number(segment, _find_language(p_item)); + if ((Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) && !is_part_of_edited_scene()) { + segment = TranslationServer::get_singleton()->tool_format_number(segment); + } else { + segment = TranslationServer::get_singleton()->format_number(segment, _find_language(p_item)); + } } segments++; } else if (p_list_items[i]->list_type == LIST_LETTERS) { diff --git a/scene/gui/spin_box.cpp b/scene/gui/spin_box.cpp index 2c08d36e7e6f..2c54ad443502 100644 --- a/scene/gui/spin_box.cpp +++ b/scene/gui/spin_box.cpp @@ -32,6 +32,7 @@ #include "core/input/input.h" #include "core/math/expression.h" +#include "core/string/translation_server.h" #include "scene/theme/theme_db.h" Size2 SpinBox::get_minimum_size() const { @@ -43,7 +44,11 @@ Size2 SpinBox::get_minimum_size() const { void SpinBox::_update_text(bool p_keep_line_edit) { String value = String::num(get_value(), Math::range_step_decimals(get_step())); if (is_localizing_numeral_system()) { - value = TS->format_number(value); + if ((Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) && !is_part_of_edited_scene()) { + value = TranslationServer::get_singleton()->tool_format_number(value); + } else { + value = TranslationServer::get_singleton()->format_number(value); + } } if (!line_edit->has_focus()) { @@ -70,7 +75,11 @@ void SpinBox::_text_submitted(const String &p_string) { // Convert commas ',' to dots '.' for French/German etc. keyboard layouts. String text = p_string.replace(",", "."); text = text.replace(";", ","); - text = TS->parse_number(text); + if ((Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) && !is_part_of_edited_scene()) { + text = TranslationServer::get_singleton()->tool_parse_number(text); + } else { + text = TranslationServer::get_singleton()->parse_number(text); + } // Ignore the prefix and suffix in the expression. text = text.trim_prefix(prefix + " ").trim_suffix(" " + suffix); @@ -78,7 +87,11 @@ void SpinBox::_text_submitted(const String &p_string) { if (err != OK) { // If the expression failed try without converting commas to dots - they might have been for parameter separation. text = p_string; - text = TS->parse_number(text); + if ((Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) && !is_part_of_edited_scene()) { + text = TranslationServer::get_singleton()->tool_parse_number(text); + } else { + text = TranslationServer::get_singleton()->parse_number(text); + } text = text.trim_prefix(prefix + " ").trim_suffix(" " + suffix); err = expr->parse(text); diff --git a/servers/text/text_server_extension.cpp b/servers/text/text_server_extension.cpp index d387c8ff7ec7..7f5d17219994 100644 --- a/servers/text/text_server_extension.cpp +++ b/servers/text/text_server_extension.cpp @@ -327,10 +327,6 @@ void TextServerExtension::_bind_methods() { GDVIRTUAL_BIND(_shaped_text_prev_character_pos, "shaped", "pos"); GDVIRTUAL_BIND(_shaped_text_closest_character_pos, "shaped", "pos"); - GDVIRTUAL_BIND(_format_number, "number", "language"); - GDVIRTUAL_BIND(_parse_number, "number", "language"); - GDVIRTUAL_BIND(_percent_sign, "language"); - GDVIRTUAL_BIND(_strip_diacritics, "string"); GDVIRTUAL_BIND(_is_valid_identifier, "string"); GDVIRTUAL_BIND(_is_valid_letter, "unicode"); @@ -1470,28 +1466,6 @@ int64_t TextServerExtension::shaped_text_closest_character_pos(const RID &p_shap return TextServer::shaped_text_closest_character_pos(p_shaped, p_pos); } -String TextServerExtension::format_number(const String &p_string, const String &p_language) const { - String ret; - if (GDVIRTUAL_CALL(_format_number, p_string, p_language, ret)) { - return ret; - } - return p_string; -} - -String TextServerExtension::parse_number(const String &p_string, const String &p_language) const { - String ret; - if (GDVIRTUAL_CALL(_parse_number, p_string, p_language, ret)) { - return ret; - } - return p_string; -} - -String TextServerExtension::percent_sign(const String &p_language) const { - String ret = "%"; - GDVIRTUAL_CALL(_percent_sign, p_language, ret); - return ret; -} - bool TextServerExtension::is_valid_identifier(const String &p_string) const { bool ret; if (GDVIRTUAL_CALL(_is_valid_identifier, p_string, ret)) { diff --git a/servers/text/text_server_extension.h b/servers/text/text_server_extension.h index 52654c010ce4..ff015e523630 100644 --- a/servers/text/text_server_extension.h +++ b/servers/text/text_server_extension.h @@ -547,13 +547,6 @@ class TextServerExtension : public TextServer { GDVIRTUAL2RC(int64_t, _shaped_text_prev_character_pos, RID, int64_t); GDVIRTUAL2RC(int64_t, _shaped_text_closest_character_pos, RID, int64_t); - virtual String format_number(const String &p_string, const String &p_language = "") const override; - virtual String parse_number(const String &p_string, const String &p_language = "") const override; - virtual String percent_sign(const String &p_language = "") const override; - GDVIRTUAL2RC(String, _format_number, const String &, const String &); - GDVIRTUAL2RC(String, _parse_number, const String &, const String &); - GDVIRTUAL1RC(String, _percent_sign, const String &); - virtual String strip_diacritics(const String &p_string) const override; GDVIRTUAL1RC(String, _strip_diacritics, const String &); diff --git a/servers/text_server.cpp b/servers/text_server.cpp index f391c795142b..674f9f146570 100644 --- a/servers/text_server.cpp +++ b/servers/text_server.cpp @@ -31,6 +31,7 @@ #include "servers/text_server.h" #include "text_server.compat.inc" +#include "core/string/translation_server.h" #include "core/variant/typed_array.h" #include "servers/rendering_server.h" @@ -1933,6 +1934,18 @@ String TextServer::strip_diacritics(const String &p_string) const { return result; } +String TextServer::format_number(const String &p_string, const String &p_language) const { + return TranslationServer::get_singleton()->format_number(p_string, p_language); +} + +String TextServer::parse_number(const String &p_string, const String &p_language) const { + return TranslationServer::get_singleton()->parse_number(p_string, p_language); +} + +String TextServer::percent_sign(const String &p_language) const { + return TranslationServer::get_singleton()->get_percent_sign(p_language); +} + TypedArray TextServer::parse_structured_text(StructuredTextParser p_parser_type, const Array &p_args, const String &p_text) const { TypedArray ret; switch (p_parser_type) { diff --git a/servers/text_server.h b/servers/text_server.h index ba3fdaa35e9b..1447fce0024b 100644 --- a/servers/text_server.h +++ b/servers/text_server.h @@ -535,9 +535,9 @@ class TextServer : public RefCounted { virtual void shaped_text_draw_outline(const RID &p_shaped, const RID &p_canvas, const Vector2 &p_pos, double p_clip_l = -1.0, double p_clip_r = -1.0, int64_t p_outline_size = 1, const Color &p_color = Color(1, 1, 1)) const; // Number conversion. - virtual String format_number(const String &p_string, const String &p_language = "") const = 0; - virtual String parse_number(const String &p_string, const String &p_language = "") const = 0; - virtual String percent_sign(const String &p_language = "") const = 0; + virtual String format_number(const String &p_string, const String &p_language = "") const; + virtual String parse_number(const String &p_string, const String &p_language = "") const; + virtual String percent_sign(const String &p_language = "") const; // String functions. virtual PackedInt32Array string_get_word_breaks(const String &p_string, const String &p_language = "", int64_t p_chars_per_line = 0) const = 0;