Merge pull request #95787 from timothyqiu/domestic
Add translation domain
This commit is contained in:
commit
7e62565f1e
|
@ -1527,21 +1527,21 @@ void Object::initialize_class() {
|
||||||
initialized = true;
|
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 {
|
String Object::tr(const StringName &p_message, const StringName &p_context) const {
|
||||||
if (!_can_translate || !TranslationServer::get_singleton()) {
|
if (!_can_translate || !TranslationServer::get_singleton()) {
|
||||||
return p_message;
|
return p_message;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
|
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(get_translation_domain());
|
||||||
String tr_msg = TranslationServer::get_singleton()->extractable_translate(p_message, p_context);
|
return domain->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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
|
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;
|
return p_message_plural;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
|
const Ref<TranslationDomain> domain = TranslationServer::get_singleton()->get_or_add_domain(get_translation_domain());
|
||||||
String tr_msg = TranslationServer::get_singleton()->extractable_translate_plural(p_message, p_message_plural, p_n, p_context);
|
return domain->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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Object::_clear_internal_resource_paths(const Variant &p_var) {
|
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("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", "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("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("is_queued_for_deletion"), &Object::is_queued_for_deletion);
|
||||||
ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free);
|
ClassDB::bind_method(D_METHOD("cancel_free"), &Object::cancel_free);
|
||||||
|
|
|
@ -665,6 +665,8 @@ private:
|
||||||
Object(bool p_reference);
|
Object(bool p_reference);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
StringName _translation_domain;
|
||||||
|
|
||||||
_FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) {
|
_FORCE_INLINE_ bool _instance_binding_reference(bool p_reference) {
|
||||||
bool can_die = true;
|
bool can_die = true;
|
||||||
if (_instance_bindings) {
|
if (_instance_bindings) {
|
||||||
|
@ -954,6 +956,9 @@ public:
|
||||||
_FORCE_INLINE_ void set_message_translation(bool p_enable) { _can_translate = p_enable; }
|
_FORCE_INLINE_ void set_message_translation(bool p_enable) { _can_translate = p_enable; }
|
||||||
_FORCE_INLINE_ bool can_translate_messages() const { return _can_translate; }
|
_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
|
#ifdef TOOLS_ENABLED
|
||||||
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
|
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);
|
void editor_set_section_unfold(const String &p_section, bool p_unfolded);
|
||||||
|
|
|
@ -233,6 +233,7 @@ void register_core_types() {
|
||||||
|
|
||||||
GDREGISTER_CLASS(MainLoop);
|
GDREGISTER_CLASS(MainLoop);
|
||||||
GDREGISTER_CLASS(Translation);
|
GDREGISTER_CLASS(Translation);
|
||||||
|
GDREGISTER_CLASS(TranslationDomain);
|
||||||
GDREGISTER_CLASS(OptimizedTranslation);
|
GDREGISTER_CLASS(OptimizedTranslation);
|
||||||
GDREGISTER_CLASS(UndoRedo);
|
GDREGISTER_CLASS(UndoRedo);
|
||||||
GDREGISTER_CLASS(TriangleMesh);
|
GDREGISTER_CLASS(TriangleMesh);
|
||||||
|
|
|
@ -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() {
|
||||||
|
}
|
|
@ -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
|
|
@ -404,69 +404,36 @@ String TranslationServer::get_locale() const {
|
||||||
return locale;
|
return locale;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String TranslationServer::get_fallback_locale() const {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
|
||||||
PackedStringArray TranslationServer::get_loaded_locales() const {
|
PackedStringArray TranslationServer::get_loaded_locales() const {
|
||||||
PackedStringArray locales;
|
return main_domain->get_loaded_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) {
|
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) {
|
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> TranslationServer::get_translation_object(const String &p_locale) {
|
||||||
Ref<Translation> res;
|
return main_domain->get_translation_object(p_locale);
|
||||||
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() {
|
void TranslationServer::clear() {
|
||||||
translations.clear();
|
main_domain->clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
StringName TranslationServer::translate(const StringName &p_message, const StringName &p_context) const {
|
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) {
|
if (!enabled) {
|
||||||
return p_message;
|
return p_message;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringName res = _get_message_from_translations(p_message, p_context, locale, false);
|
const StringName res = main_domain->translate(p_message, p_context);
|
||||||
|
|
||||||
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;
|
return pseudolocalization_enabled ? pseudolocalize(res) : res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,51 +445,7 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons
|
||||||
return p_message_plural;
|
return p_message_plural;
|
||||||
}
|
}
|
||||||
|
|
||||||
StringName res = _get_message_from_translations(p_message, p_context, locale, true, p_message_plural, p_n);
|
return main_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
|
||||||
|
|
||||||
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;
|
TranslationServer *TranslationServer::singleton = nullptr;
|
||||||
|
@ -549,6 +472,34 @@ bool TranslationServer::_load_translations(const String &p_from) {
|
||||||
return false;
|
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() {
|
void TranslationServer::setup() {
|
||||||
String test = GLOBAL_DEF("internationalization/locale/test", "");
|
String test = GLOBAL_DEF("internationalization/locale/test", "");
|
||||||
test = test.strip_edges();
|
test = test.strip_edges();
|
||||||
|
@ -574,140 +525,45 @@ void TranslationServer::setup() {
|
||||||
#endif
|
#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() {
|
String TranslationServer::get_tool_locale() {
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
|
if (Engine::get_singleton()->is_editor_hint() || Engine::get_singleton()->is_project_manager_hint()) {
|
||||||
if (TranslationServer::get_singleton()->get_tool_translation().is_valid()) {
|
const PackedStringArray &locales = editor_domain->get_loaded_locales();
|
||||||
return tool_translation->get_locale();
|
if (locales.is_empty()) {
|
||||||
} else {
|
|
||||||
return "en";
|
return "en";
|
||||||
}
|
}
|
||||||
|
return locales[0];
|
||||||
} else {
|
} else {
|
||||||
#else
|
#else
|
||||||
{
|
{
|
||||||
#endif
|
#endif
|
||||||
// Look for best matching loaded translation.
|
// Look for best matching loaded translation.
|
||||||
String best_locale = "en";
|
Ref<Translation> t = main_domain->get_translation_object(locale);
|
||||||
int best_score = 0;
|
if (t.is_null()) {
|
||||||
|
return "en";
|
||||||
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;
|
return t->get_locale();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
|
StringName TranslationServer::tool_translate(const StringName &p_message, const StringName &p_context) const {
|
||||||
if (tool_translation.is_valid()) {
|
return editor_domain->translate(p_message, p_context);
|
||||||
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 {
|
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()) {
|
return editor_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
|
||||||
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 {
|
StringName TranslationServer::property_translate(const StringName &p_message, const StringName &p_context) const {
|
||||||
if (property_translation.is_valid()) {
|
return property_domain->translate(p_message, p_context);
|
||||||
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 {
|
StringName TranslationServer::doc_translate(const StringName &p_message, const StringName &p_context) const {
|
||||||
if (doc_translation.is_valid()) {
|
return doc_domain->translate(p_message, p_context);
|
||||||
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 {
|
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()) {
|
return doc_domain->translate_plural(p_message, p_message_plural, p_n, p_context);
|
||||||
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 {
|
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("remove_translation", "translation"), &TranslationServer::remove_translation);
|
||||||
ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
|
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("clear"), &TranslationServer::clear);
|
||||||
|
|
||||||
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
|
ClassDB::bind_method(D_METHOD("get_loaded_locales"), &TranslationServer::get_loaded_locales);
|
||||||
|
@ -947,5 +807,9 @@ void TranslationServer::load_translations() {
|
||||||
|
|
||||||
TranslationServer::TranslationServer() {
|
TranslationServer::TranslationServer() {
|
||||||
singleton = this;
|
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();
|
init_locale_info();
|
||||||
}
|
}
|
||||||
|
|
|
@ -32,6 +32,7 @@
|
||||||
#define TRANSLATION_SERVER_H
|
#define TRANSLATION_SERVER_H
|
||||||
|
|
||||||
#include "core/string/translation.h"
|
#include "core/string/translation.h"
|
||||||
|
#include "core/string/translation_domain.h"
|
||||||
|
|
||||||
class TranslationServer : public Object {
|
class TranslationServer : public Object {
|
||||||
GDCLASS(TranslationServer, Object);
|
GDCLASS(TranslationServer, Object);
|
||||||
|
@ -39,11 +40,11 @@ class TranslationServer : public Object {
|
||||||
String locale = "en";
|
String locale = "en";
|
||||||
String fallback;
|
String fallback;
|
||||||
|
|
||||||
HashSet<Ref<Translation>> translations;
|
Ref<TranslationDomain> main_domain;
|
||||||
Ref<Translation> tool_translation;
|
Ref<TranslationDomain> editor_domain;
|
||||||
Ref<Translation> property_translation;
|
Ref<TranslationDomain> property_domain;
|
||||||
Ref<Translation> doc_translation;
|
Ref<TranslationDomain> doc_domain;
|
||||||
Ref<Translation> extractable_translation;
|
HashMap<StringName, Ref<TranslationDomain>> custom_domains;
|
||||||
|
|
||||||
bool enabled = true;
|
bool enabled = true;
|
||||||
|
|
||||||
|
@ -70,8 +71,6 @@ class TranslationServer : public Object {
|
||||||
bool _load_translations(const String &p_from);
|
bool _load_translations(const String &p_from);
|
||||||
String _standardize_locale(const String &p_locale, bool p_add_defaults) const;
|
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();
|
static void _bind_methods();
|
||||||
|
|
||||||
struct LocaleScriptInfo {
|
struct LocaleScriptInfo {
|
||||||
|
@ -99,6 +98,7 @@ public:
|
||||||
|
|
||||||
void set_locale(const String &p_locale);
|
void set_locale(const String &p_locale);
|
||||||
String get_locale() const;
|
String get_locale() const;
|
||||||
|
String get_fallback_locale() const;
|
||||||
Ref<Translation> get_translation_object(const String &p_locale);
|
Ref<Translation> get_translation_object(const String &p_locale);
|
||||||
|
|
||||||
Vector<String> get_all_languages() const;
|
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;
|
int compare_locales(const String &p_locale_a, const String &p_locale_b) const;
|
||||||
|
|
||||||
String get_tool_locale();
|
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(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;
|
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;
|
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(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;
|
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;
|
bool has_domain(const StringName &p_domain) const;
|
||||||
StringName extractable_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
|
Ref<TranslationDomain> get_or_add_domain(const StringName &p_domain);
|
||||||
|
void remove_domain(const StringName &p_domain);
|
||||||
|
|
||||||
void setup();
|
void setup();
|
||||||
|
|
||||||
|
|
|
@ -973,6 +973,13 @@
|
||||||
Similar to [method call_thread_safe], but for setting properties.
|
Similar to [method call_thread_safe], but for setting properties.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="update_configuration_warnings">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
|
|
|
@ -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].
|
[b]Note:[/b] Due of the implementation, each [Dictionary] is formatted very similarly to the returned values of [method get_method_list].
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="has_meta" qualifiers="const">
|
||||||
<return type="bool" />
|
<return type="bool" />
|
||||||
<param index="0" name="name" type="StringName" />
|
<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.
|
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>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="to_string">
|
||||||
<return type="String" />
|
<return type="String" />
|
||||||
<description>
|
<description>
|
||||||
|
|
|
@ -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="&""" />
|
||||||
|
<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="&""" />
|
||||||
|
<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>
|
|
@ -4,7 +4,8 @@
|
||||||
The server responsible for language translations.
|
The server responsible for language translations.
|
||||||
</brief_description>
|
</brief_description>
|
||||||
<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>
|
</description>
|
||||||
<tutorials>
|
<tutorials>
|
||||||
<link title="Internationalizing games">$DOCS_URL/tutorials/i18n/internationalizing_games.html</link>
|
<link title="Internationalizing games">$DOCS_URL/tutorials/i18n/internationalizing_games.html</link>
|
||||||
|
@ -15,13 +16,13 @@
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="translation" type="Translation" />
|
<param index="0" name="translation" type="Translation" />
|
||||||
<description>
|
<description>
|
||||||
Adds a [Translation] resource.
|
Adds a translation to the main translation domain.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="clear">
|
<method name="clear">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<description>
|
<description>
|
||||||
Clears the server from all translations.
|
Removes all translations from the main translation domain.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="compare_locales" qualifiers="const">
|
<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]).
|
Returns a locale's language and its variant (e.g. [code]"en_US"[/code] would return [code]"English (United States)"[/code]).
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="get_script_name" qualifiers="const">
|
||||||
<return type="String" />
|
<return type="String" />
|
||||||
<param index="0" name="script" type="String" />
|
<param index="0" name="script" type="String" />
|
||||||
|
@ -102,8 +110,14 @@
|
||||||
<return type="Translation" />
|
<return type="Translation" />
|
||||||
<param index="0" name="locale" type="String" />
|
<param index="0" name="locale" type="String" />
|
||||||
<description>
|
<description>
|
||||||
Returns the [Translation] instance based on the [param locale] passed in.
|
Returns the [Translation] instance that best matches [param locale] in the main translation domain. Returns [code]null[/code] if there are no matches.
|
||||||
It will return [code]null[/code] if there is no [Translation] instance that matches the [param locale].
|
</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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="pseudolocalize" qualifiers="const">
|
<method name="pseudolocalize" qualifiers="const">
|
||||||
|
@ -119,11 +133,19 @@
|
||||||
Reparses the pseudolocalization options and reloads the translation.
|
Reparses the pseudolocalization options and reloads the translation.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</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">
|
<method name="remove_translation">
|
||||||
<return type="void" />
|
<return type="void" />
|
||||||
<param index="0" name="translation" type="Translation" />
|
<param index="0" name="translation" type="Translation" />
|
||||||
<description>
|
<description>
|
||||||
Removes the given translation from the server.
|
Removes the given translation from the main translation domain.
|
||||||
</description>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="set_locale">
|
<method name="set_locale">
|
||||||
|
@ -146,7 +168,8 @@
|
||||||
<param index="0" name="message" type="StringName" />
|
<param index="0" name="message" type="StringName" />
|
||||||
<param index="1" name="context" type="StringName" default="&""" />
|
<param index="1" name="context" type="StringName" default="&""" />
|
||||||
<description>
|
<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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
<method name="translate_plural" qualifiers="const">
|
<method name="translate_plural" qualifiers="const">
|
||||||
|
@ -156,8 +179,9 @@
|
||||||
<param index="2" name="n" type="int" />
|
<param index="2" name="n" type="int" />
|
||||||
<param index="3" name="context" type="StringName" default="&""" />
|
<param index="3" name="context" type="StringName" default="&""" />
|
||||||
<description>
|
<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.
|
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>
|
</description>
|
||||||
</method>
|
</method>
|
||||||
</methods>
|
</methods>
|
||||||
|
|
|
@ -6677,6 +6677,8 @@ EditorNode::EditorNode() {
|
||||||
DEV_ASSERT(!singleton);
|
DEV_ASSERT(!singleton);
|
||||||
singleton = this;
|
singleton = this;
|
||||||
|
|
||||||
|
set_translation_domain("godot.editor");
|
||||||
|
|
||||||
Resource::_get_local_scene_func = _resource_get_edited_scene;
|
Resource::_get_local_scene_func = _resource_get_edited_scene;
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
|
@ -1226,6 +1226,8 @@ fail:
|
||||||
|
|
||||||
void EditorSettings::setup_language() {
|
void EditorSettings::setup_language() {
|
||||||
String lang = get("interface/editor/editor_language");
|
String lang = get("interface/editor/editor_language");
|
||||||
|
TranslationServer::get_singleton()->set_locale(lang);
|
||||||
|
|
||||||
if (lang == "en") {
|
if (lang == "en") {
|
||||||
return; // Default, nothing to do.
|
return; // Default, nothing to do.
|
||||||
}
|
}
|
||||||
|
|
|
@ -54,6 +54,8 @@ Vector<String> get_editor_locales() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_editor_translations(const String &p_locale) {
|
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;
|
EditorTranslationList *etl = _editor_translations;
|
||||||
while (etl->data) {
|
while (etl->data) {
|
||||||
if (etl->lang == p_locale) {
|
if (etl->lang == p_locale) {
|
||||||
|
@ -70,7 +72,7 @@ void load_editor_translations(const String &p_locale) {
|
||||||
|
|
||||||
if (tr.is_valid()) {
|
if (tr.is_valid()) {
|
||||||
tr->set_locale(etl->lang);
|
tr->set_locale(etl->lang);
|
||||||
TranslationServer::get_singleton()->set_tool_translation(tr);
|
domain->add_translation(tr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -80,6 +82,8 @@ void load_editor_translations(const String &p_locale) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_property_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;
|
PropertyTranslationList *etl = _property_translations;
|
||||||
while (etl->data) {
|
while (etl->data) {
|
||||||
if (etl->lang == p_locale) {
|
if (etl->lang == p_locale) {
|
||||||
|
@ -96,7 +100,7 @@ void load_property_translations(const String &p_locale) {
|
||||||
|
|
||||||
if (tr.is_valid()) {
|
if (tr.is_valid()) {
|
||||||
tr->set_locale(etl->lang);
|
tr->set_locale(etl->lang);
|
||||||
TranslationServer::get_singleton()->set_property_translation(tr);
|
domain->add_translation(tr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,6 +110,8 @@ void load_property_translations(const String &p_locale) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_doc_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;
|
DocTranslationList *dtl = _doc_translations;
|
||||||
while (dtl->data) {
|
while (dtl->data) {
|
||||||
if (dtl->lang == p_locale) {
|
if (dtl->lang == p_locale) {
|
||||||
|
@ -122,7 +128,7 @@ void load_doc_translations(const String &p_locale) {
|
||||||
|
|
||||||
if (tr.is_valid()) {
|
if (tr.is_valid()) {
|
||||||
tr->set_locale(dtl->lang);
|
tr->set_locale(dtl->lang);
|
||||||
TranslationServer::get_singleton()->set_doc_translation(tr);
|
domain->add_translation(tr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,6 +138,8 @@ void load_doc_translations(const String &p_locale) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void load_extractable_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;
|
ExtractableTranslationList *etl = _extractable_translations;
|
||||||
while (etl->data) {
|
while (etl->data) {
|
||||||
if (etl->lang == p_locale) {
|
if (etl->lang == p_locale) {
|
||||||
|
@ -148,7 +156,7 @@ void load_extractable_translations(const String &p_locale) {
|
||||||
|
|
||||||
if (tr.is_valid()) {
|
if (tr.is_valid()) {
|
||||||
tr->set_locale(etl->lang);
|
tr->set_locale(etl->lang);
|
||||||
TranslationServer::get_singleton()->set_extractable_translation(tr);
|
domain->add_translation(tr);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -207,6 +207,10 @@ void EditorFileDialog::_update_theme_item_cache() {
|
||||||
|
|
||||||
void EditorFileDialog::_notification(int p_what) {
|
void EditorFileDialog::_notification(int p_what) {
|
||||||
switch (p_what) {
|
switch (p_what) {
|
||||||
|
case NOTIFICATION_POSTINITIALIZE: {
|
||||||
|
set_translation_domain(SNAME("godot.editor"));
|
||||||
|
} break;
|
||||||
|
|
||||||
case NOTIFICATION_THEME_CHANGED:
|
case NOTIFICATION_THEME_CHANGED:
|
||||||
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
|
case Control::NOTIFICATION_LAYOUT_DIRECTION_CHANGED:
|
||||||
case NOTIFICATION_TRANSLATION_CHANGED: {
|
case NOTIFICATION_TRANSLATION_CHANGED: {
|
||||||
|
|
|
@ -1081,6 +1081,8 @@ void ProjectManager::_titlebar_resized() {
|
||||||
ProjectManager::ProjectManager() {
|
ProjectManager::ProjectManager() {
|
||||||
singleton = this;
|
singleton = this;
|
||||||
|
|
||||||
|
set_translation_domain("godot.editor");
|
||||||
|
|
||||||
// Turn off some servers we aren't going to be using in the Project Manager.
|
// Turn off some servers we aren't going to be using in the Project Manager.
|
||||||
NavigationServer3D::get_singleton()->set_active(false);
|
NavigationServer3D::get_singleton()->set_active(false);
|
||||||
PhysicsServer3D::get_singleton()->set_active(false);
|
PhysicsServer3D::get_singleton()->set_active(false);
|
||||||
|
|
|
@ -111,6 +111,7 @@ void Node::_notification(int p_notification) {
|
||||||
data.auto_translate_mode = AUTO_TRANSLATE_MODE_ALWAYS;
|
data.auto_translate_mode = AUTO_TRANSLATE_MODE_ALWAYS;
|
||||||
}
|
}
|
||||||
data.is_auto_translate_dirty = true;
|
data.is_auto_translate_dirty = true;
|
||||||
|
data.is_translation_domain_dirty = true;
|
||||||
|
|
||||||
#ifdef TOOLS_ENABLED
|
#ifdef TOOLS_ENABLED
|
||||||
// Don't translate UI elements when they're being edited.
|
// 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;
|
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 {
|
StringName Node::get_name() const {
|
||||||
return data.name;
|
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("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("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_window"), &Node::get_window);
|
||||||
ClassDB::bind_method(D_METHOD("get_last_exclusive_window"), &Node::get_last_exclusive_window);
|
ClassDB::bind_method(D_METHOD("get_last_exclusive_window"), &Node::get_last_exclusive_window);
|
||||||
|
|
|
@ -255,6 +255,9 @@ private:
|
||||||
mutable bool is_auto_translating = true;
|
mutable bool is_auto_translating = true;
|
||||||
mutable bool is_auto_translate_dirty = 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;
|
mutable NodePath *path_cache = nullptr;
|
||||||
|
|
||||||
} data;
|
} data;
|
||||||
|
@ -281,6 +284,7 @@ private:
|
||||||
void _propagate_physics_interpolation_reset_requested(bool p_requested);
|
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_process_owner(Node *p_owner, int p_pause_notification, int p_enabled_notification);
|
||||||
void _propagate_groups_dirty();
|
void _propagate_groups_dirty();
|
||||||
|
void _propagate_translation_domain_dirty();
|
||||||
Array _get_node_and_resource(const NodePath &p_path);
|
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;
|
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;
|
AutoTranslateMode get_auto_translate_mode() const;
|
||||||
bool can_auto_translate() 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(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; }
|
_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; }
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue