1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-02 16:48:55 +00:00

Add methods for querying loaded Translation instances

This commit is contained in:
Haoyu Qiu
2025-11-09 16:17:04 +08:00
parent b79fe2e020
commit ec860ffe4a
9 changed files with 257 additions and 82 deletions

View File

@@ -32,6 +32,7 @@
#include "core/string/translation.h"
#include "core/string/translation_server.h"
#include "core/variant/typed_array.h"
struct _character_accent_pair {
const char32_t character;
@@ -252,27 +253,7 @@ PackedStringArray TranslationDomain::get_loaded_locales() const {
return locales;
}
bool TranslationDomain::has_translation_for_locale(const String &p_locale) const {
for (const Ref<Translation> &E : translations) {
if (E->get_locale() == p_locale) {
return true;
}
}
return false;
}
// Translation objects that could potentially be used for the given locale.
HashSet<Ref<Translation>> TranslationDomain::get_potential_translations(const String &p_locale) const {
HashSet<Ref<Translation>> res;
for (const Ref<Translation> &E : translations) {
if (TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()) > 0) {
res.insert(E);
}
}
return res;
}
#ifndef DISABLE_DEPRECATED
Ref<Translation> TranslationDomain::get_translation_object(const String &p_locale) const {
Ref<Translation> res;
int best_score = 0;
@@ -289,6 +270,7 @@ Ref<Translation> TranslationDomain::get_translation_object(const String &p_local
}
return res;
}
#endif
void TranslationDomain::add_translation(const Ref<Translation> &p_translation) {
ERR_FAIL_COND_MSG(p_translation.is_null(), "Invalid translation provided.");
@@ -303,6 +285,69 @@ void TranslationDomain::clear() {
translations.clear();
}
const HashSet<Ref<Translation>> TranslationDomain::get_translations() const {
return translations;
}
HashSet<Ref<Translation>> TranslationDomain::find_translations(const String &p_locale, bool p_exact) const {
HashSet<Ref<Translation>> res;
if (p_exact) {
for (const Ref<Translation> &E : translations) {
if (E->get_locale() == p_locale) {
res.insert(E);
}
}
} else {
for (const Ref<Translation> &E : translations) {
if (TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()) > 0) {
res.insert(E);
}
}
}
return res;
}
bool TranslationDomain::has_translation(const Ref<Translation> &p_translation) const {
return translations.has(p_translation);
}
bool TranslationDomain::has_translation_for_locale(const String &p_locale, bool p_exact) const {
if (p_exact) {
for (const Ref<Translation> &E : translations) {
if (E->get_locale() == p_locale) {
return true;
}
}
} else {
for (const Ref<Translation> &E : translations) {
if (TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale()) > 0) {
return true;
}
}
}
return false;
}
TypedArray<Translation> TranslationDomain::get_translations_bind() const {
TypedArray<Translation> res;
res.reserve(translations.size());
for (const Ref<Translation> &E : translations) {
res.push_back(E);
}
return res;
}
TypedArray<Translation> TranslationDomain::find_translations_bind(const String &p_locale, bool p_exact) const {
const HashSet<Ref<Translation>> &found = find_translations(p_locale, p_exact);
TypedArray<Translation> res;
res.reserve(found.size());
for (const Ref<Translation> &E : found) {
res.push_back(E);
}
return res;
}
StringName TranslationDomain::translate(const StringName &p_message, const StringName &p_context) const {
if (!enabled) {
return p_message;
@@ -459,10 +504,17 @@ StringName TranslationDomain::pseudolocalize(const StringName &p_message) const
}
void TranslationDomain::_bind_methods() {
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationDomain::get_translation_object);
#endif
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationDomain::add_translation);
ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationDomain::remove_translation);
ClassDB::bind_method(D_METHOD("clear"), &TranslationDomain::clear);
ClassDB::bind_method(D_METHOD("get_translations"), &TranslationDomain::get_translations_bind);
ClassDB::bind_method(D_METHOD("has_translation_for_locale", "locale", "exact"), &TranslationDomain::has_translation_for_locale);
ClassDB::bind_method(D_METHOD("has_translation", "translation"), &TranslationDomain::has_translation);
ClassDB::bind_method(D_METHOD("find_translations", "locale", "exact"), &TranslationDomain::find_translations_bind);
ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationDomain::translate, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("translate_plural", "message", "message_plural", "n", "context"), &TranslationDomain::translate_plural, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("get_locale_override"), &TranslationDomain::get_locale_override);

View File

@@ -71,16 +71,25 @@ public:
StringName get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const;
StringName get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const;
PackedStringArray get_loaded_locales() const;
bool has_translation_for_locale(const String &p_locale) const;
HashSet<Ref<Translation>> get_potential_translations(const String &p_locale) const;
public:
// These two methods are public for easier TranslationServer bindings.
TypedArray<Translation> get_translations_bind() const;
TypedArray<Translation> find_translations_bind(const String &p_locale, bool p_exact) const;
#ifndef DISABLE_DEPRECATED
Ref<Translation> get_translation_object(const String &p_locale) const;
#endif
void add_translation(const Ref<Translation> &p_translation);
void remove_translation(const Ref<Translation> &p_translation);
void clear();
bool has_translation(const Ref<Translation> &p_translation) const;
const HashSet<Ref<Translation>> get_translations() const;
HashSet<Ref<Translation>> find_translations(const String &p_locale, bool p_exact) const;
bool has_translation_for_locale(const String &p_locale, bool p_exact) const;
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;

View File

@@ -36,6 +36,7 @@
#include "core/os/main_loop.h"
#include "core/os/os.h"
#include "core/string/locales.h"
#include "core/variant/typed_array.h"
void TranslationServer::init_locale_info() {
// Init locale info.
@@ -503,9 +504,27 @@ void TranslationServer::remove_translation(const Ref<Translation> &p_translation
main_domain->remove_translation(p_translation);
}
#ifndef DISABLE_DEPRECATED
Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
return main_domain->get_translation_object(p_locale);
}
#endif
TypedArray<Translation> TranslationServer::get_translations() const {
return main_domain->get_translations_bind();
}
TypedArray<Translation> TranslationServer::find_translations(const String &p_locale, bool p_exact) const {
return main_domain->find_translations_bind(p_locale, p_exact);
}
bool TranslationServer::has_translation(const Ref<Translation> &p_translation) const {
return main_domain->has_translation(p_translation);
}
bool TranslationServer::has_translation_for_locale(const String &p_locale, bool p_exact) const {
return main_domain->has_translation_for_locale(p_locale, p_exact);
}
void TranslationServer::clear() {
main_domain->clear();
@@ -576,22 +595,28 @@ void TranslationServer::setup() {
String TranslationServer::get_tool_locale() {
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
if (editor_domain->has_translation_for_locale(locale)) {
if (editor_domain->has_translation_for_locale(locale, true)) {
return locale;
}
return "en";
} else {
#else
{
}
#endif
// Look for best matching loaded translation.
Ref<Translation> t = main_domain->get_translation_object(locale);
if (t.is_null()) {
return fallback;
Ref<Translation> res;
int best_score = 0;
for (const Ref<Translation> &E : main_domain->get_translations()) {
int score = TranslationServer::get_singleton()->compare_locales(locale, E->get_locale());
if (score > 0 && score >= best_score) {
res = E;
best_score = score;
if (score == 10) {
return locale; // Exact match.
}
return t->get_locale();
}
}
return res.is_valid() ? res->get_locale() : fallback;
}
bool TranslationServer::is_pseudolocalization_enabled() const {
return main_domain->is_pseudolocalization_enabled();
@@ -676,7 +701,15 @@ void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
#ifndef DISABLE_DEPRECATED
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
#endif
ClassDB::bind_method(D_METHOD("get_translations"), &TranslationServer::get_translations);
ClassDB::bind_method(D_METHOD("find_translations", "locale", "exact"), &TranslationServer::find_translations);
ClassDB::bind_method(D_METHOD("has_translation_for_locale", "locale", "exact"), &TranslationServer::has_translation_for_locale);
ClassDB::bind_method(D_METHOD("has_translation", "translation"), &TranslationServer::has_translation);
ClassDB::bind_method(D_METHOD("has_domain", "domain"), &TranslationServer::has_domain);
ClassDB::bind_method(D_METHOD("get_or_add_domain", "domain"), &TranslationServer::get_or_add_domain);

View File

@@ -110,7 +110,15 @@ public:
String get_locale() const;
void set_fallback_locale(const String &p_locale);
String get_fallback_locale() const;
#ifndef DISABLE_DEPRECATED
Ref<Translation> get_translation_object(const String &p_locale);
#endif
bool has_translation(const Ref<Translation> &p_translation) const;
TypedArray<Translation> get_translations() const;
TypedArray<Translation> find_translations(const String &p_locale, bool p_exact) const;
bool has_translation_for_locale(const String &p_locale, bool p_exact) const;
Vector<String> get_all_languages() const;
String get_language_name(const String &p_language) const;

View File

@@ -23,19 +23,48 @@
Removes all translations.
</description>
</method>
<method name="find_translations" qualifiers="const">
<return type="Translation[]" />
<param index="0" name="locale" type="String" />
<param index="1" name="exact" type="bool" />
<description>
Returns the [Translation] instances that match [param locale] (see [method TranslationServer.compare_locales]). If [param exact] is [code]true[/code], only instances whose locale exactly equals [param locale] will be returned.
</description>
</method>
<method name="get_locale_override" qualifiers="const">
<return type="String" />
<description>
Returns the locale override of the domain. Returns an empty string if locale override is disabled.
</description>
</method>
<method name="get_translation_object" qualifiers="const">
<method name="get_translation_object" qualifiers="const" deprecated="Use [method find_translations] instead.">
<return type="Translation" />
<param index="0" name="locale" type="String" />
<description>
Returns the [Translation] instance that best matches [param locale]. Returns [code]null[/code] if there are no matches.
</description>
</method>
<method name="get_translations" qualifiers="const">
<return type="Translation[]" />
<description>
Returns all available [Translation] instances as added by [method add_translation].
</description>
</method>
<method name="has_translation" qualifiers="const">
<return type="bool" />
<param index="0" name="translation" type="Translation" />
<description>
Returns [code]true[/code] if this translation domain contains the given [param translation].
</description>
</method>
<method name="has_translation_for_locale" qualifiers="const">
<return type="bool" />
<param index="0" name="locale" type="String" />
<param index="1" name="exact" type="bool" />
<description>
Returns [code]true[/code] if there are any [Translation] instances that match [param locale] (see [method TranslationServer.compare_locales]). If [param exact] is [code]true[/code], only instances whose locale exactly equals [param locale] are considered.
</description>
</method>
<method name="pseudolocalize" qualifiers="const">
<return type="StringName" />
<param index="0" name="message" type="StringName" />

View File

@@ -33,6 +33,14 @@
Compares two locales and returns a similarity score between [code]0[/code] (no match) and [code]10[/code] (full match).
</description>
</method>
<method name="find_translations" qualifiers="const">
<return type="Translation[]" />
<param index="0" name="locale" type="String" />
<param index="1" name="exact" type="bool" />
<description>
Returns the [Translation] instances in the main translation domain that match [param locale] (see [method compare_locales]). If [param exact] is [code]true[/code], only instances whose locale exactly equals [param locale] will be returned.
</description>
</method>
<method name="format_number" qualifiers="const">
<return type="String" />
<param index="0" name="number" type="String" />
@@ -128,13 +136,19 @@
[b]Note:[/b] When called from an exported project returns the same value as [method get_locale].
</description>
</method>
<method name="get_translation_object">
<method name="get_translation_object" deprecated="Use [method find_translations] instead.">
<return type="Translation" />
<param index="0" name="locale" type="String" />
<description>
Returns the [Translation] instance that best matches [param locale] in the main translation domain. Returns [code]null[/code] if there are no matches.
</description>
</method>
<method name="get_translations" qualifiers="const">
<return type="Translation[]" />
<description>
Returns all available [Translation] instances in the main translation domain as added by [method add_translation].
</description>
</method>
<method name="has_domain" qualifiers="const">
<return type="bool" />
<param index="0" name="domain" type="StringName" />
@@ -142,6 +156,21 @@
Returns [code]true[/code] if a translation domain with the specified name exists.
</description>
</method>
<method name="has_translation" qualifiers="const">
<return type="bool" />
<param index="0" name="translation" type="Translation" />
<description>
Returns [code]true[/code] if the main translation domain contains the given [param translation].
</description>
</method>
<method name="has_translation_for_locale" qualifiers="const">
<return type="bool" />
<param index="0" name="locale" type="String" />
<param index="1" name="exact" type="bool" />
<description>
Returns [code]true[/code] if there are any [Translation] instances in the main translation domain that match [param locale] (see [method compare_locales]). If [param exact] is [code]true[/code], only instances whose locale exactly equals [param locale] are considered.
</description>
</method>
<method name="parse_number" qualifiers="const">
<return type="String" />
<param index="0" name="number" type="String" />

View File

@@ -585,10 +585,9 @@ void EditorNode::_update_translations() {
if (main->is_enabled()) {
// Check for the exact locale.
// `get_potential_translations("zh_CN")` could return translations for "zh".
if (main->has_translation_for_locale(main->get_locale_override())) {
if (main->has_translation_for_locale(main->get_locale_override(), true)) {
// The set of translation resources for the current locale changed.
const HashSet<Ref<Translation>> translations = main->get_potential_translations(main->get_locale_override());
const HashSet<Ref<Translation>> translations = main->find_translations(main->get_locale_override(), false);
if (translations != tracked_translations) {
_translation_resources_changed();
}
@@ -609,7 +608,7 @@ void EditorNode::_translation_resources_changed() {
const Ref<TranslationDomain> main = TranslationServer::get_singleton()->get_main_domain();
if (main->is_enabled()) {
const HashSet<Ref<Translation>> translations = main->get_potential_translations(main->get_locale_override());
const HashSet<Ref<Translation>> translations = main->find_translations(main->get_locale_override(), false);
tracked_translations.reserve(translations.size());
for (const Ref<Translation> &translation : translations) {
translation->connect_changed(callable_mp(this, &EditorNode::_queue_translation_notification));
@@ -957,7 +956,7 @@ void EditorNode::_notification(int p_what) {
// Remember the selected locale to preview node translations.
const String preview_locale = EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "preview_locale", String());
if (!preview_locale.is_empty() && TranslationServer::get_singleton()->get_loaded_locales().has(preview_locale)) {
if (!preview_locale.is_empty() && TranslationServer::get_singleton()->has_translation_for_locale(preview_locale, true)) {
set_preview_locale(preview_locale);
}

View File

@@ -228,37 +228,37 @@ TEST_CASE("[TranslationCSV] CSV import") {
CHECK(result == OK);
CHECK(gen_files.size() == 4);
TranslationServer *ts = TranslationServer::get_singleton();
Ref<TranslationDomain> td = TranslationServer::get_singleton()->get_or_add_domain("godot.test");
for (const String &file : gen_files) {
Ref<Translation> translation = ResourceLoader::load(file);
CHECK(translation.is_valid());
ts->add_translation(translation);
td->add_translation(translation);
}
ts->set_locale("en");
td->set_locale_override("en");
// `tr` can be called on any Object, we reuse TranslationServer for convenience.
CHECK(ts->tr("GOOD_MORNING") == "Good Morning");
CHECK(ts->tr("GOOD_EVENING") == "Good Evening");
CHECK(td->translate("GOOD_MORNING", StringName()) == "Good Morning");
CHECK(td->translate("GOOD_EVENING", StringName()) == "Good Evening");
ts->set_locale("de");
td->set_locale_override("de");
CHECK(ts->tr("GOOD_MORNING") == "Guten Morgen");
CHECK(ts->tr("GOOD_EVENING") == "Good Evening"); // Left blank in CSV, should source from 'en'.
CHECK(td->translate("GOOD_MORNING", StringName()) == "Guten Morgen");
CHECK(td->translate("GOOD_EVENING", StringName()) == "Good Evening"); // Left blank in CSV, should source from 'en'.
ts->set_locale("ja");
td->set_locale_override("ja");
CHECK(ts->tr("GOOD_MORNING") == String::utf8("おはよう"));
CHECK(ts->tr("GOOD_EVENING") == String::utf8("こんばんは"));
CHECK(td->translate("GOOD_MORNING", StringName()) == String::utf8("おはよう"));
CHECK(td->translate("GOOD_EVENING", StringName()) == String::utf8("こんばんは"));
/* FIXME: This passes, but triggers a chain reaction that makes test_viewport
* and test_text_edit explode in a billion glittery Unicode particles.
ts->set_locale("fa");
td->set_locale_override("fa");
CHECK(ts->tr("GOOD_MORNING") == String::utf8("صبح بخیر"));
CHECK(ts->tr("GOOD_EVENING") == String::utf8("عصر بخیر"));
CHECK(td->translate("GOOD_MORNING", String()) == String::utf8("صبح بخیر"));
CHECK(td->translate("GOOD_EVENING", String()) == String::utf8("عصر بخیر"));
*/
TranslationServer::get_singleton()->remove_domain("godot.test");
}
#endif // TOOLS_ENABLED

View File

@@ -36,39 +36,55 @@
namespace TestTranslationServer {
TEST_CASE("[TranslationServer] Translation operations") {
Ref<TranslationDomain> td = TranslationServer::get_singleton()->get_or_add_domain("godot.test");
CHECK(td->get_translations().is_empty());
Ref<Translation> t1 = memnew(Translation);
t1->set_locale("uk");
t1->set_locale("uk"); // Ukrainian.
t1->add_message("Good Morning", String(U"Добрий ранок"));
td->add_translation(t1);
CHECK(td->get_translations().size() == 1);
CHECK(td->has_translation_for_locale("uk", true));
CHECK(td->has_translation_for_locale("uk", false));
CHECK_FALSE(td->has_translation_for_locale("uk_UA", true));
CHECK(td->has_translation_for_locale("uk_UA", false));
CHECK(td->find_translations("uk", false).size() == 1);
CHECK(td->find_translations("uk", true).size() == 1);
CHECK(td->find_translations("uk_UA", false).size() == 1);
CHECK(td->find_translations("uk_UA", true).size() == 0);
Ref<Translation> t2 = memnew(Translation);
t2->set_locale("uk");
t2->add_message("Hello Godot", String(U"你好戈多"));
t2->set_locale("uk_UA"); // Ukrainian in Ukraine.
t2->add_message("Hello Godot", String(U"Привіт, Годо."));
td->add_translation(t2);
CHECK(td->get_translations().size() == 2);
CHECK(td->has_translation_for_locale("uk", true));
CHECK(td->has_translation_for_locale("uk", false));
CHECK(td->has_translation_for_locale("uk_UA", true));
CHECK(td->has_translation_for_locale("uk_UA", false));
CHECK(td->find_translations("uk", false).size() == 2);
CHECK(td->find_translations("uk", true).size() == 1);
CHECK(td->find_translations("uk_UA", false).size() == 2);
CHECK(td->find_translations("uk_UA", true).size() == 1);
TranslationServer *ts = TranslationServer::get_singleton();
td->set_locale_override("uk");
CHECK(td->translate("Good Morning", StringName()) == String::utf8("Добрий ранок"));
// Adds translation for UK locale for the first time.
int l_count_before = ts->get_loaded_locales().size();
ts->add_translation(t1);
int l_count_after = ts->get_loaded_locales().size();
CHECK(l_count_after > l_count_before);
td->remove_translation(t1);
CHECK(td->get_translations().size() == 1);
CHECK_FALSE(td->has_translation_for_locale("uk", true));
CHECK(td->has_translation_for_locale("uk", false));
CHECK(td->has_translation_for_locale("uk_UA", true));
CHECK(td->has_translation_for_locale("uk_UA", false));
CHECK(td->find_translations("uk", true).size() == 0);
CHECK(td->find_translations("uk", false).size() == 1);
CHECK(td->find_translations("uk_UA", true).size() == 1);
CHECK(td->find_translations("uk_UA", false).size() == 1);
// Adds translation for UK locale again.
ts->add_translation(t2);
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
// Removing that translation.
ts->remove_translation(t2);
CHECK_EQ(ts->get_loaded_locales().size(), l_count_after);
CHECK(ts->get_translation_object("uk").is_valid());
ts->set_locale("uk");
CHECK(ts->translate("Good Morning") == String::utf8("Добрий ранок"));
ts->remove_translation(t1);
CHECK(ts->get_translation_object("uk").is_null());
// If no suitable Translation object has been found - the original message should be returned.
CHECK(ts->translate("Good Morning") == "Good Morning");
CHECK(td->translate("Good Morning", StringName()) == "Good Morning");
TranslationServer::get_singleton()->remove_domain("godot.test");
}
TEST_CASE("[TranslationServer] Locale operations") {