summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHaoyu Qiu <timothyqiu32@gmail.com>2024-08-15 15:00:47 +0800
committerHaoyu Qiu <timothyqiu32@gmail.com>2024-08-15 15:00:47 +0800
commit7343dc3a5d9135d419c47c077e1f5e84025a9a02 (patch)
tree88a75a3444d12f9ef73b31f429617c86f0a8de89
parent33c30b9e63a58b860cb2f36957c5e25cee34a627 (diff)
downloadredot-engine-7343dc3a5d9135d419c47c077e1f5e84025a9a02.tar.gz
Split TranslationServer into its own file
-rw-r--r--core/io/resource_loader.cpp2
-rw-r--r--core/object/object.cpp2
-rw-r--r--core/register_core_types.cpp1
-rw-r--r--core/string/translation.compat.inc5
-rw-r--r--core/string/translation.cpp917
-rw-r--r--core/string/translation.h128
-rw-r--r--core/string/translation_server.compat.inc38
-rw-r--r--core/string/translation_server.cpp947
-rw-r--r--core/string/translation_server.h164
-rw-r--r--core/string/ustring.cpp2
-rw-r--r--editor/doc_tools.cpp2
-rw-r--r--editor/editor_locale_dialog.cpp1
-rw-r--r--editor/editor_locale_dialog.h1
-rw-r--r--editor/editor_node.cpp2
-rw-r--r--editor/editor_property_name_processor.cpp2
-rw-r--r--editor/editor_settings.cpp2
-rw-r--r--editor/editor_translation.cpp1
-rw-r--r--editor/import/dynamic_font_import_settings.cpp1
-rw-r--r--editor/import/resource_importer_csv_translation.cpp2
-rw-r--r--editor/localization_editor.cpp2
-rw-r--r--editor/plugins/font_config_plugin.cpp1
-rw-r--r--editor/project_manager/quick_settings_dialog.cpp2
-rw-r--r--main/main.cpp2
-rw-r--r--modules/text_server_adv/text_server_adv.cpp2
-rw-r--r--modules/text_server_fb/text_server_fb.cpp2
-rw-r--r--scene/gui/control.cpp2
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/main/window.cpp2
-rw-r--r--tests/core/string/test_translation.h1
-rw-r--r--tests/core/string/test_translation_server.h2
30 files changed, 1174 insertions, 1066 deletions
diff --git a/core/io/resource_loader.cpp b/core/io/resource_loader.cpp
index 928bb95de3..31539a7904 100644
--- a/core/io/resource_loader.cpp
+++ b/core/io/resource_loader.cpp
@@ -38,7 +38,7 @@
#include "core/os/os.h"
#include "core/os/safe_binary_mutex.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/variant/variant_parser.h"
#include "servers/rendering_server.h"
diff --git a/core/object/object.cpp b/core/object/object.cpp
index e4d1a8fc9a..ac23bf831f 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -38,7 +38,7 @@
#include "core/object/script_language.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/templates/local_vector.h"
#include "core/variant/typed_array.h"
diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp
index c0a86e9fb7..c866ff0415 100644
--- a/core/register_core_types.cpp
+++ b/core/register_core_types.cpp
@@ -79,6 +79,7 @@
#include "core/os/time.h"
#include "core/string/optimized_translation.h"
#include "core/string/translation.h"
+#include "core/string/translation_server.h"
static Ref<ResourceFormatSaverBinary> resource_saver_binary;
static Ref<ResourceFormatLoaderBinary> resource_loader_binary;
diff --git a/core/string/translation.compat.inc b/core/string/translation.compat.inc
index d792d4a6fc..68bd1831e4 100644
--- a/core/string/translation.compat.inc
+++ b/core/string/translation.compat.inc
@@ -38,9 +38,4 @@ void Translation::_bind_compatibility_methods() {
ClassDB::bind_compatibility_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
}
-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(""));
-}
-
#endif
diff --git a/core/string/translation.cpp b/core/string/translation.cpp
index 432016284a..33d4a1bcde 100644
--- a/core/string/translation.cpp
+++ b/core/string/translation.cpp
@@ -31,14 +31,9 @@
#include "translation.h"
#include "translation.compat.inc"
-#include "core/config/project_settings.h"
-#include "core/io/resource_loader.h"
#include "core/os/os.h"
-#include "core/string/locales.h"
-
-#ifdef TOOLS_ENABLED
-#include "main/main.h"
-#endif
+#include "core/os/thread.h"
+#include "core/string/translation_server.h"
Dictionary Translation::_get_messages() const {
Dictionary d;
@@ -173,911 +168,3 @@ void Translation::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
}
-
-///////////////////////////////////////////////
-
-struct _character_accent_pair {
- const char32_t character;
- const char32_t *accented_character;
-};
-
-static _character_accent_pair _character_to_accented[] = {
- { 'A', U"Å" },
- { 'B', U"ß" },
- { 'C', U"Ç" },
- { 'D', U"Ð" },
- { 'E', U"É" },
- { 'F', U"F́" },
- { 'G', U"Ĝ" },
- { 'H', U"Ĥ" },
- { 'I', U"Ĩ" },
- { 'J', U"Ĵ" },
- { 'K', U"ĸ" },
- { 'L', U"Ł" },
- { 'M', U"Ḿ" },
- { 'N', U"й" },
- { 'O', U"Ö" },
- { 'P', U"Ṕ" },
- { 'Q', U"Q́" },
- { 'R', U"Ř" },
- { 'S', U"Ŝ" },
- { 'T', U"Ŧ" },
- { 'U', U"Ũ" },
- { 'V', U"Ṽ" },
- { 'W', U"Ŵ" },
- { 'X', U"X́" },
- { 'Y', U"Ÿ" },
- { 'Z', U"Ž" },
- { 'a', U"á" },
- { 'b', U"ḅ" },
- { 'c', U"ć" },
- { 'd', U"d́" },
- { 'e', U"é" },
- { 'f', U"f́" },
- { 'g', U"ǵ" },
- { 'h', U"h̀" },
- { 'i', U"í" },
- { 'j', U"ǰ" },
- { 'k', U"ḱ" },
- { 'l', U"ł" },
- { 'm', U"m̀" },
- { 'n', U"ή" },
- { 'o', U"ô" },
- { 'p', U"ṕ" },
- { 'q', U"q́" },
- { 'r', U"ŕ" },
- { 's', U"š" },
- { 't', U"ŧ" },
- { 'u', U"ü" },
- { 'v', U"ṽ" },
- { 'w', U"ŵ" },
- { 'x', U"x́" },
- { 'y', U"ý" },
- { 'z', U"ź" },
-};
-
-Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
-
-HashMap<String, String> TranslationServer::language_map;
-HashMap<String, String> TranslationServer::script_map;
-HashMap<String, String> TranslationServer::locale_rename_map;
-HashMap<String, String> TranslationServer::country_name_map;
-HashMap<String, String> TranslationServer::variant_map;
-HashMap<String, String> TranslationServer::country_rename_map;
-
-void TranslationServer::init_locale_info() {
- // Init locale info.
- language_map.clear();
- int idx = 0;
- while (language_list[idx][0] != nullptr) {
- language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
- idx++;
- }
-
- // Init locale-script map.
- locale_script_info.clear();
- idx = 0;
- while (locale_scripts[idx][0] != nullptr) {
- LocaleScriptInfo info;
- info.name = locale_scripts[idx][0];
- info.script = locale_scripts[idx][1];
- info.default_country = locale_scripts[idx][2];
- Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
- for (int i = 0; i < supported_countries.size(); i++) {
- info.supported_countries.insert(supported_countries[i]);
- }
- 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 (!String(locale_renames[idx][1]).is_empty()) {
- locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
- }
- idx++;
- }
-
- // Init country names.
- country_name_map.clear();
- idx = 0;
- while (country_names[idx][0] != nullptr) {
- country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
- idx++;
- }
-
- // Init country renames.
- country_rename_map.clear();
- idx = 0;
- while (country_renames[idx][0] != nullptr) {
- if (!String(country_renames[idx][1]).is_empty()) {
- country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
- }
- idx++;
- }
-}
-
-String TranslationServer::standardize_locale(const String &p_locale) const {
- return _standardize_locale(p_locale, false);
-}
-
-String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
- // Replaces '-' with '_' for macOS style locales.
- String univ_locale = p_locale.replace("-", "_");
-
- // Extract locale elements.
- String lang_name, script_name, country_name, variant_name;
- Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
- lang_name = 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_name = 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_name = 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_name = locale_elements[2];
- } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) {
- variant_name = locale_elements[2].to_lower();
- }
- }
- if (locale_elements.size() >= 4) {
- if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) {
- variant_name = locale_elements[3].to_lower();
- }
- }
-
- // Try extract script and variant from the extra part.
- Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
- for (int i = 0; i < script_extra.size(); i++) {
- if (script_extra[i].to_lower() == "cyrillic") {
- script_name = "Cyrl";
- break;
- } else if (script_extra[i].to_lower() == "latin") {
- script_name = "Latn";
- break;
- } else if (script_extra[i].to_lower() == "devanagari") {
- script_name = "Deva";
- break;
- } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) {
- variant_name = script_extra[i].to_lower();
- }
- }
-
- // Handles known non-ISO language names used e.g. on Windows.
- if (locale_rename_map.has(lang_name)) {
- lang_name = locale_rename_map[lang_name];
- }
-
- // Handle country renames.
- if (country_rename_map.has(country_name)) {
- country_name = country_rename_map[country_name];
- }
-
- // Remove unsupported script codes.
- if (!script_map.has(script_name)) {
- script_name = "";
- }
-
- // Add script code base on language and country codes for some ambiguous cases.
- if (p_add_defaults) {
- if (script_name.is_empty()) {
- for (int i = 0; i < locale_script_info.size(); i++) {
- const LocaleScriptInfo &info = locale_script_info[i];
- if (info.name == lang_name) {
- if (country_name.is_empty() || info.supported_countries.has(country_name)) {
- script_name = info.script;
- break;
- }
- }
- }
- }
- if (!script_name.is_empty() && country_name.is_empty()) {
- // Add conntry code based on script for some ambiguous cases.
- for (int i = 0; i < locale_script_info.size(); i++) {
- const LocaleScriptInfo &info = locale_script_info[i];
- if (info.name == lang_name && info.script == script_name) {
- country_name = info.default_country;
- break;
- }
- }
- }
- }
-
- // Combine results.
- String out = lang_name;
- if (!script_name.is_empty()) {
- out = out + "_" + script_name;
- }
- if (!country_name.is_empty()) {
- out = out + "_" + country_name;
- }
- if (!variant_name.is_empty()) {
- out = out + "_" + variant_name;
- }
- return out;
-}
-
-int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
- String locale_a = _standardize_locale(p_locale_a, true);
- String locale_b = _standardize_locale(p_locale_b, true);
-
- if (locale_a == locale_b) {
- // Exact match.
- return 10;
- }
-
- Vector<String> locale_a_elements = locale_a.split("_");
- Vector<String> locale_b_elements = locale_b.split("_");
- if (locale_a_elements[0] == locale_b_elements[0]) {
- // Matching language, both locales have extra parts.
- // Return number of matching elements.
- int matching_elements = 1;
- for (int i = 1; i < locale_a_elements.size(); i++) {
- for (int j = 1; j < locale_b_elements.size(); j++) {
- if (locale_a_elements[i] == locale_b_elements[j]) {
- matching_elements++;
- }
- }
- }
- return matching_elements;
- } else {
- // No match.
- return 0;
- }
-}
-
-String TranslationServer::get_locale_name(const String &p_locale) const {
- String lang_name, script_name, country_name;
- Vector<String> locale_elements = standardize_locale(p_locale).split("_");
- lang_name = 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_name = 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_name = 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_name = locale_elements[2];
- }
- }
-
- String name = language_map[lang_name];
- if (!script_name.is_empty()) {
- name = name + " (" + script_map[script_name] + ")";
- }
- if (!country_name.is_empty()) {
- name = name + ", " + country_name_map[country_name];
- }
- return name;
-}
-
-Vector<String> TranslationServer::get_all_languages() const {
- Vector<String> languages;
-
- for (const KeyValue<String, String> &E : language_map) {
- languages.push_back(E.key);
- }
-
- return languages;
-}
-
-String TranslationServer::get_language_name(const String &p_language) const {
- return language_map[p_language];
-}
-
-Vector<String> TranslationServer::get_all_scripts() const {
- Vector<String> scripts;
-
- for (const KeyValue<String, String> &E : script_map) {
- scripts.push_back(E.key);
- }
-
- return scripts;
-}
-
-String TranslationServer::get_script_name(const String &p_script) const {
- return script_map[p_script];
-}
-
-Vector<String> TranslationServer::get_all_countries() const {
- Vector<String> countries;
-
- for (const KeyValue<String, String> &E : country_name_map) {
- 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 new_locale = standardize_locale(p_locale);
- if (locale == new_locale) {
- return;
- }
-
- locale = new_locale;
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-String TranslationServer::get_locale() const {
- return locale;
-}
-
-PackedStringArray TranslationServer::get_loaded_locales() const {
- PackedStringArray locales;
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), PackedStringArray());
- String l = t->get_locale();
-
- locales.push_back(l);
- }
-
- return locales;
-}
-
-void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
- translations.insert(p_translation);
-}
-
-void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
- translations.erase(p_translation);
-}
-
-Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
- Ref<Translation> res;
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), nullptr);
- String l = t->get_locale();
-
- int score = compare_locales(p_locale, l);
- if (score > 0 && score >= best_score) {
- res = t;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
- return res;
-}
-
-void TranslationServer::clear() {
- translations.clear();
-}
-
-StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
- // Match given message against the translation catalog for the project locale.
-
- if (!enabled) {
- return p_message;
- }
-
- StringName res = _get_message_from_translations(p_message, p_context, locale, false);
-
- if (!res && fallback.length() >= 2) {
- res = _get_message_from_translations(p_message, p_context, fallback, false);
- }
-
- if (!res) {
- return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
- }
-
- return pseudolocalization_enabled ? pseudolocalize(res) : res;
-}
-
-StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (!enabled) {
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
- }
-
- StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
-
- if (!res && fallback.length() >= 2) {
- res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
- }
-
- if (!res) {
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
- }
-
- return res;
-}
-
-StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
- StringName res;
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), p_message);
- String l = t->get_locale();
-
- int score = compare_locales(p_locale, l);
- if (score > 0 && score >= best_score) {
- StringName r;
- if (!plural) {
- r = t->get_message(p_message, p_context);
- } else {
- r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
- }
- if (!r) {
- continue;
- }
- res = r;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
-
- return res;
-}
-
-TranslationServer *TranslationServer::singleton = nullptr;
-
-bool TranslationServer::_load_translations(const String &p_from) {
- if (ProjectSettings::get_singleton()->has_setting(p_from)) {
- const Vector<String> &translation_names = GLOBAL_GET(p_from);
-
- int tcount = translation_names.size();
-
- if (tcount) {
- const String *r = translation_names.ptr();
-
- for (int i = 0; i < tcount; i++) {
- Ref<Translation> tr = ResourceLoader::load(r[i]);
- if (tr.is_valid()) {
- add_translation(tr);
- }
- }
- }
- return true;
- }
-
- return false;
-}
-
-void TranslationServer::setup() {
- String test = GLOBAL_DEF("internationalization/locale/test", "");
- test = test.strip_edges();
- if (!test.is_empty()) {
- set_locale(test);
- } else {
- set_locale(OS::get_singleton()->get_locale());
- }
-
- fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
- pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
- pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
- 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);
- expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
- pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
- pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
-
-#ifdef TOOLS_ENABLED
- ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
-#endif
-}
-
-void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
- tool_translation = p_translation;
-}
-
-Ref<Translation> TranslationServer::get_tool_translation() const {
- return tool_translation;
-}
-
-String TranslationServer::get_tool_locale() {
-#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()) {
- return tool_translation->get_locale();
- } else {
- return "en";
- }
- } else {
-#else
- {
-#endif
- // Look for best matching loaded translation.
- String best_locale = "en";
- int best_score = 0;
-
- for (const Ref<Translation> &E : translations) {
- const Ref<Translation> &t = E;
- ERR_FAIL_COND_V(t.is_null(), best_locale);
- String l = t->get_locale();
-
- int score = compare_locales(locale, l);
- if (score > 0 && score >= best_score) {
- best_locale = l;
- best_score = score;
- if (score == 10) {
- break; // Exact match, skip the rest.
- }
- }
- }
- return best_locale;
- }
-}
-
-StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
- if (tool_translation.is_valid()) {
- StringName r = tool_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (tool_translation.is_valid()) {
- StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
- property_translation = p_translation;
-}
-
-StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
- if (property_translation.is_valid()) {
- StringName r = property_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
- doc_translation = p_translation;
-}
-
-StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
- if (doc_translation.is_valid()) {
- StringName r = doc_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (doc_translation.is_valid()) {
- StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
- extractable_translation = p_translation;
-}
-
-StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
- if (extractable_translation.is_valid()) {
- StringName r = extractable_translation->get_message(p_message, p_context);
- if (r) {
- return r;
- }
- }
- return p_message;
-}
-
-StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
- if (extractable_translation.is_valid()) {
- StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
- if (r) {
- return r;
- }
- }
-
- if (p_n == 1) {
- return p_message;
- }
- return p_message_plural;
-}
-
-bool TranslationServer::is_pseudolocalization_enabled() const {
- return pseudolocalization_enabled;
-}
-
-void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
- pseudolocalization_enabled = p_enabled;
-
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-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");
- expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
- pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
- pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
- pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
-
- ResourceLoader::reload_translation_remaps();
-
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
- }
-}
-
-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);
- }
-
- if (pseudolocalization_double_vowels_enabled) {
- message = double_vowels(message);
- }
-
- if (pseudolocalization_accents_enabled) {
- message = replace_with_accented_string(message);
- }
-
- if (pseudolocalization_fake_bidi_enabled) {
- message = wrap_with_fakebidi_characters(message);
- }
-
- StringName res = add_padding(message, length);
- 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 + " !!!]";
- return res;
-}
-
-String TranslationServer::get_override_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += '*';
- }
- return res;
-}
-
-String TranslationServer::double_vowels(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- res += p_message[i];
- if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
- p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
- res += p_message[i];
- }
- }
- return res;
-};
-
-String TranslationServer::replace_with_accented_string(String &p_message) const {
- String res;
- for (int i = 0; i < p_message.length(); i++) {
- if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
- res += p_message[i];
- res += p_message[i + 1];
- i++;
- continue;
- }
- const char32_t *accented = get_accented_version(p_message[i]);
- if (accented) {
- res += accented;
- } else {
- res += p_message[i];
- }
- }
- return res;
-}
-
-String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
- String res;
- char32_t fakebidiprefix = U'\u202e';
- char32_t fakebidisuffix = U'\u202c';
- res += fakebidiprefix;
- // 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)) {
- res += fakebidisuffix;
- res += p_message[i];
- res += p_message[i + 1];
- res += fakebidiprefix;
- i++;
- } else {
- res += p_message[i];
- }
- }
- res += fakebidisuffix;
- 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;
-
- return prefix + p_message + suffix;
-}
-
-const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
- if (!is_ascii_alphabet_char(p_character)) {
- return nullptr;
- }
-
- for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
- if (_character_to_accented[i].character == p_character) {
- return _character_to_accented[i].accented_character;
- }
- }
-
- return nullptr;
-}
-
-bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
- return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
- (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
- p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
-}
-
-#ifdef TOOLS_ENABLED
-void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
- const String pf = p_function;
- if (p_idx == 0) {
- HashMap<String, String> *target_hash_map = nullptr;
- if (pf == "get_language_name") {
- target_hash_map = &language_map;
- } else if (pf == "get_script_name") {
- target_hash_map = &script_map;
- } else if (pf == "get_country_name") {
- target_hash_map = &country_name_map;
- }
-
- if (target_hash_map) {
- for (const KeyValue<String, String> &E : *target_hash_map) {
- r_options->push_back(E.key.quote());
- }
- }
- }
- Object::get_argument_options(p_function, p_idx, r_options);
-}
-#endif // TOOLS_ENABLED
-
-void TranslationServer::_bind_methods() {
- ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
- ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
- ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
-
- ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
- ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
-
- ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
- ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
-
- ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
- ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
-
- ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
- ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
-
- ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
-
- ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
- ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
-
- ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
- ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
- ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
-
- ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
-
- ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
-
- ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
- ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
- ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
- ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
- ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
-}
-
-void TranslationServer::load_translations() {
- _load_translations("internationalization/locale/translations"); //all
- _load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
-
- if (locale.substr(0, 2) != locale) {
- _load_translations("internationalization/locale/translations_" + locale);
- }
-}
-
-TranslationServer::TranslationServer() {
- singleton = this;
- init_locale_info();
-}
diff --git a/core/string/translation.h b/core/string/translation.h
index 0a7eacc45f..2c5baae8b7 100644
--- a/core/string/translation.h
+++ b/core/string/translation.h
@@ -74,132 +74,4 @@ public:
Translation() {}
};
-class TranslationServer : public Object {
- GDCLASS(TranslationServer, Object);
-
- String locale = "en";
- String fallback;
-
- HashSet<Ref<Translation>> translations;
- Ref<Translation> tool_translation;
- Ref<Translation> property_translation;
- Ref<Translation> doc_translation;
- Ref<Translation> extractable_translation;
-
- bool enabled = true;
-
- bool pseudolocalization_enabled = false;
- bool pseudolocalization_accents_enabled = false;
- bool pseudolocalization_double_vowels_enabled = false;
- bool pseudolocalization_fake_bidi_enabled = false;
- bool pseudolocalization_override_enabled = false;
- bool pseudolocalization_skip_placeholders_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;
- const char32_t *get_accented_version(char32_t p_character) const;
- bool is_placeholder(String &p_message, int p_index) const;
-
- static TranslationServer *singleton;
- bool _load_translations(const String &p_from);
- String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
-
- StringName _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 = 0) const;
-
- static void _bind_methods();
-
-#ifndef DISABLE_DEPRECATED
- static void _bind_compatibility_methods();
-#endif
-
- struct LocaleScriptInfo {
- String name;
- String script;
- String default_country;
- HashSet<String> supported_countries;
- };
- static Vector<LocaleScriptInfo> locale_script_info;
-
- static HashMap<String, String> language_map;
- static HashMap<String, String> script_map;
- static HashMap<String, String> locale_rename_map;
- static HashMap<String, String> country_name_map;
- static HashMap<String, String> country_rename_map;
- static HashMap<String, String> variant_map;
-
- void init_locale_info();
-
-public:
- _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
-
- void set_enabled(bool p_enabled) { enabled = p_enabled; }
- _FORCE_INLINE_ bool is_enabled() const { return enabled; }
-
- void set_locale(const String &p_locale);
- String get_locale() const;
- Ref<Translation> get_translation_object(const String &p_locale);
-
- Vector<String> get_all_languages() const;
- String get_language_name(const String &p_language) const;
-
- Vector<String> get_all_scripts() const;
- String get_script_name(const String &p_script) const;
-
- Vector<String> get_all_countries() const;
- String get_country_name(const String &p_country) const;
-
- String get_locale_name(const String &p_locale) const;
-
- PackedStringArray get_loaded_locales() const;
-
- void add_translation(const Ref<Translation> &p_translation);
- void remove_translation(const Ref<Translation> &p_translation);
-
- StringName translate(const StringName &p_message, const StringName &p_context = "") const;
- 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;
-
- bool is_pseudolocalization_enabled() const;
- void set_pseudolocalization_enabled(bool p_enabled);
- void reload_pseudolocalization();
-
- 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();
- void set_tool_translation(const Ref<Translation> &p_translation);
- Ref<Translation> get_tool_translation() const;
- StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
- void set_property_translation(const Ref<Translation> &p_translation);
- StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
- void set_doc_translation(const Ref<Translation> &p_translation);
- StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
- void set_extractable_translation(const Ref<Translation> &p_translation);
- StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
- StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
-
- void setup();
-
- void clear();
-
- void load_translations();
-
-#ifdef TOOLS_ENABLED
- virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
-#endif // TOOLS_ENABLED
-
- TranslationServer();
-};
-
#endif // TRANSLATION_H
diff --git a/core/string/translation_server.compat.inc b/core/string/translation_server.compat.inc
new file mode 100644
index 0000000000..9d1ee8b9df
--- /dev/null
+++ b/core/string/translation_server.compat.inc
@@ -0,0 +1,38 @@
+/**************************************************************************/
+/* translation_server.compat.inc */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 DISABLE_DEPRECATED
+
+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(""));
+}
+
+#endif
diff --git a/core/string/translation_server.cpp b/core/string/translation_server.cpp
new file mode 100644
index 0000000000..6e784881d0
--- /dev/null
+++ b/core/string/translation_server.cpp
@@ -0,0 +1,947 @@
+/**************************************************************************/
+/* translation_server.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "translation_server.h"
+#include "translation_server.compat.inc"
+
+#include "core/config/project_settings.h"
+#include "core/io/resource_loader.h"
+#include "core/os/os.h"
+#include "core/string/locales.h"
+
+#ifdef TOOLS_ENABLED
+#include "main/main.h"
+#endif
+
+struct _character_accent_pair {
+ const char32_t character;
+ const char32_t *accented_character;
+};
+
+static _character_accent_pair _character_to_accented[] = {
+ { 'A', U"Å" },
+ { 'B', U"ß" },
+ { 'C', U"Ç" },
+ { 'D', U"Ð" },
+ { 'E', U"É" },
+ { 'F', U"F́" },
+ { 'G', U"Ĝ" },
+ { 'H', U"Ĥ" },
+ { 'I', U"Ĩ" },
+ { 'J', U"Ĵ" },
+ { 'K', U"ĸ" },
+ { 'L', U"Ł" },
+ { 'M', U"Ḿ" },
+ { 'N', U"й" },
+ { 'O', U"Ö" },
+ { 'P', U"Ṕ" },
+ { 'Q', U"Q́" },
+ { 'R', U"Ř" },
+ { 'S', U"Ŝ" },
+ { 'T', U"Ŧ" },
+ { 'U', U"Ũ" },
+ { 'V', U"Ṽ" },
+ { 'W', U"Ŵ" },
+ { 'X', U"X́" },
+ { 'Y', U"Ÿ" },
+ { 'Z', U"Ž" },
+ { 'a', U"á" },
+ { 'b', U"ḅ" },
+ { 'c', U"ć" },
+ { 'd', U"d́" },
+ { 'e', U"é" },
+ { 'f', U"f́" },
+ { 'g', U"ǵ" },
+ { 'h', U"h̀" },
+ { 'i', U"í" },
+ { 'j', U"ǰ" },
+ { 'k', U"ḱ" },
+ { 'l', U"ł" },
+ { 'm', U"m̀" },
+ { 'n', U"ή" },
+ { 'o', U"ô" },
+ { 'p', U"ṕ" },
+ { 'q', U"q́" },
+ { 'r', U"ŕ" },
+ { 's', U"š" },
+ { 't', U"ŧ" },
+ { 'u', U"ü" },
+ { 'v', U"ṽ" },
+ { 'w', U"ŵ" },
+ { 'x', U"x́" },
+ { 'y', U"ý" },
+ { 'z', U"ź" },
+};
+
+Vector<TranslationServer::LocaleScriptInfo> TranslationServer::locale_script_info;
+
+HashMap<String, String> TranslationServer::language_map;
+HashMap<String, String> TranslationServer::script_map;
+HashMap<String, String> TranslationServer::locale_rename_map;
+HashMap<String, String> TranslationServer::country_name_map;
+HashMap<String, String> TranslationServer::variant_map;
+HashMap<String, String> TranslationServer::country_rename_map;
+
+void TranslationServer::init_locale_info() {
+ // Init locale info.
+ language_map.clear();
+ int idx = 0;
+ while (language_list[idx][0] != nullptr) {
+ language_map[language_list[idx][0]] = String::utf8(language_list[idx][1]);
+ idx++;
+ }
+
+ // Init locale-script map.
+ locale_script_info.clear();
+ idx = 0;
+ while (locale_scripts[idx][0] != nullptr) {
+ LocaleScriptInfo info;
+ info.name = locale_scripts[idx][0];
+ info.script = locale_scripts[idx][1];
+ info.default_country = locale_scripts[idx][2];
+ Vector<String> supported_countries = String(locale_scripts[idx][3]).split(",", false);
+ for (int i = 0; i < supported_countries.size(); i++) {
+ info.supported_countries.insert(supported_countries[i]);
+ }
+ 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 (!String(locale_renames[idx][1]).is_empty()) {
+ locale_rename_map[locale_renames[idx][0]] = locale_renames[idx][1];
+ }
+ idx++;
+ }
+
+ // Init country names.
+ country_name_map.clear();
+ idx = 0;
+ while (country_names[idx][0] != nullptr) {
+ country_name_map[String(country_names[idx][0])] = String::utf8(country_names[idx][1]);
+ idx++;
+ }
+
+ // Init country renames.
+ country_rename_map.clear();
+ idx = 0;
+ while (country_renames[idx][0] != nullptr) {
+ if (!String(country_renames[idx][1]).is_empty()) {
+ country_rename_map[country_renames[idx][0]] = country_renames[idx][1];
+ }
+ idx++;
+ }
+}
+
+String TranslationServer::standardize_locale(const String &p_locale) const {
+ return _standardize_locale(p_locale, false);
+}
+
+String TranslationServer::_standardize_locale(const String &p_locale, bool p_add_defaults) const {
+ // Replaces '-' with '_' for macOS style locales.
+ String univ_locale = p_locale.replace("-", "_");
+
+ // Extract locale elements.
+ String lang_name, script_name, country_name, variant_name;
+ Vector<String> locale_elements = univ_locale.get_slice("@", 0).split("_");
+ lang_name = 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_name = 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_name = 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_name = locale_elements[2];
+ } else if (variant_map.has(locale_elements[2].to_lower()) && variant_map[locale_elements[2].to_lower()] == lang_name) {
+ variant_name = locale_elements[2].to_lower();
+ }
+ }
+ if (locale_elements.size() >= 4) {
+ if (variant_map.has(locale_elements[3].to_lower()) && variant_map[locale_elements[3].to_lower()] == lang_name) {
+ variant_name = locale_elements[3].to_lower();
+ }
+ }
+
+ // Try extract script and variant from the extra part.
+ Vector<String> script_extra = univ_locale.get_slice("@", 1).split(";");
+ for (int i = 0; i < script_extra.size(); i++) {
+ if (script_extra[i].to_lower() == "cyrillic") {
+ script_name = "Cyrl";
+ break;
+ } else if (script_extra[i].to_lower() == "latin") {
+ script_name = "Latn";
+ break;
+ } else if (script_extra[i].to_lower() == "devanagari") {
+ script_name = "Deva";
+ break;
+ } else if (variant_map.has(script_extra[i].to_lower()) && variant_map[script_extra[i].to_lower()] == lang_name) {
+ variant_name = script_extra[i].to_lower();
+ }
+ }
+
+ // Handles known non-ISO language names used e.g. on Windows.
+ if (locale_rename_map.has(lang_name)) {
+ lang_name = locale_rename_map[lang_name];
+ }
+
+ // Handle country renames.
+ if (country_rename_map.has(country_name)) {
+ country_name = country_rename_map[country_name];
+ }
+
+ // Remove unsupported script codes.
+ if (!script_map.has(script_name)) {
+ script_name = "";
+ }
+
+ // Add script code base on language and country codes for some ambiguous cases.
+ if (p_add_defaults) {
+ if (script_name.is_empty()) {
+ for (int i = 0; i < locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = locale_script_info[i];
+ if (info.name == lang_name) {
+ if (country_name.is_empty() || info.supported_countries.has(country_name)) {
+ script_name = info.script;
+ break;
+ }
+ }
+ }
+ }
+ if (!script_name.is_empty() && country_name.is_empty()) {
+ // Add conntry code based on script for some ambiguous cases.
+ for (int i = 0; i < locale_script_info.size(); i++) {
+ const LocaleScriptInfo &info = locale_script_info[i];
+ if (info.name == lang_name && info.script == script_name) {
+ country_name = info.default_country;
+ break;
+ }
+ }
+ }
+ }
+
+ // Combine results.
+ String out = lang_name;
+ if (!script_name.is_empty()) {
+ out = out + "_" + script_name;
+ }
+ if (!country_name.is_empty()) {
+ out = out + "_" + country_name;
+ }
+ if (!variant_name.is_empty()) {
+ out = out + "_" + variant_name;
+ }
+ return out;
+}
+
+int TranslationServer::compare_locales(const String &p_locale_a, const String &p_locale_b) const {
+ String locale_a = _standardize_locale(p_locale_a, true);
+ String locale_b = _standardize_locale(p_locale_b, true);
+
+ if (locale_a == locale_b) {
+ // Exact match.
+ return 10;
+ }
+
+ Vector<String> locale_a_elements = locale_a.split("_");
+ Vector<String> locale_b_elements = locale_b.split("_");
+ if (locale_a_elements[0] == locale_b_elements[0]) {
+ // Matching language, both locales have extra parts.
+ // Return number of matching elements.
+ int matching_elements = 1;
+ for (int i = 1; i < locale_a_elements.size(); i++) {
+ for (int j = 1; j < locale_b_elements.size(); j++) {
+ if (locale_a_elements[i] == locale_b_elements[j]) {
+ matching_elements++;
+ }
+ }
+ }
+ return matching_elements;
+ } else {
+ // No match.
+ return 0;
+ }
+}
+
+String TranslationServer::get_locale_name(const String &p_locale) const {
+ String lang_name, script_name, country_name;
+ Vector<String> locale_elements = standardize_locale(p_locale).split("_");
+ lang_name = 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_name = 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_name = 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_name = locale_elements[2];
+ }
+ }
+
+ String name = language_map[lang_name];
+ if (!script_name.is_empty()) {
+ name = name + " (" + script_map[script_name] + ")";
+ }
+ if (!country_name.is_empty()) {
+ name = name + ", " + country_name_map[country_name];
+ }
+ return name;
+}
+
+Vector<String> TranslationServer::get_all_languages() const {
+ Vector<String> languages;
+
+ for (const KeyValue<String, String> &E : language_map) {
+ languages.push_back(E.key);
+ }
+
+ return languages;
+}
+
+String TranslationServer::get_language_name(const String &p_language) const {
+ return language_map[p_language];
+}
+
+Vector<String> TranslationServer::get_all_scripts() const {
+ Vector<String> scripts;
+
+ for (const KeyValue<String, String> &E : script_map) {
+ scripts.push_back(E.key);
+ }
+
+ return scripts;
+}
+
+String TranslationServer::get_script_name(const String &p_script) const {
+ return script_map[p_script];
+}
+
+Vector<String> TranslationServer::get_all_countries() const {
+ Vector<String> countries;
+
+ for (const KeyValue<String, String> &E : country_name_map) {
+ 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 new_locale = standardize_locale(p_locale);
+ if (locale == new_locale) {
+ return;
+ }
+
+ locale = new_locale;
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+String TranslationServer::get_locale() const {
+ return locale;
+}
+
+PackedStringArray TranslationServer::get_loaded_locales() const {
+ PackedStringArray locales;
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), PackedStringArray());
+ String l = t->get_locale();
+
+ locales.push_back(l);
+ }
+
+ return locales;
+}
+
+void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
+ translations.insert(p_translation);
+}
+
+void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
+ translations.erase(p_translation);
+}
+
+Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
+ Ref<Translation> res;
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), nullptr);
+ String l = t->get_locale();
+
+ int score = compare_locales(p_locale, l);
+ if (score > 0 && score >= best_score) {
+ res = t;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+ return res;
+}
+
+void TranslationServer::clear() {
+ translations.clear();
+}
+
+StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
+ // Match given message against the translation catalog for the project locale.
+
+ if (!enabled) {
+ return p_message;
+ }
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, false);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, false);
+ }
+
+ if (!res) {
+ return pseudolocalization_enabled ? pseudolocalize(p_message) : p_message;
+ }
+
+ return pseudolocalization_enabled ? pseudolocalize(res) : res;
+}
+
+StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (!enabled) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
+
+ if (!res && fallback.length() >= 2) {
+ res = _get_message_from_translations(p_message, p_context, fallback, true, p_message_plural, p_n);
+ }
+
+ if (!res) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ return res;
+}
+
+StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, bool plural, const String &p_message_plural, int p_n) const {
+ StringName res;
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), p_message);
+ String l = t->get_locale();
+
+ int score = compare_locales(p_locale, l);
+ if (score > 0 && score >= best_score) {
+ StringName r;
+ if (!plural) {
+ r = t->get_message(p_message, p_context);
+ } else {
+ r = t->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ }
+ if (!r) {
+ continue;
+ }
+ res = r;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+
+ return res;
+}
+
+TranslationServer *TranslationServer::singleton = nullptr;
+
+bool TranslationServer::_load_translations(const String &p_from) {
+ if (ProjectSettings::get_singleton()->has_setting(p_from)) {
+ const Vector<String> &translation_names = GLOBAL_GET(p_from);
+
+ int tcount = translation_names.size();
+
+ if (tcount) {
+ const String *r = translation_names.ptr();
+
+ for (int i = 0; i < tcount; i++) {
+ Ref<Translation> tr = ResourceLoader::load(r[i]);
+ if (tr.is_valid()) {
+ add_translation(tr);
+ }
+ }
+ }
+ return true;
+ }
+
+ return false;
+}
+
+void TranslationServer::setup() {
+ String test = GLOBAL_DEF("internationalization/locale/test", "");
+ test = test.strip_edges();
+ if (!test.is_empty()) {
+ set_locale(test);
+ } else {
+ set_locale(OS::get_singleton()->get_locale());
+ }
+
+ fallback = GLOBAL_DEF("internationalization/locale/fallback", "en");
+ pseudolocalization_enabled = GLOBAL_DEF("internationalization/pseudolocalization/use_pseudolocalization", false);
+ pseudolocalization_accents_enabled = GLOBAL_DEF("internationalization/pseudolocalization/replace_with_accents", true);
+ 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);
+ expansion_ratio = GLOBAL_DEF("internationalization/pseudolocalization/expansion_ratio", 0.0);
+ pseudolocalization_prefix = GLOBAL_DEF("internationalization/pseudolocalization/prefix", "[");
+ pseudolocalization_suffix = GLOBAL_DEF("internationalization/pseudolocalization/suffix", "]");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_DEF("internationalization/pseudolocalization/skip_placeholders", true);
+
+#ifdef TOOLS_ENABLED
+ ProjectSettings::get_singleton()->set_custom_property_info(PropertyInfo(Variant::STRING, "internationalization/locale/fallback", PROPERTY_HINT_LOCALE_ID, ""));
+#endif
+}
+
+void TranslationServer::set_tool_translation(const Ref<Translation> &p_translation) {
+ tool_translation = p_translation;
+}
+
+Ref<Translation> TranslationServer::get_tool_translation() const {
+ return tool_translation;
+}
+
+String TranslationServer::get_tool_locale() {
+#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()) {
+ return tool_translation->get_locale();
+ } else {
+ return "en";
+ }
+ } else {
+#else
+ {
+#endif
+ // Look for best matching loaded translation.
+ String best_locale = "en";
+ int best_score = 0;
+
+ for (const Ref<Translation> &E : translations) {
+ const Ref<Translation> &t = E;
+ ERR_FAIL_COND_V(t.is_null(), best_locale);
+ String l = t->get_locale();
+
+ int score = compare_locales(locale, l);
+ if (score > 0 && score >= best_score) {
+ best_locale = l;
+ best_score = score;
+ if (score == 10) {
+ break; // Exact match, skip the rest.
+ }
+ }
+ }
+ return best_locale;
+ }
+}
+
+StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+void TranslationServer::set_property_translation(const Ref<Translation> &p_translation) {
+ property_translation = p_translation;
+}
+
+StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
+ if (property_translation.is_valid()) {
+ StringName r = property_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
+ doc_translation = p_translation;
+}
+
+StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+void TranslationServer::set_extractable_translation(const Ref<Translation> &p_translation) {
+ extractable_translation = p_translation;
+}
+
+StringName TranslationServer::extractable_translate(const StringName &p_message, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_message(p_message, p_context);
+ if (r) {
+ return r;
+ }
+ }
+ return p_message;
+}
+
+StringName TranslationServer::extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (extractable_translation.is_valid()) {
+ StringName r = extractable_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
+bool TranslationServer::is_pseudolocalization_enabled() const {
+ return pseudolocalization_enabled;
+}
+
+void TranslationServer::set_pseudolocalization_enabled(bool p_enabled) {
+ pseudolocalization_enabled = p_enabled;
+
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+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");
+ expansion_ratio = GLOBAL_GET("internationalization/pseudolocalization/expansion_ratio");
+ pseudolocalization_prefix = GLOBAL_GET("internationalization/pseudolocalization/prefix");
+ pseudolocalization_suffix = GLOBAL_GET("internationalization/pseudolocalization/suffix");
+ pseudolocalization_skip_placeholders_enabled = GLOBAL_GET("internationalization/pseudolocalization/skip_placeholders");
+
+ ResourceLoader::reload_translation_remaps();
+
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_TRANSLATION_CHANGED);
+ }
+}
+
+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);
+ }
+
+ if (pseudolocalization_double_vowels_enabled) {
+ message = double_vowels(message);
+ }
+
+ if (pseudolocalization_accents_enabled) {
+ message = replace_with_accented_string(message);
+ }
+
+ if (pseudolocalization_fake_bidi_enabled) {
+ message = wrap_with_fakebidi_characters(message);
+ }
+
+ StringName res = add_padding(message, length);
+ 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 + " !!!]";
+ return res;
+}
+
+String TranslationServer::get_override_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += '*';
+ }
+ return res;
+}
+
+String TranslationServer::double_vowels(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ res += p_message[i];
+ if (p_message[i] == 'a' || p_message[i] == 'e' || p_message[i] == 'i' || p_message[i] == 'o' || p_message[i] == 'u' ||
+ p_message[i] == 'A' || p_message[i] == 'E' || p_message[i] == 'I' || p_message[i] == 'O' || p_message[i] == 'U') {
+ res += p_message[i];
+ }
+ }
+ return res;
+};
+
+String TranslationServer::replace_with_accented_string(String &p_message) const {
+ String res;
+ for (int i = 0; i < p_message.length(); i++) {
+ if (pseudolocalization_skip_placeholders_enabled && is_placeholder(p_message, i)) {
+ res += p_message[i];
+ res += p_message[i + 1];
+ i++;
+ continue;
+ }
+ const char32_t *accented = get_accented_version(p_message[i]);
+ if (accented) {
+ res += accented;
+ } else {
+ res += p_message[i];
+ }
+ }
+ return res;
+}
+
+String TranslationServer::wrap_with_fakebidi_characters(String &p_message) const {
+ String res;
+ char32_t fakebidiprefix = U'\u202e';
+ char32_t fakebidisuffix = U'\u202c';
+ res += fakebidiprefix;
+ // 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)) {
+ res += fakebidisuffix;
+ res += p_message[i];
+ res += p_message[i + 1];
+ res += fakebidiprefix;
+ i++;
+ } else {
+ res += p_message[i];
+ }
+ }
+ res += fakebidisuffix;
+ 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;
+
+ return prefix + p_message + suffix;
+}
+
+const char32_t *TranslationServer::get_accented_version(char32_t p_character) const {
+ if (!is_ascii_alphabet_char(p_character)) {
+ return nullptr;
+ }
+
+ for (unsigned int i = 0; i < sizeof(_character_to_accented) / sizeof(_character_to_accented[0]); i++) {
+ if (_character_to_accented[i].character == p_character) {
+ return _character_to_accented[i].accented_character;
+ }
+ }
+
+ return nullptr;
+}
+
+bool TranslationServer::is_placeholder(String &p_message, int p_index) const {
+ return p_index < p_message.length() - 1 && p_message[p_index] == '%' &&
+ (p_message[p_index + 1] == 's' || p_message[p_index + 1] == 'c' || p_message[p_index + 1] == 'd' ||
+ p_message[p_index + 1] == 'o' || p_message[p_index + 1] == 'x' || p_message[p_index + 1] == 'X' || p_message[p_index + 1] == 'f');
+}
+
+#ifdef TOOLS_ENABLED
+void TranslationServer::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
+ const String pf = p_function;
+ if (p_idx == 0) {
+ HashMap<String, String> *target_hash_map = nullptr;
+ if (pf == "get_language_name") {
+ target_hash_map = &language_map;
+ } else if (pf == "get_script_name") {
+ target_hash_map = &script_map;
+ } else if (pf == "get_country_name") {
+ target_hash_map = &country_name_map;
+ }
+
+ if (target_hash_map) {
+ for (const KeyValue<String, String> &E : *target_hash_map) {
+ r_options->push_back(E.key.quote());
+ }
+ }
+ }
+ Object::get_argument_options(p_function, p_idx, r_options);
+}
+#endif // TOOLS_ENABLED
+
+void TranslationServer::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
+ ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
+ ClassDB::bind_method(D_METHOD("get_tool_locale"), &TranslationServer::get_tool_locale);
+
+ ClassDB::bind_method(D_METHOD("compare_locales", "locale_a", "locale_b"), &TranslationServer::compare_locales);
+ ClassDB::bind_method(D_METHOD("standardize_locale", "locale"), &TranslationServer::standardize_locale);
+
+ ClassDB::bind_method(D_METHOD("get_all_languages"), &TranslationServer::get_all_languages);
+ ClassDB::bind_method(D_METHOD("get_language_name", "language"), &TranslationServer::get_language_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_scripts"), &TranslationServer::get_all_scripts);
+ ClassDB::bind_method(D_METHOD("get_script_name", "script"), &TranslationServer::get_script_name);
+
+ ClassDB::bind_method(D_METHOD("get_all_countries"), &TranslationServer::get_all_countries);
+ ClassDB::bind_method(D_METHOD("get_country_name", "country"), &TranslationServer::get_country_name);
+
+ ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
+
+ ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(StringName()));
+ ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(StringName()));
+
+ ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
+ ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
+ ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
+
+ ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
+
+ ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
+
+ ClassDB::bind_method(D_METHOD("is_pseudolocalization_enabled"), &TranslationServer::is_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("set_pseudolocalization_enabled", "enabled"), &TranslationServer::set_pseudolocalization_enabled);
+ ClassDB::bind_method(D_METHOD("reload_pseudolocalization"), &TranslationServer::reload_pseudolocalization);
+ ClassDB::bind_method(D_METHOD("pseudolocalize", "message"), &TranslationServer::pseudolocalize);
+ ADD_PROPERTY(PropertyInfo(Variant::Type::BOOL, "pseudolocalization_enabled"), "set_pseudolocalization_enabled", "is_pseudolocalization_enabled");
+}
+
+void TranslationServer::load_translations() {
+ _load_translations("internationalization/locale/translations"); //all
+ _load_translations("internationalization/locale/translations_" + locale.substr(0, 2));
+
+ if (locale.substr(0, 2) != locale) {
+ _load_translations("internationalization/locale/translations_" + locale);
+ }
+}
+
+TranslationServer::TranslationServer() {
+ singleton = this;
+ init_locale_info();
+}
diff --git a/core/string/translation_server.h b/core/string/translation_server.h
new file mode 100644
index 0000000000..ebe81d9712
--- /dev/null
+++ b/core/string/translation_server.h
@@ -0,0 +1,164 @@
+/**************************************************************************/
+/* translation_server.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* 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 TRANSLATION_SERVER_H
+#define TRANSLATION_SERVER_H
+
+#include "core/string/translation.h"
+
+class TranslationServer : public Object {
+ GDCLASS(TranslationServer, Object);
+
+ String locale = "en";
+ String fallback;
+
+ HashSet<Ref<Translation>> translations;
+ Ref<Translation> tool_translation;
+ Ref<Translation> property_translation;
+ Ref<Translation> doc_translation;
+ Ref<Translation> extractable_translation;
+
+ bool enabled = true;
+
+ bool pseudolocalization_enabled = false;
+ bool pseudolocalization_accents_enabled = false;
+ bool pseudolocalization_double_vowels_enabled = false;
+ bool pseudolocalization_fake_bidi_enabled = false;
+ bool pseudolocalization_override_enabled = false;
+ bool pseudolocalization_skip_placeholders_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;
+ const char32_t *get_accented_version(char32_t p_character) const;
+ bool is_placeholder(String &p_message, int p_index) const;
+
+ static TranslationServer *singleton;
+ bool _load_translations(const String &p_from);
+ String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
+
+ StringName _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 = 0) const;
+
+ static void _bind_methods();
+
+#ifndef DISABLE_DEPRECATED
+ static void _bind_compatibility_methods();
+#endif
+
+ struct LocaleScriptInfo {
+ String name;
+ String script;
+ String default_country;
+ HashSet<String> supported_countries;
+ };
+ static Vector<LocaleScriptInfo> locale_script_info;
+
+ static HashMap<String, String> language_map;
+ static HashMap<String, String> script_map;
+ static HashMap<String, String> locale_rename_map;
+ static HashMap<String, String> country_name_map;
+ static HashMap<String, String> country_rename_map;
+ static HashMap<String, String> variant_map;
+
+ void init_locale_info();
+
+public:
+ _FORCE_INLINE_ static TranslationServer *get_singleton() { return singleton; }
+
+ void set_enabled(bool p_enabled) { enabled = p_enabled; }
+ _FORCE_INLINE_ bool is_enabled() const { return enabled; }
+
+ void set_locale(const String &p_locale);
+ String get_locale() const;
+ Ref<Translation> get_translation_object(const String &p_locale);
+
+ Vector<String> get_all_languages() const;
+ String get_language_name(const String &p_language) const;
+
+ Vector<String> get_all_scripts() const;
+ String get_script_name(const String &p_script) const;
+
+ Vector<String> get_all_countries() const;
+ String get_country_name(const String &p_country) const;
+
+ String get_locale_name(const String &p_locale) const;
+
+ PackedStringArray get_loaded_locales() const;
+
+ void add_translation(const Ref<Translation> &p_translation);
+ void remove_translation(const Ref<Translation> &p_translation);
+
+ StringName translate(const StringName &p_message, const StringName &p_context = "") const;
+ 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;
+
+ bool is_pseudolocalization_enabled() const;
+ void set_pseudolocalization_enabled(bool p_enabled);
+ void reload_pseudolocalization();
+
+ 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();
+ void set_tool_translation(const Ref<Translation> &p_translation);
+ Ref<Translation> get_tool_translation() const;
+ StringName tool_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_property_translation(const Ref<Translation> &p_translation);
+ StringName property_translate(const StringName &p_message, const StringName &p_context = "") const;
+ void set_doc_translation(const Ref<Translation> &p_translation);
+ StringName doc_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+ void set_extractable_translation(const Ref<Translation> &p_translation);
+ StringName extractable_translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
+
+ void setup();
+
+ void clear();
+
+ void load_translations();
+
+#ifdef TOOLS_ENABLED
+ virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const override;
+#endif // TOOLS_ENABLED
+
+ TranslationServer();
+};
+
+#endif // TRANSLATION_SERVER_H
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 3d37e17ef8..0569ffe3fd 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -36,7 +36,7 @@
#include "core/os/memory.h"
#include "core/string/print_string.h"
#include "core/string/string_name.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/string/ucaps.h"
#include "core/variant/variant.h"
#include "core/version_generated.gen.h"
diff --git a/editor/doc_tools.cpp b/editor/doc_tools.cpp
index 331dacf6ad..bf5b717c19 100644
--- a/editor/doc_tools.cpp
+++ b/editor/doc_tools.cpp
@@ -38,7 +38,7 @@
#include "core/io/marshalls.h"
#include "core/io/resource_importer.h"
#include "core/object/script_language.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "editor/editor_settings.h"
#include "editor/export/editor_export.h"
#include "scene/resources/theme.h"
diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp
index f8fd05bf1e..83f1c70c69 100644
--- a/editor/editor_locale_dialog.cpp
+++ b/editor/editor_locale_dialog.cpp
@@ -31,6 +31,7 @@
#include "editor_locale_dialog.h"
#include "core/config/project_settings.h"
+#include "core/string/translation_server.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/check_button.h"
diff --git a/editor/editor_locale_dialog.h b/editor/editor_locale_dialog.h
index 467861c36b..bc75e1df59 100644
--- a/editor/editor_locale_dialog.h
+++ b/editor/editor_locale_dialog.h
@@ -31,7 +31,6 @@
#ifndef EDITOR_LOCALE_DIALOG_H
#define EDITOR_LOCALE_DIALOG_H
-#include "core/string/translation.h"
#include "scene/gui/dialogs.h"
class Button;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f24fa344ae..d1dffba2ab 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -42,7 +42,7 @@
#include "core/os/os.h"
#include "core/os/time.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/version.h"
#include "editor/editor_string_names.h"
#include "main/main.h"
diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp
index f23cab676c..7b45ba6a9f 100644
--- a/editor/editor_property_name_processor.cpp
+++ b/editor/editor_property_name_processor.cpp
@@ -30,7 +30,7 @@
#include "editor_property_name_processor.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "editor_settings.h"
EditorPropertyNameProcessor *EditorPropertyNameProcessor::singleton = nullptr;
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 0956d12236..59e4f19066 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -43,7 +43,7 @@
#include "core/object/class_db.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/version.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
diff --git a/editor/editor_translation.cpp b/editor/editor_translation.cpp
index 77154ec344..4654d41082 100644
--- a/editor/editor_translation.cpp
+++ b/editor/editor_translation.cpp
@@ -33,6 +33,7 @@
#include "core/io/compression.h"
#include "core/io/file_access_memory.h"
#include "core/io/translation_loader_po.h"
+#include "core/string/translation_server.h"
#include "editor/doc_translations.gen.h"
#include "editor/editor_translations.gen.h"
#include "editor/extractable_translations.gen.h"
diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp
index 00ce3d6a7a..590e3a9ede 100644
--- a/editor/import/dynamic_font_import_settings.cpp
+++ b/editor/import/dynamic_font_import_settings.cpp
@@ -31,6 +31,7 @@
#include "dynamic_font_import_settings.h"
#include "core/config/project_settings.h"
+#include "core/string/translation_server.h"
#include "editor/editor_file_system.h"
#include "editor/editor_inspector.h"
#include "editor/editor_locale_dialog.h"
diff --git a/editor/import/resource_importer_csv_translation.cpp b/editor/import/resource_importer_csv_translation.cpp
index d2705ac98a..c181011402 100644
--- a/editor/import/resource_importer_csv_translation.cpp
+++ b/editor/import/resource_importer_csv_translation.cpp
@@ -33,7 +33,7 @@
#include "core/io/file_access.h"
#include "core/io/resource_saver.h"
#include "core/string/optimized_translation.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
String ResourceImporterCSVTranslation::get_importer_name() const {
return "csv_translation";
diff --git a/editor/localization_editor.cpp b/editor/localization_editor.cpp
index 11d1b3e089..6bc3a27a95 100644
--- a/editor/localization_editor.cpp
+++ b/editor/localization_editor.cpp
@@ -31,7 +31,7 @@
#include "localization_editor.h"
#include "core/config/project_settings.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "editor/editor_translation_parser.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/filesystem_dock.h"
diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp
index e6ce63fe36..a698ab739f 100644
--- a/editor/plugins/font_config_plugin.cpp
+++ b/editor/plugins/font_config_plugin.cpp
@@ -30,6 +30,7 @@
#include "font_config_plugin.h"
+#include "core/string/translation_server.h"
#include "editor/editor_settings.h"
#include "editor/import/dynamic_font_import_settings.h"
#include "editor/themes/editor_scale.h"
diff --git a/editor/project_manager/quick_settings_dialog.cpp b/editor/project_manager/quick_settings_dialog.cpp
index a98d9073b0..4502b42fe6 100644
--- a/editor/project_manager/quick_settings_dialog.cpp
+++ b/editor/project_manager/quick_settings_dialog.cpp
@@ -31,7 +31,7 @@
#include "quick_settings_dialog.h"
#include "core/config/project_settings.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
diff --git a/main/main.cpp b/main/main.cpp
index e42469b51b..01ec96c052 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -49,7 +49,7 @@
#include "core/os/os.h"
#include "core/os/time.h"
#include "core/register_core_types.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/version.h"
#include "drivers/register_driver_types.h"
#include "main/app_icon.gen.h"
diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp
index 499ddb703b..4e34b0cddc 100644
--- a/modules/text_server_adv/text_server_adv.cpp
+++ b/modules/text_server_adv/text_server_adv.cpp
@@ -51,7 +51,7 @@ using namespace godot;
#include "core/error/error_macros.h"
#include "core/object/worker_thread_pool.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "scene/resources/image_texture.h"
#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp
index b45c004011..e89546a77e 100644
--- a/modules/text_server_fb/text_server_fb.cpp
+++ b/modules/text_server_fb/text_server_fb.cpp
@@ -52,7 +52,7 @@ using namespace godot;
#include "core/config/project_settings.h"
#include "core/error/error_macros.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "modules/modules_enabled.gen.h" // For freetype, msdfgen, svg.
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index d169e82e5d..2d425490d1 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -37,7 +37,7 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/string/print_string.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "scene/gui/label.h"
#include "scene/gui/panel.h"
#include "scene/main/canvas_layer.h"
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index ac6ebd5cc1..5cdb0c3d44 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -38,7 +38,7 @@
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "core/string/string_builder.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "scene/gui/label.h"
#include "scene/main/window.h"
#include "scene/theme/theme_db.h"
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 23843938a4..c2704a3848 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -34,7 +34,7 @@
#include "core/config/project_settings.h"
#include "core/debugger/engine_debugger.h"
#include "core/input/shortcut.h"
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "core/variant/variant_parser.h"
#include "scene/gui/control.h"
#include "scene/theme/theme_db.h"
diff --git a/tests/core/string/test_translation.h b/tests/core/string/test_translation.h
index acdd851b29..7c389191e3 100644
--- a/tests/core/string/test_translation.h
+++ b/tests/core/string/test_translation.h
@@ -34,6 +34,7 @@
#include "core/string/optimized_translation.h"
#include "core/string/translation.h"
#include "core/string/translation_po.h"
+#include "core/string/translation_server.h"
#ifdef TOOLS_ENABLED
#include "editor/import/resource_importer_csv_translation.h"
diff --git a/tests/core/string/test_translation_server.h b/tests/core/string/test_translation_server.h
index 2c20574309..ac1599f2e8 100644
--- a/tests/core/string/test_translation_server.h
+++ b/tests/core/string/test_translation_server.h
@@ -31,7 +31,7 @@
#ifndef TEST_TRANSLATION_SERVER_H
#define TEST_TRANSLATION_SERVER_H
-#include "core/string/translation.h"
+#include "core/string/translation_server.h"
#include "tests/test_macros.h"