Merge pull request #95787 from timothyqiu/domestic

Add translation domain
This commit is contained in:
Rémi Verschelde 2024-09-20 16:06:23 +02:00
commit 7e62565f1e
No known key found for this signature in database
GPG key ID: C3336907360768E1
18 changed files with 509 additions and 241 deletions

View file

@ -1527,21 +1527,21 @@ void Object::initialize_class() {
initialized = true;
}
StringName Object::get_translation_domain() const {
return _translation_domain;
}
void Object::set_translation_domain(const StringName &p_domain) {
_translation_domain = p_domain;
}
String Object::tr(const StringName &p_message, const StringName &p_context) const {
if (!_can_translate || !TranslationServer::get_singleton()) {
return p_message;
}
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
String tr_msg = TranslationServer::get_singleton()->extractable_translate(p_message, p_context);
if (!tr_msg.is_empty() && tr_msg != p_message) {
return tr_msg;
}
return TranslationServer::get_singleton()->tool_translate(p_message, p_context);
}
return TranslationServer::get_singleton()->translate(p_message, p_context);
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(get_translation_domain());
return domain->translate(p_message, p_context);
}
String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
@ -1553,16 +1553,8 @@ String Object::tr_n(const StringName &p_message, const StringName &p_message_plu
return p_message_plural;
}
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
String tr_msg = TranslationServer::get_singleton()->extractable_translate_plural(p_message, p_message_plural, p_n, p_context);
if (!tr_msg.is_empty() && tr_msg != p_message && tr_msg != p_message_plural) {
return tr_msg;
}
return TranslationServer::get_singleton()->tool_translate_plural(p_message, p_message_plural, p_n, p_context);
}
return TranslationServer::get_singleton()->translate_plural(p_message, p_message_plural, p_n, p_context);
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(get_translation_domain());
return domain->translate_plural(p_message, p_message_plural, p_n, p_context);
}
void Object::_clear_internal_resource_paths(const Variant &p_var) {
@ -1714,6 +1706,8 @@ void Object::_bind_methods() {
ClassDB::bind_method(D_METHOD("can_translate_messages"), &Object::can_translate_messages);
ClassDB::bind_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("get_translation_domain"), &Object::get_translation_domain);
ClassDB::bind_method(D_METHOD("set_translation_domain", "domain"), &Object::set_translation_domain);
ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion);
ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free);

View file

@ -665,6 +665,8 @@ private:
Object(bool p_reference);
protected:
StringName _translation_domain;
_FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) {
bool can_die = true;
if (_instance_bindings) {
@ -954,6 +956,9 @@ public:
_FORCE_INLINE_ void set_message_translation(bool p_enable) { _can_translate = p_enable; }
_FORCE_INLINE_ bool can_translate_messages() const { return _can_translate; }
virtual StringName get_translation_domain() const;
virtual void set_translation_domain(const StringName &p_domain);
#ifdef TOOLS_ENABLED
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
void editor_set_section_unfold(const String &p_section, bool p_unfolded);

View file

@ -233,6 +233,7 @@ void register_core_types() {
GDREGISTER_CLASS(MainLoop);
GDREGISTER_CLASS(Translation);
GDREGISTER_CLASS(TranslationDomain);
GDREGISTER_CLASS(OptimizedTranslation);
GDREGISTER_CLASS(UndoRedo);
GDREGISTER_CLASS(TriangleMesh);

View file

@ -0,0 +1,165 @@
/**************************************************************************/
/* translation_domain.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_domain.h"
#include "core/string/translation.h"
#include "core/string/translation_server.h"
StringName TranslationDomain::get_message_from_translations(const String &p_locale, const StringName &p_message, const StringName &p_context) const {
StringName res;
int best_score = 0;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
if (score > 0 && score >= best_score) {
const StringName r = E->get_message(p_message, p_context);
if (!r) {
continue;
}
res = r;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return res;
}
StringName TranslationDomain::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 {
StringName res;
int best_score = 0;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
if (score > 0 && score >= best_score) {
const StringName r = E->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;
}
PackedStringArray TranslationDomain::get_loaded_locales() const {
PackedStringArray locales;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
locales.push_back(E->get_locale());
}
return locales;
}
Ref<Translation> TranslationDomain::get_translation_object(const String &p_locale) const {
Ref<Translation> res;
int best_score = 0;
for (const Ref<Translation> &E : translations) {
ERR_CONTINUE(E.is_null());
int score = TranslationServer::get_singleton()->compare_locales(p_locale, E->get_locale());
if (score > 0 && score >= best_score) {
res = E;
best_score = score;
if (score == 10) {
break; // Exact match, skip the rest.
}
}
}
return res;
}
void TranslationDomain::add_translation(const Ref<Translation> &p_translation) {
translations.insert(p_translation);
}
void TranslationDomain::remove_translation(const Ref<Translation> &p_translation) {
translations.erase(p_translation);
}
void TranslationDomain::clear() {
translations.clear();
}
StringName TranslationDomain::translate(const StringName &p_message, const StringName &p_context) const {
const String &locale = TranslationServer::get_singleton()->get_locale();
StringName res = get_message_from_translations(locale, p_message, p_context);
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
if (!res && fallback.length() >= 2) {
res = get_message_from_translations(fallback, p_message, p_context);
}
if (!res) {
return p_message;
}
return res;
}
StringName TranslationDomain::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
const String &locale = TranslationServer::get_singleton()->get_locale();
StringName res = get_message_from_translations(locale, p_message, p_message_plural, p_n, p_context);
const String &fallback = TranslationServer::get_singleton()->get_fallback_locale();
if (!res && fallback.length() >= 2) {
res = get_message_from_translations(fallback, p_message, p_message_plural, p_n, p_context);
}
if (!res) {
if (p_n == 1) {
return p_message;
}
return p_message_plural;
}
return res;
}
void TranslationDomain::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationDomain::get_translation_object);
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("translate", "message", "context"), &TranslationDomain::translate, DEFVAL(StringName()));
ClassDB::bind_method(D_METHOD("translate_plural", "message", "message_plural", "n", "context"), &TranslationDomain::translate_plural, DEFVAL(StringName()));
}
TranslationDomain::TranslationDomain() {
}

View file

@ -0,0 +1,65 @@
/**************************************************************************/
/* translation_domain.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_DOMAIN_H
#define TRANSLATION_DOMAIN_H
#include "core/object/ref_counted.h"
class Translation;
class TranslationDomain : public RefCounted {
GDCLASS(TranslationDomain, RefCounted);
HashSet<Ref<Translation>> translations;
protected:
static void _bind_methods();
public:
// Methods in this section are not intended for scripting.
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;
public:
Ref<Translation> get_translation_object(const String &p_locale) const;
void add_translation(const Ref<Translation> &p_translation);
void remove_translation(const Ref<Translation> &p_translation);
void clear();
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;
TranslationDomain();
};
#endif // TRANSLATION_DOMAIN_H

View file

@ -404,69 +404,36 @@ String TranslationServer::get_locale() const {
return locale;
}
String TranslationServer::get_fallback_locale() const {
return fallback;
}
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;
return main_domain->get_loaded_locales();
}
void TranslationServer::add_translation(const Ref<Translation> &p_translation) {
translations.insert(p_translation);
main_domain->add_translation(p_translation);
}
void TranslationServer::remove_translation(const Ref<Translation> &p_translation) {
translations.erase(p_translation);
main_domain->remove_translation(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;
return main_domain->get_translation_object(p_locale);
}
void TranslationServer::clear() {
translations.clear();
main_domain->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;
}
const StringName res = main_domain->translate(p_message, p_context);
return pseudolocalization_enabled ? pseudolocalize(res) : res;
}
@ -478,51 +445,7 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons
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;
return main_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
}
TranslationServer *TranslationServer::singleton = nullptr;
@ -549,6 +472,34 @@ bool TranslationServer::_load_translations(const String &p_from) {
return false;
}
bool TranslationServer::has_domain(const StringName &p_domain) const {
if (p_domain == StringName()) {
return true;
}
return custom_domains.has(p_domain);
}
Ref<TranslationDomain> TranslationServer::get_or_add_domain(const StringName &p_domain) {
if (p_domain == StringName()) {
return main_domain;
}
const Ref<TranslationDomain> *domain = custom_domains.getptr(p_domain);
if (domain) {
if (domain->is_valid()) {
return *domain;
}
ERR_PRINT("Bug (please report): Found invalid translation domain.");
}
Ref<TranslationDomain> new_domain = memnew(TranslationDomain);
custom_domains[p_domain] = new_domain;
return new_domain;
}
void TranslationServer::remove_domain(const StringName &p_domain) {
ERR_FAIL_COND_MSG(p_domain == StringName(), "Cannot remove main translation domain.");
custom_domains.erase(p_domain);
}
void TranslationServer::setup() {
String test = GLOBAL_DEF("internationalization/locale/test", "");
test = test.strip_edges();
@ -574,140 +525,45 @@ void TranslationServer::setup() {
#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 {
const PackedStringArray &locales = editor_domain->get_loaded_locales();
if (locales.is_empty()) {
return "en";
}
return locales[0];
} 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.
}
}
Ref<Translation> t = main_domain->get_translation_object(locale);
if (t.is_null()) {
return "en";
}
return best_locale;
return t->get_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;
return editor_domain->translate(p_message, p_context);
}
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;
return editor_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
}
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;
return property_domain->translate(p_message, p_context);
}
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;
return doc_domain->translate(p_message, p_context);
}
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;
return doc_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
}
bool TranslationServer::is_pseudolocalization_enabled() const {
@ -925,6 +781,10 @@ void TranslationServer::_bind_methods() {
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("has_domain", "domain"), &TranslationServer::has_domain);
ClassDB::bind_method(D_METHOD("get_or_add_domain", "domain"), &TranslationServer::get_or_add_domain);
ClassDB::bind_method(D_METHOD("remove_domain", "domain"), &TranslationServer::remove_domain);
ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
@ -947,5 +807,9 @@ void TranslationServer::load_translations() {
TranslationServer::TranslationServer() {
singleton = this;
main_domain.instantiate();
editor_domain = get_or_add_domain("godot.editor");
property_domain = get_or_add_domain("godot.properties");
doc_domain = get_or_add_domain("godot.documentation");
init_locale_info();
}

View file

@ -32,6 +32,7 @@
#define TRANSLATION_SERVER_H
#include "core/string/translation.h"
#include "core/string/translation_domain.h"
class TranslationServer : public Object {
GDCLASS(TranslationServer, Object);
@ -39,11 +40,11 @@ class TranslationServer : public 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;
Ref<TranslationDomain> main_domain;
Ref<TranslationDomain> editor_domain;
Ref<TranslationDomain> property_domain;
Ref<TranslationDomain> doc_domain;
HashMap<StringName, Ref<TranslationDomain>> custom_domains;
bool enabled = true;
@ -70,8 +71,6 @@ class TranslationServer : public Object {
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();
struct LocaleScriptInfo {
@ -99,6 +98,7 @@ public:
void set_locale(const String &p_locale);
String get_locale() const;
String get_fallback_locale() const;
Ref<Translation> get_translation_object(const String &p_locale);
Vector<String> get_all_languages() const;
@ -131,18 +131,15 @@ public:
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;
bool has_domain(const StringName &p_domain) const;
Ref<TranslationDomain> get_or_add_domain(const StringName &p_domain);
void remove_domain(const StringName &p_domain);
void setup();

View file

@ -973,6 +973,13 @@
Similar to [method call_thread_safe], but for setting properties.
</description>
</method>
<method name="set_translation_domain_inherited">
<return type="void" />
<description>
Makes this node inherit the translation domain from its parent node. If this node has no parent, the main translation domain will be used.
This is the default behavior for all nodes. Calling [method Object.set_translation_domain] disables this behavior.
</description>
</method>
<method name="update_configuration_warnings">
<return type="void" />
<description>

View file

@ -818,6 +818,12 @@
[b]Note:[/b] Due of the implementation, each [Dictionary] is formatted very similarly to the returned values of [method get_method_list].
</description>
</method>
<method name="get_translation_domain" qualifiers="const">
<return type="StringName" />
<description>
Returns the name of the translation domain used by [method tr] and [method tr_n]. See also [TranslationServer].
</description>
</method>
<method name="has_meta" qualifiers="const">
<return type="bool" />
<param index="0" name="name" type="StringName" />
@ -1070,6 +1076,13 @@
If a script already exists, its instance is detached, and its property values and state are lost. Built-in property values are still kept.
</description>
</method>
<method name="set_translation_domain">
<return type="void" />
<param index="0" name="domain" type="StringName" />
<description>
Sets the name of the translation domain used by [method tr] and [method tr_n]. See also [TranslationServer].
</description>
</method>
<method name="to_string">
<return type="String" />
<description>

View file

@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8" ?>
<class name="TranslationDomain" inherits="RefCounted" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd">
<brief_description>
A self-contained collection of [Translation] resources.
</brief_description>
<description>
[TranslationDomain] is a self-contained collection of [Translation] resources. Translations can be added to or removed from it.
If you're working with the main translation domain, it is more convenient to use the wrap methods on [TranslationServer].
</description>
<tutorials>
</tutorials>
<methods>
<method name="add_translation">
<return type="void" />
<param index="0" name="translation" type="Translation" />
<description>
Adds a translation.
</description>
</method>
<method name="clear">
<return type="void" />
<description>
Removes all translations.
</description>
</method>
<method name="get_translation_object" qualifiers="const">
<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="remove_translation">
<return type="void" />
<param index="0" name="translation" type="Translation" />
<description>
Removes the given translation.
</description>
</method>
<method name="translate" qualifiers="const">
<return type="StringName" />
<param index="0" name="message" type="StringName" />
<param index="1" name="context" type="StringName" default="&amp;&quot;&quot;" />
<description>
Returns the current locale's translation for the given message and context.
</description>
</method>
<method name="translate_plural" qualifiers="const">
<return type="StringName" />
<param index="0" name="message" type="StringName" />
<param index="1" name="message_plural" type="StringName" />
<param index="2" name="n" type="int" />
<param index="3" name="context" type="StringName" default="&amp;&quot;&quot;" />
<description>
Returns the current locale's translation for the given message, plural message and context.
The number [param n] is the number or quantity of the plural object. It will be used to guide the translation system to fetch the correct plural form for the selected language.
</description>
</method>
</methods>
</class>

View file

@ -4,7 +4,8 @@
The server responsible for language translations.
</brief_description>
<description>
The server that manages all language translations. Translations can be added to or removed from it.
The translation server is the API backend that manages all language translations.
Translations are stored in [TranslationDomain]s, which can be accessed by name. The most commonly used translation domain is the main translation domain. It always exists and can be accessed using an empty [StringName]. The translation server provides wrapper methods for accessing the main translation domain directly, without having to fetch the translation domain first. Custom translation domains are mainly for advanced usages like editor plugins. Names starting with [code]godot.[/code] are reserved for engine internals.
</description>
<tutorials>
<link title="Internationalizing games">$DOCS_URL/tutorials/i18n/internationalizing_games.html</link>
@ -15,13 +16,13 @@
<return type="void" />
<param index="0" name="translation" type="Translation" />
<description>
Adds a [Translation] resource.
Adds a translation to the main translation domain.
</description>
</method>
<method name="clear">
<return type="void" />
<description>
Clears the server from all translations.
Removes all translations from the main translation domain.
</description>
</method>
<method name="compare_locales" qualifiers="const">
@ -84,6 +85,13 @@
Returns a locale's language and its variant (e.g. [code]"en_US"[/code] would return [code]"English (United States)"[/code]).
</description>
</method>
<method name="get_or_add_domain">
<return type="TranslationDomain" />
<param index="0" name="domain" type="StringName" />
<description>
Returns the translation domain with the specified name. An empty translation domain will be created and added if it does not exist.
</description>
</method>
<method name="get_script_name" qualifiers="const">
<return type="String" />
<param index="0" name="script" type="String" />
@ -102,8 +110,14 @@
<return type="Translation" />
<param index="0" name="locale" type="String" />
<description>
Returns the [Translation] instance based on the [param locale] passed in.
It will return [code]null[/code] if there is no [Translation] instance that matches the [param locale].
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="has_domain" qualifiers="const">
<return type="bool" />
<param index="0" name="domain" type="StringName" />
<description>
Returns [code]true[/code] if a translation domain with the specified name exists.
</description>
</method>
<method name="pseudolocalize" qualifiers="const">
@ -119,11 +133,19 @@
Reparses the pseudolocalization options and reloads the translation.
</description>
</method>
<method name="remove_domain">
<return type="void" />
<param index="0" name="domain" type="StringName" />
<description>
Removes the translation domain with the specified name.
[b]Note:[/b] Trying to remove the main translation domain is an error.
</description>
</method>
<method name="remove_translation">
<return type="void" />
<param index="0" name="translation" type="Translation" />
<description>
Removes the given translation from the server.
Removes the given translation from the main translation domain.
</description>
</method>
<method name="set_locale">
@ -146,7 +168,8 @@
<param index="0" name="message" type="StringName" />
<param index="1" name="context" type="StringName" default="&amp;&quot;&quot;" />
<description>
Returns the current locale's translation for the given message (key) and context.
Returns the current locale's translation for the given message and context.
[b]Note:[/b] This method always uses the main translation domain.
</description>
</method>
<method name="translate_plural" qualifiers="const">
@ -156,8 +179,9 @@
<param index="2" name="n" type="int" />
<param index="3" name="context" type="StringName" default="&amp;&quot;&quot;" />
<description>
Returns the current locale's translation for the given message (key), plural message and context.
Returns the current locale's translation for the given message, plural message and context.
The number [param n] is the number or quantity of the plural object. It will be used to guide the translation system to fetch the correct plural form for the selected language.
[b]Note:[/b] This method always uses the main translation domain.
</description>
</method>
</methods>

View file

@ -6677,6 +6677,8 @@ EditorNode::EditorNode() {
DEV_ASSERT(!singleton);
singleton = this;
set_translation_domain("godot.editor");
Resource::_get_local_scene_func = _resource_get_edited_scene;
{

View file

@ -1226,6 +1226,8 @@ fail:
void EditorSettings::setup_language() {
String lang = get("interface/editor/editor_language");
TranslationServer::get_singleton()->set_locale(lang);
if (lang == "en") {
return; // Default, nothing to do.
}

View file

@ -54,6 +54,8 @@ Vector<String> get_editor_locales() {
}
void load_editor_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor");
EditorTranslationList *etl = _editor_translations;
while (etl->data) {
if (etl->lang == p_locale) {
@ -70,7 +72,7 @@ void load_editor_translations(const String &p_locale) {
if (tr.is_valid()) {
tr->set_locale(etl->lang);
TranslationServer::get_singleton()->set_tool_translation(tr);
domain->add_translation(tr);
break;
}
}
@ -80,6 +82,8 @@ void load_editor_translations(const String &p_locale) {
}
void load_property_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.properties");
PropertyTranslationList *etl = _property_translations;
while (etl->data) {
if (etl->lang == p_locale) {
@ -96,7 +100,7 @@ void load_property_translations(const String &p_locale) {
if (tr.is_valid()) {
tr->set_locale(etl->lang);
TranslationServer::get_singleton()->set_property_translation(tr);
domain->add_translation(tr);
break;
}
}
@ -106,6 +110,8 @@ void load_property_translations(const String &p_locale) {
}
void load_doc_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.documentation");
DocTranslationList *dtl = _doc_translations;
while (dtl->data) {
if (dtl->lang == p_locale) {
@ -122,7 +128,7 @@ void load_doc_translations(const String &p_locale) {
if (tr.is_valid()) {
tr->set_locale(dtl->lang);
TranslationServer::get_singleton()->set_doc_translation(tr);
domain->add_translation(tr);
break;
}
}
@ -132,6 +138,8 @@ void load_doc_translations(const String &p_locale) {
}
void load_extractable_translations(const String &p_locale) {
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain("godot.editor");
ExtractableTranslationList *etl = _extractable_translations;
while (etl->data) {
if (etl->lang == p_locale) {
@ -148,7 +156,7 @@ void load_extractable_translations(const String &p_locale) {
if (tr.is_valid()) {
tr->set_locale(etl->lang);
TranslationServer::get_singleton()->set_extractable_translation(tr);
domain->add_translation(tr);
break;
}
}

View file

@ -207,6 +207,10 @@ void EditorFileDialog::_update_theme_item_cache() {
void EditorFileDialog::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_POSTINITIALIZE: {
set_translation_domain(SNAME("godot.editor"));
} break;
case NOTIFICATION_THEME_CHANGED:
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
case NOTIFICATION_TRANSLATION_CHANGED: {

View file

@ -1081,6 +1081,8 @@ void ProjectManager::_titlebar_resized() {
ProjectManager::ProjectManager() {
singleton = this;
set_translation_domain("godot.editor");
// Turn off some servers we aren't going to be using in the Project Manager.
NavigationServer3D::get_singleton()->set_active(false);
PhysicsServer3D::get_singleton()->set_active(false);

View file

@ -111,6 +111,7 @@ void Node::_notification(int p_notification) {
data.auto_translate_mode = AUTO_TRANSLATE_MODE_ALWAYS;
}
data.is_auto_translate_dirty = true;
data.is_translation_domain_dirty = true;
#ifdef TOOLS_ENABLED
// Don't translate UI elements when they're being edited.
@ -1320,6 +1321,51 @@ bool Node::can_auto_translate() const {
return data.is_auto_translating;
}
StringName Node::get_translation_domain() const {
ERR_READ_THREAD_GUARD_V(StringName());
if (data.is_translation_domain_inherited && data.is_translation_domain_dirty) {
const_cast<Node *>(this)->_translation_domain = data.parent ? data.parent->get_translation_domain() : StringName();
data.is_translation_domain_dirty = false;
}
return _translation_domain;
}
void Node::set_translation_domain(const StringName &p_domain) {
ERR_THREAD_GUARD
if (!data.is_translation_domain_inherited && _translation_domain == p_domain) {
return;
}
_translation_domain = p_domain;
data.is_translation_domain_inherited = false;
data.is_translation_domain_dirty = false;
_propagate_translation_domain_dirty();
}
void Node::set_translation_domain_inherited() {
ERR_THREAD_GUARD
if (data.is_translation_domain_inherited) {
return;
}
data.is_translation_domain_inherited = true;
data.is_translation_domain_dirty = true;
_propagate_translation_domain_dirty();
}
void Node::_propagate_translation_domain_dirty() {
for (KeyValue<StringName, Node *> &K : data.children) {
Node *child = K.value;
if (child->data.is_translation_domain_inherited) {
child->data.is_translation_domain_dirty = true;
child->_propagate_translation_domain_dirty();
}
}
notification(NOTIFICATION_TRANSLATION_CHANGED);
}
StringName Node::get_name() const {
return data.name;
}
@ -3610,6 +3656,7 @@ void Node::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_auto_translate_mode", "mode"), &Node::set_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_auto_translate_mode"), &Node::get_auto_translate_mode);
ClassDB::bind_method(D_METHOD("set_translation_domain_inherited"), &Node::set_translation_domain_inherited);
ClassDB::bind_method(D_METHOD("get_window"), &Node::get_window);
ClassDB::bind_method(D_METHOD("get_last_exclusive_window"), &Node::get_last_exclusive_window);

View file

@ -255,6 +255,9 @@ private:
mutable bool is_auto_translating = true;
mutable bool is_auto_translate_dirty = true;
mutable bool is_translation_domain_inherited = true;
mutable bool is_translation_domain_dirty = true;
mutable NodePath *path_cache = nullptr;
} data;
@ -281,6 +284,7 @@ private:
void _propagate_physics_interpolation_reset_requested(bool p_requested);
void _propagate_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
void _propagate_groups_dirty();
void _propagate_translation_domain_dirty();
Array _get_node_and_resource(const NodePath &p_path);
void _duplicate_properties(const Node *p_root, const Node *p_original, Node *p_copy, int p_flags) const;
@ -735,6 +739,10 @@ public:
AutoTranslateMode get_auto_translate_mode() const;
bool can_auto_translate() const;
virtual StringName get_translation_domain() const override;
virtual void set_translation_domain(const StringName &p_domain) override;
void set_translation_domain_inherited();
_FORCE_INLINE_ String atr(const String p_message, const StringName p_context = "") const { return can_auto_translate() ? tr(p_message, p_context) : p_message; }
_FORCE_INLINE_ String atr_n(const String p_message, const StringName &p_message_plural, int p_n, const StringName p_context = "") const { return can_auto_translate() ? tr_n(p_message, p_message_plural, p_n, p_context) : p_message; }