summaryrefslogtreecommitdiff
path: root/core
diff options
context:
space:
mode:
Diffstat (limited to 'core')
-rw-r--r--core/compressed_translation.cpp11
-rw-r--r--core/compressed_translation.h3
-rw-r--r--core/io/translation_loader_po.cpp110
-rw-r--r--core/object.cpp17
-rw-r--r--core/object.h3
-rw-r--r--core/translation.cpp217
-rw-r--r--core/translation.h32
-rw-r--r--core/translation_po.cpp312
-rw-r--r--core/translation_po.h92
-rw-r--r--core/ustring.cpp58
-rw-r--r--core/ustring.h11
11 files changed, 755 insertions, 111 deletions
diff --git a/core/compressed_translation.cpp b/core/compressed_translation.cpp
index a66997aa52..a92275565d 100644
--- a/core/compressed_translation.cpp
+++ b/core/compressed_translation.cpp
@@ -43,6 +43,8 @@ struct _PHashTranslationCmp {
};
void PHashTranslation::generate(const Ref<Translation> &p_from) {
+ // This method compresses a Translation instance.
+ // Right now it doesn't handle context or plurals, so Translation subclasses using plurals or context (i.e TranslationPO) shouldn't be compressed.
#ifdef TOOLS_ENABLED
List<StringName> keys;
p_from->get_message_list(&keys);
@@ -212,7 +214,9 @@ bool PHashTranslation::_get(const StringName &p_name, Variant &r_ret) const {
return true;
}
-StringName PHashTranslation::get_message(const StringName &p_src_text) const {
+StringName PHashTranslation::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ // p_context passed in is ignore. The use of context is not yet supported in PHashTranslation.
+
int htsize = hash_table.size();
if (htsize == 0) {
@@ -267,6 +271,11 @@ StringName PHashTranslation::get_message(const StringName &p_src_text) const {
}
}
+StringName PHashTranslation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ // The use of plurals translation is not yet supported in PHashTranslation.
+ return get_message(p_src_text, p_context);
+}
+
void PHashTranslation::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "hash_table"));
p_list->push_back(PropertyInfo(Variant::PACKED_INT32_ARRAY, "bucket_table"));
diff --git a/core/compressed_translation.h b/core/compressed_translation.h
index 4f9c422e1e..c8b3cd2330 100644
--- a/core/compressed_translation.h
+++ b/core/compressed_translation.h
@@ -79,7 +79,8 @@ protected:
static void _bind_methods();
public:
- virtual StringName get_message(const StringName &p_src_text) const override; //overridable for other implementations
+ virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override; //overridable for other implementations
+ virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override;
void generate(const Ref<Translation> &p_from);
PHashTranslation() {}
diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp
index 11aeddee09..d8ddb213c3 100644
--- a/core/io/translation_loader_po.cpp
+++ b/core/io/translation_loader_po.cpp
@@ -32,26 +32,34 @@
#include "core/os/file_access.h"
#include "core/translation.h"
+#include "core/translation_po.h"
RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) {
enum Status {
STATUS_NONE,
STATUS_READING_ID,
STATUS_READING_STRING,
+ STATUS_READING_CONTEXT,
+ STATUS_READING_PLURAL,
};
Status status = STATUS_NONE;
String msg_id;
String msg_str;
+ String msg_context;
+ Vector<String> msgs_plural;
String config;
if (r_error) {
*r_error = ERR_FILE_CORRUPT;
}
- Ref<Translation> translation = Ref<Translation>(memnew(Translation));
+ Ref<TranslationPO> translation = Ref<TranslationPO>(memnew(TranslationPO));
int line = 1;
+ int plural_forms = 0;
+ int plural_index = -1;
+ bool entered_context = false;
bool skip_this = false;
bool skip_next = false;
bool is_eof = false;
@@ -63,40 +71,107 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) {
// If we reached last line and it's not a content line, break, otherwise let processing that last loop
if (is_eof && l.empty()) {
- if (status == STATUS_READING_ID) {
+ if (status == STATUS_READING_ID || status == STATUS_READING_CONTEXT || (status == STATUS_READING_PLURAL && plural_index != plural_forms - 1)) {
memdelete(f);
- ERR_FAIL_V_MSG(RES(), "Unexpected EOF while reading 'msgid' at: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(RES(), "Unexpected EOF while reading PO file at: " + path + ":" + itos(line));
} else {
break;
}
}
- if (l.begins_with("msgid")) {
+ if (l.begins_with("msgctxt")) {
+ if (status != STATUS_READING_STRING && status != STATUS_READING_PLURAL) {
+ memdelete(f);
+ ERR_FAIL_V_MSG(RES(), "Unexpected 'msgctxt', was expecting 'msgid_plural' or 'msgstr' before 'msgctxt' while parsing: " + path + ":" + itos(line));
+ }
+
+ // In PO file, "msgctxt" appears before "msgid". If we encounter a "msgctxt", we add what we have read
+ // and set "entered_context" to true to prevent adding twice.
+ if (!skip_this && msg_id != "") {
+ if (status == STATUS_READING_STRING) {
+ translation->add_message(msg_id, msg_str, msg_context);
+ } else if (status == STATUS_READING_PLURAL) {
+ if (plural_index != plural_forms - 1) {
+ memdelete(f);
+ ERR_FAIL_V_MSG(RES(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ }
+ translation->add_plural_message(msg_id, msgs_plural, msg_context);
+ }
+ }
+ msg_context = "";
+ l = l.substr(7, l.length()).strip_edges();
+ status = STATUS_READING_CONTEXT;
+ entered_context = true;
+ }
+
+ if (l.begins_with("msgid_plural")) {
+ if (plural_forms == 0) {
+ memdelete(f);
+ ERR_FAIL_V_MSG(RES(), "PO file uses 'msgid_plural' but 'Plural-Forms' is invalid or missing in header: " + path + ":" + itos(line));
+ } else if (status != STATUS_READING_ID) {
+ memdelete(f);
+ ERR_FAIL_V_MSG(RES(), "Unexpected 'msgid_plural', was expecting 'msgid' before 'msgid_plural' while parsing: " + path + ":" + itos(line));
+ }
+ // We don't record the message in "msgid_plural" itself as tr_n(), TTRN(), RTRN() interfaces provide the plural string already.
+ // We just have to reset variables related to plurals for "msgstr[]" later on.
+ l = l.substr(12, l.length()).strip_edges();
+ plural_index = -1;
+ msgs_plural.clear();
+ msgs_plural.resize(plural_forms);
+ status = STATUS_READING_PLURAL;
+ } else if (l.begins_with("msgid")) {
if (status == STATUS_READING_ID) {
memdelete(f);
ERR_FAIL_V_MSG(RES(), "Unexpected 'msgid', was expecting 'msgstr' while parsing: " + path + ":" + itos(line));
}
if (msg_id != "") {
- if (!skip_this) {
- translation->add_message(msg_id, msg_str);
+ if (!skip_this && !entered_context) {
+ if (status == STATUS_READING_STRING) {
+ translation->add_message(msg_id, msg_str, msg_context);
+ } else if (status == STATUS_READING_PLURAL) {
+ if (plural_index != plural_forms - 1) {
+ memdelete(f);
+ ERR_FAIL_V_MSG(RES(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ }
+ translation->add_plural_message(msg_id, msgs_plural, msg_context);
+ }
}
} else if (config == "") {
config = msg_str;
+ // Record plural rule.
+ int p_start = config.find("Plural-Forms");
+ if (p_start != -1) {
+ int p_end = config.find("\n", p_start);
+ translation->set_plural_rule(config.substr(p_start, p_end - p_start));
+ plural_forms = translation->get_plural_forms();
+ }
}
l = l.substr(5, l.length()).strip_edges();
status = STATUS_READING_ID;
+ // If we did not encounter msgctxt, we reset context to empty to reset it.
+ if (!entered_context) {
+ msg_context = "";
+ }
msg_id = "";
msg_str = "";
skip_this = skip_next;
skip_next = false;
+ entered_context = false;
}
- if (l.begins_with("msgstr")) {
+ if (l.begins_with("msgstr[")) {
+ if (status != STATUS_READING_PLURAL) {
+ memdelete(f);
+ ERR_FAIL_V_MSG(RES(), "Unexpected 'msgstr[]', was expecting 'msgid_plural' before 'msgstr[]' while parsing: " + path + ":" + itos(line));
+ }
+ plural_index++; // Increment to add to the next slot in vector msgs_plural.
+ l = l.substr(9, l.length()).strip_edges();
+ } else if (l.begins_with("msgstr")) {
if (status != STATUS_READING_ID) {
memdelete(f);
- ERR_FAIL_V_MSG(RES(), "Unexpected 'msgstr', was expecting 'msgid' while parsing: " + path + ":" + itos(line));
+ ERR_FAIL_V_MSG(RES(), "Unexpected 'msgstr', was expecting 'msgid' before 'msgstr' while parsing: " + path + ":" + itos(line));
}
l = l.substr(6, l.length()).strip_edges();
@@ -108,7 +183,7 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) {
skip_next = true;
}
line++;
- continue; //nothing to read or comment
+ continue; // Nothing to read or comment.
}
if (!l.begins_with("\"") || status == STATUS_NONE) {
@@ -146,8 +221,12 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) {
if (status == STATUS_READING_ID) {
msg_id += l;
- } else {
+ } else if (status == STATUS_READING_STRING) {
msg_str += l;
+ } else if (status == STATUS_READING_CONTEXT) {
+ msg_context += l;
+ } else if (status == STATUS_READING_PLURAL && plural_index >= 0) {
+ msgs_plural.write[plural_index] = msgs_plural[plural_index] + l;
}
line++;
@@ -155,14 +234,23 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) {
memdelete(f);
+ // Add the last set of data from last iteration.
if (status == STATUS_READING_STRING) {
if (msg_id != "") {
if (!skip_this) {
- translation->add_message(msg_id, msg_str);
+ translation->add_message(msg_id, msg_str, msg_context);
}
} else if (config == "") {
config = msg_str;
}
+ } else if (status == STATUS_READING_PLURAL) {
+ if (!skip_this && msg_id != "") {
+ if (plural_index != plural_forms - 1) {
+ memdelete(f);
+ ERR_FAIL_V_MSG(RES(), "Number of 'msgstr[]' doesn't match with number of plural forms: " + path + ":" + itos(line));
+ }
+ translation->add_plural_message(msg_id, msgs_plural, msg_context);
+ }
}
ERR_FAIL_COND_V_MSG(config == "", RES(), "No config found in file: " + path + ".");
diff --git a/core/object.cpp b/core/object.cpp
index ff6d4a666f..67c605c39b 100644
--- a/core/object.cpp
+++ b/core/object.cpp
@@ -1432,12 +1432,22 @@ void Object::initialize_class() {
initialized = true;
}
-StringName Object::tr(const StringName &p_message) const {
+String Object::tr(const StringName &p_message, const StringName &p_context) const {
if (!_can_translate || !TranslationServer::get_singleton()) {
return p_message;
}
+ return TranslationServer::get_singleton()->translate(p_message, p_context);
+}
- return TranslationServer::get_singleton()->translate(p_message);
+String Object::tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (!_can_translate || !TranslationServer::get_singleton()) {
+ // Return message based on English plural rule if translation is not possible.
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+ 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) {
@@ -1578,7 +1588,8 @@ void Object::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_message_translation", "enable"), &Object::set_message_translation);
ClassDB::bind_method(D_METHOD("can_translate_messages"), &Object::can_translate_messages);
- ClassDB::bind_method(D_METHOD("tr", "message"), &Object::tr);
+ ClassDB::bind_method(D_METHOD("tr", "message", "context"), &Object::tr, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("tr_n", "message", "plural_message", "n", "context"), &Object::tr_n, DEFVAL(""));
ClassDB::bind_method(D_METHOD("is_queued_for_deletion"), &Object::is_queued_for_deletion);
diff --git a/core/object.h b/core/object.h
index d9847d10aa..f9a12da8f6 100644
--- a/core/object.h
+++ b/core/object.h
@@ -719,7 +719,8 @@ public:
virtual void get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const;
- StringName tr(const StringName &p_message) const; // translate message (internationalization)
+ String tr(const StringName &p_message, const StringName &p_context = "") const; // translate message (internationalization)
+ String tr_n(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
bool _is_queued_for_deletion = false; // set to true by SceneTree::queue_delete()
bool is_queued_for_deletion() const;
diff --git a/core/translation.cpp b/core/translation.cpp
index 4f835bd7b4..8c8ca06740 100644
--- a/core/translation.cpp
+++ b/core/translation.cpp
@@ -794,17 +794,12 @@ static const char *locale_renames[][2] = {
///////////////////////////////////////////////
-Vector<String> Translation::_get_messages() const {
- Vector<String> msgs;
- msgs.resize(translation_map.size() * 2);
- int idx = 0;
+Dictionary Translation::_get_messages() const {
+ Dictionary d;
for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
- msgs.set(idx + 0, E->key());
- msgs.set(idx + 1, E->get());
- idx += 2;
+ d[E->key()] = E->value();
}
-
- return msgs;
+ return d;
}
Vector<String> Translation::_get_message_list() const {
@@ -819,14 +814,11 @@ Vector<String> Translation::_get_message_list() const {
return msgs;
}
-void Translation::_set_messages(const Vector<String> &p_messages) {
- int msg_count = p_messages.size();
- ERR_FAIL_COND(msg_count % 2);
-
- const String *r = p_messages.ptr();
-
- for (int i = 0; i < msg_count; i += 2) {
- add_message(r[i + 0], r[i + 1]);
+void Translation::_set_messages(const Dictionary &p_messages) {
+ List<Variant> keys;
+ p_messages.get_key_list(&keys);
+ for (auto E = keys.front(); E; E = E->next()) {
+ translation_map[E->get()] = p_messages[E->get()];
}
}
@@ -848,11 +840,21 @@ void Translation::set_locale(const String &p_locale) {
}
}
-void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text) {
+void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) {
translation_map[p_src_text] = p_xlated_text;
}
-StringName Translation::get_message(const StringName &p_src_text) const {
+void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
+ WARN_PRINT("Translation class doesn't handle plural messages. Calling add_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
+ ERR_FAIL_COND_MSG(p_plural_xlated_texts.empty(), "Parameter vector p_plural_xlated_texts passed in is empty.");
+ translation_map[p_src_text] = p_plural_xlated_texts[0];
+}
+
+StringName Translation::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ if (p_context != StringName()) {
+ WARN_PRINT("Translation class doesn't handle context. Using context in get_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
+ }
+
const Map<StringName, StringName>::Element *E = translation_map.find(p_src_text);
if (!E) {
return StringName();
@@ -861,7 +863,16 @@ StringName Translation::get_message(const StringName &p_src_text) const {
return E->get();
}
-void Translation::erase_message(const StringName &p_src_text) {
+StringName Translation::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ WARN_PRINT("Translation class doesn't handle plural messages. Calling get_plural_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles plurals, such as TranslationPO class");
+ return get_message(p_src_text);
+}
+
+void Translation::erase_message(const StringName &p_src_text, const StringName &p_context) {
+ if (p_context != StringName()) {
+ WARN_PRINT("Translation class doesn't handle context. Using context in erase_message() on a Translation instance is probably a mistake. \nUse a derived Translation class that handles context, such as TranslationPO class");
+ }
+
translation_map.erase(p_src_text);
}
@@ -878,15 +889,17 @@ int Translation::get_message_count() const {
void Translation::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &Translation::set_locale);
ClassDB::bind_method(D_METHOD("get_locale"), &Translation::get_locale);
- ClassDB::bind_method(D_METHOD("add_message", "src_message", "xlated_message"), &Translation::add_message);
- ClassDB::bind_method(D_METHOD("get_message", "src_message"), &Translation::get_message);
- ClassDB::bind_method(D_METHOD("erase_message", "src_message"), &Translation::erase_message);
+ ClassDB::bind_method(D_METHOD("add_message", "src_message", "xlated_message", "context"), &Translation::add_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("add_plural_message", "src_message", "xlated_messages", "context"), &Translation::add_plural_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_message", "src_message", "context"), &Translation::get_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("get_plural_message", "src_message", "src_plural_message", "n", "context"), &Translation::get_plural_message, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("erase_message", "src_message", "context"), &Translation::erase_message, DEFVAL(""));
ClassDB::bind_method(D_METHOD("get_message_list"), &Translation::_get_message_list);
ClassDB::bind_method(D_METHOD("get_message_count"), &Translation::get_message_count);
ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages);
ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages);
- ADD_PROPERTY(PropertyInfo(Variant::PACKED_STRING_ARRAY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
+ ADD_PROPERTY(PropertyInfo(Variant::DICTIONARY, "messages", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NOEDITOR | PROPERTY_USAGE_INTERNAL), "_set_messages", "_get_messages");
ADD_PROPERTY(PropertyInfo(Variant::STRING, "locale"), "set_locale", "get_locale");
}
@@ -1020,11 +1033,35 @@ void TranslationServer::remove_translation(const Ref<Translation> &p_translation
translations.erase(p_translation);
}
+Ref<Translation> TranslationServer::get_translation_object(const String &p_locale) {
+ Ref<Translation> res;
+ String lang = get_language_code(p_locale);
+ bool near_match_found = false;
+
+ for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
+ const Ref<Translation> &t = E->get();
+ ERR_FAIL_COND_V(t.is_null(), nullptr);
+ String l = t->get_locale();
+
+ // Exact match.
+ if (l == p_locale) {
+ return t;
+ }
+
+ // If near match found, keep that match, but keep looking to try to look for perfect match.
+ if (get_language_code(l) == lang && !near_match_found) {
+ res = t;
+ near_match_found = true;
+ }
+ }
+ return res;
+}
+
void TranslationServer::clear() {
translations.clear();
}
-StringName TranslationServer::translate(const StringName &p_message) 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) {
@@ -1033,6 +1070,46 @@ StringName TranslationServer::translate(const StringName &p_message) const {
ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid.");
+ 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 p_message;
+ }
+
+ return res;
+}
+
+StringName TranslationServer::translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (!enabled) {
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+ }
+
+ ERR_FAIL_COND_V_MSG(locale.length() < 2, p_message, "Could not translate message as configured locale '" + locale + "' is invalid.");
+
+ 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 {
// Locale can be of the form 'll_CC', i.e. language code and regional code,
// e.g. 'en_US', 'en_GB', etc. It might also be simply 'll', e.g. 'en'.
// To find the relevant translation, we look for those with locale starting
@@ -1044,7 +1121,7 @@ StringName TranslationServer::translate(const StringName &p_message) const {
// logic, so be sure to propagate changes there when changing things here.
StringName res;
- String lang = get_language_code(locale);
+ String lang = get_language_code(p_locale);
bool near_match = false;
for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
@@ -1052,7 +1129,7 @@ StringName TranslationServer::translate(const StringName &p_message) const {
ERR_FAIL_COND_V(t.is_null(), p_message);
String l = t->get_locale();
- bool exact_match = (l == locale);
+ bool exact_match = (l == p_locale);
if (!exact_match) {
if (near_match) {
continue; // Only near-match once, but keep looking for exact matches.
@@ -1062,7 +1139,13 @@ StringName TranslationServer::translate(const StringName &p_message) const {
}
}
- StringName r = t->get_message(p_message);
+ 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;
}
@@ -1075,44 +1158,6 @@ StringName TranslationServer::translate(const StringName &p_message) const {
}
}
- if (!res && fallback.length() >= 2) {
- // Try again with the fallback locale.
- String fallback_lang = get_language_code(fallback);
- near_match = false;
-
- for (const Set<Ref<Translation>>::Element *E = translations.front(); E; E = E->next()) {
- const Ref<Translation> &t = E->get();
- ERR_FAIL_COND_V(t.is_null(), p_message);
- String l = t->get_locale();
-
- bool exact_match = (l == fallback);
- if (!exact_match) {
- if (near_match) {
- continue; // Only near-match once, but keep looking for exact matches.
- }
- if (get_language_code(l) != fallback_lang) {
- continue; // Language code does not match.
- }
- }
-
- StringName r = t->get_message(p_message);
- if (!r) {
- continue;
- }
- res = r;
-
- if (exact_match) {
- break;
- } else {
- near_match = true;
- }
- }
- }
-
- if (!res) {
- return p_message;
- }
-
return res;
}
@@ -1169,9 +1214,9 @@ void TranslationServer::set_tool_translation(const Ref<Translation> &p_translati
tool_translation = p_translation;
}
-StringName TranslationServer::tool_translate(const StringName &p_message) const {
+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);
+ StringName r = tool_translation->get_message(p_message, p_context);
if (r) {
return r;
}
@@ -1179,13 +1224,27 @@ StringName TranslationServer::tool_translate(const StringName &p_message) const
return p_message;
}
+StringName TranslationServer::tool_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (tool_translation.is_valid()) {
+ StringName r = tool_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
doc_translation = p_translation;
}
-StringName TranslationServer::doc_translate(const StringName &p_message) const {
+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);
+ StringName r = doc_translation->get_message(p_message, p_context);
if (r) {
return r;
}
@@ -1193,16 +1252,32 @@ StringName TranslationServer::doc_translate(const StringName &p_message) const {
return p_message;
}
+StringName TranslationServer::doc_translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context) const {
+ if (doc_translation.is_valid()) {
+ StringName r = doc_translation->get_plural_message(p_message, p_message_plural, p_n, p_context);
+ if (r) {
+ return r;
+ }
+ }
+
+ if (p_n == 1) {
+ return p_message;
+ }
+ return p_message_plural;
+}
+
void TranslationServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_locale", "locale"), &TranslationServer::set_locale);
ClassDB::bind_method(D_METHOD("get_locale"), &TranslationServer::get_locale);
ClassDB::bind_method(D_METHOD("get_locale_name", "locale"), &TranslationServer::get_locale_name);
- ClassDB::bind_method(D_METHOD("translate", "message"), &TranslationServer::translate);
+ ClassDB::bind_method(D_METHOD("translate", "message", "context"), &TranslationServer::translate, DEFVAL(""));
+ ClassDB::bind_method(D_METHOD("translate_plural", "message", "plural_message", "n", "context"), &TranslationServer::translate_plural, DEFVAL(""));
ClassDB::bind_method(D_METHOD("add_translation", "translation"), &TranslationServer::add_translation);
ClassDB::bind_method(D_METHOD("remove_translation", "translation"), &TranslationServer::remove_translation);
+ ClassDB::bind_method(D_METHOD("get_translation_object", "locale"), &TranslationServer::get_translation_object);
ClassDB::bind_method(D_METHOD("clear"), &TranslationServer::clear);
diff --git a/core/translation.h b/core/translation.h
index 4f50a1a4bc..cba25a434f 100644
--- a/core/translation.h
+++ b/core/translation.h
@@ -41,10 +41,9 @@ class Translation : public Resource {
String locale = "en";
Map<StringName, StringName> translation_map;
- Vector<String> _get_message_list() const;
-
- Vector<String> _get_messages() const;
- void _set_messages(const Vector<String> &p_messages);
+ virtual Vector<String> _get_message_list() const;
+ virtual Dictionary _get_messages() const;
+ virtual void _set_messages(const Dictionary &p_messages);
protected:
static void _bind_methods();
@@ -53,12 +52,13 @@ public:
void set_locale(const String &p_locale);
_FORCE_INLINE_ String get_locale() const { return locale; }
- void add_message(const StringName &p_src_text, const StringName &p_xlated_text);
- virtual StringName get_message(const StringName &p_src_text) const; //overridable for other implementations
- void erase_message(const StringName &p_src_text);
-
- void get_message_list(List<StringName> *r_messages) const;
- int get_message_count() const;
+ virtual void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "");
+ virtual void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = "");
+ virtual StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const; //overridable for other implementations
+ virtual StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const;
+ virtual void erase_message(const StringName &p_src_text, const StringName &p_context = "");
+ virtual void get_message_list(List<StringName> *r_messages) const;
+ virtual int get_message_count() const;
Translation() {}
};
@@ -80,6 +80,8 @@ class TranslationServer : public Object {
static TranslationServer *singleton;
bool _load_translations(const String &p_from);
+ 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();
public:
@@ -90,6 +92,7 @@ public:
void set_locale(const String &p_locale);
String get_locale() const;
+ Ref<Translation> get_translation_object(const String &p_locale);
String get_locale_name(const String &p_locale) const;
@@ -98,7 +101,8 @@ public:
void add_translation(const Ref<Translation> &p_translation);
void remove_translation(const Ref<Translation> &p_translation);
- StringName translate(const StringName &p_message) const;
+ StringName translate(const StringName &p_message, const StringName &p_context = "") const;
+ StringName translate_plural(const StringName &p_message, const StringName &p_message_plural, int p_n, const StringName &p_context = "") const;
static Vector<String> get_all_locales();
static Vector<String> get_all_locale_names();
@@ -107,9 +111,11 @@ public:
static String get_language_code(const String &p_locale);
void set_tool_translation(const Ref<Translation> &p_translation);
- StringName tool_translate(const StringName &p_message) 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_doc_translation(const Ref<Translation> &p_translation);
- StringName doc_translate(const StringName &p_message) 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;
void setup();
diff --git a/core/translation_po.cpp b/core/translation_po.cpp
new file mode 100644
index 0000000000..203f29026b
--- /dev/null
+++ b/core/translation_po.cpp
@@ -0,0 +1,312 @@
+/*************************************************************************/
+/* translation_po.cpp */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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_po.h"
+
+#include "core/os/file_access.h"
+
+#ifdef DEBUG_TRANSLATION_PO
+void TranslationPO::print_translation_map() {
+ Error err;
+ FileAccess *file = FileAccess::open("translation_map_print_test.txt", FileAccess::WRITE, &err);
+ if (err != OK) {
+ ERR_PRINT("Failed to open translation_map_print_test.txt");
+ return;
+ }
+
+ file->store_line("NPlural : " + String::num_int64(this->get_plural_forms()));
+ file->store_line("Plural rule : " + this->get_plural_rule());
+ file->store_line("");
+
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+ for (auto E = context_l.front(); E; E = E->next()) {
+ StringName ctx = E->get();
+ file->store_line(" ===== Context: " + String::utf8(String(ctx).utf8()) + " ===== ");
+ const HashMap<StringName, Vector<StringName>> &inner_map = translation_map[ctx];
+
+ List<StringName> id_l;
+ inner_map.get_key_list(&id_l);
+ for (auto E2 = id_l.front(); E2; E2 = E2->next()) {
+ StringName id = E2->get();
+ file->store_line("msgid: " + String::utf8(String(id).utf8()));
+ for (int i = 0; i < inner_map[id].size(); i++) {
+ file->store_line("msgstr[" + String::num_int64(i) + "]: " + String::utf8(String(inner_map[id][i]).utf8()));
+ }
+ file->store_line("");
+ }
+ }
+ file->close();
+}
+#endif
+
+Dictionary TranslationPO::_get_messages() const {
+ // Return translation_map as a Dictionary.
+
+ Dictionary d;
+
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+ for (auto E = context_l.front(); E; E = E->next()) {
+ StringName ctx = E->get();
+ const HashMap<StringName, Vector<StringName>> &id_str_map = translation_map[ctx];
+
+ Dictionary d2;
+ List<StringName> id_l;
+ id_str_map.get_key_list(&id_l);
+ // Save list of id and strs associated with a context in a temporary dictionary.
+ for (auto E2 = id_l.front(); E2; E2 = E2->next()) {
+ StringName id = E2->get();
+ d2[id] = id_str_map[id];
+ }
+
+ d[ctx] = d2;
+ }
+
+ return d;
+}
+
+void TranslationPO::_set_messages(const Dictionary &p_messages) {
+ // Construct translation_map from a Dictionary.
+
+ List<Variant> context_l;
+ p_messages.get_key_list(&context_l);
+ for (auto E = context_l.front(); E; E = E->next()) {
+ StringName ctx = E->get();
+ const Dictionary &id_str_map = p_messages[ctx];
+
+ HashMap<StringName, Vector<StringName>> temp_map;
+ List<Variant> id_l;
+ id_str_map.get_key_list(&id_l);
+ for (auto E2 = id_l.front(); E2; E2 = E2->next()) {
+ StringName id = E2->get();
+ temp_map[id] = id_str_map[id];
+ }
+
+ translation_map[ctx] = temp_map;
+ }
+}
+
+Vector<String> TranslationPO::_get_message_list() const {
+ // Return all keys in translation_map.
+
+ List<StringName> msgs;
+ get_message_list(&msgs);
+
+ Vector<String> v;
+ for (auto E = msgs.front(); E; E = E->next()) {
+ v.push_back(E->get());
+ }
+
+ return v;
+}
+
+int TranslationPO::_get_plural_index(int p_n) const {
+ // Get a number between [0;number of plural forms).
+
+ input_val.clear();
+ input_val.push_back(p_n);
+
+ Variant result;
+ for (int i = 0; i < equi_tests.size(); i++) {
+ Error err = expr->parse(equi_tests[i], input_name);
+ ERR_FAIL_COND_V_MSG(err != OK, 0, "Cannot parse expression. Error: " + expr->get_error_text());
+
+ result = expr->execute(input_val);
+ ERR_FAIL_COND_V_MSG(expr->has_execute_failed(), 0, "Cannot evaluate expression.");
+
+ // Last expression. Variant result will either map to a bool or an integer, in both cases returning it will give the correct plural index.
+ if (i + 1 == equi_tests.size()) {
+ return result;
+ }
+
+ if (bool(result)) {
+ return i;
+ }
+ }
+
+ ERR_FAIL_V_MSG(0, "Unexpected. Function should have returned. Please report this bug.");
+}
+
+void TranslationPO::_cache_plural_tests(const String &p_plural_rule) {
+ // Some examples of p_plural_rule passed in can have the form:
+ // "n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5" (Arabic)
+ // "n >= 2" (French) // When evaluating the last, esp careful with this one.
+ // "n != 1" (English)
+ int first_ques_mark = p_plural_rule.find("?");
+ if (first_ques_mark == -1) {
+ equi_tests.push_back(p_plural_rule.strip_edges());
+ return;
+ }
+
+ String equi_test = p_plural_rule.substr(0, first_ques_mark).strip_edges();
+ equi_tests.push_back(equi_test);
+
+ String after_colon = p_plural_rule.substr(p_plural_rule.find(":") + 1, p_plural_rule.length());
+ _cache_plural_tests(after_colon);
+}
+
+void TranslationPO::set_plural_rule(const String &p_plural_rule) {
+ // Set plural_forms and plural_rule.
+ // p_plural_rule passed in has the form "Plural-Forms: nplurals=2; plural=(n >= 2);".
+
+ int first_semi_col = p_plural_rule.find(";");
+ plural_forms = p_plural_rule.substr(p_plural_rule.find("=") + 1, first_semi_col - (p_plural_rule.find("=") + 1)).to_int();
+
+ int expression_start = p_plural_rule.find("=", first_semi_col) + 1;
+ int second_semi_col = p_plural_rule.rfind(";");
+ plural_rule = p_plural_rule.substr(expression_start, second_semi_col - expression_start);
+
+ // Setup the cache to make evaluating plural rule faster later on.
+ plural_rule = plural_rule.replacen("(", "");
+ plural_rule = plural_rule.replacen(")", "");
+ _cache_plural_tests(plural_rule);
+ expr.instance();
+ input_name.push_back("n");
+}
+
+void TranslationPO::add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) {
+ HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
+
+ if (map_id_str.has(p_src_text)) {
+ WARN_PRINT("Double translations for \"" + String(p_src_text) + "\" under the same context \"" + String(p_context) + "\" for locale \"" + get_locale() + "\".\nThere should only be one unique translation for a given string under the same context.");
+ map_id_str[p_src_text].set(0, p_xlated_text);
+ } else {
+ map_id_str[p_src_text].push_back(p_xlated_text);
+ }
+}
+
+void TranslationPO::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context) {
+ ERR_FAIL_COND_MSG(p_plural_xlated_texts.size() != plural_forms, "Trying to add plural texts that don't match the required number of plural forms for locale \"" + get_locale() + "\"");
+
+ HashMap<StringName, Vector<StringName>> &map_id_str = translation_map[p_context];
+
+ if (map_id_str.has(p_src_text)) {
+ WARN_PRINT("Double translations for \"" + p_src_text + "\" under the same context \"" + p_context + "\" for locale " + get_locale() + ".\nThere should only be one unique translation for a given string under the same context.");
+ map_id_str[p_src_text].clear();
+ }
+
+ for (int i = 0; i < p_plural_xlated_texts.size(); i++) {
+ map_id_str[p_src_text].push_back(p_plural_xlated_texts[i]);
+ }
+}
+
+int TranslationPO::get_plural_forms() const {
+ return plural_forms;
+}
+
+String TranslationPO::get_plural_rule() const {
+ return plural_rule;
+}
+
+StringName TranslationPO::get_message(const StringName &p_src_text, const StringName &p_context) const {
+ if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
+ return StringName();
+ }
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+
+ return translation_map[p_context][p_src_text][0];
+}
+
+StringName TranslationPO::get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context) const {
+ ERR_FAIL_COND_V_MSG(p_n < 0, StringName(), "N passed into translation to get a plural message should not be negative. For negative numbers, use singular translation please. Search \"gettext PO Plural Forms\" online for the documentation on translating negative numbers.");
+
+ // If the query is the same as last time, return the cached result.
+ if (p_n == last_plural_n && p_context == last_plural_context && p_src_text == last_plural_key) {
+ return translation_map[p_context][p_src_text][last_plural_mapped_index];
+ }
+
+ if (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
+ return StringName();
+ }
+ ERR_FAIL_COND_V_MSG(translation_map[p_context][p_src_text].empty(), StringName(), "Source text \"" + String(p_src_text) + "\" is registered but doesn't have a translation. Please report this bug.");
+
+ if (translation_map[p_context][p_src_text].size() == 1) {
+ WARN_PRINT("Source string \"" + String(p_src_text) + "\" doesn't have plural translations. Use singular translation API for such as tr(), TTR() to translate \"" + String(p_src_text) + "\"");
+ return translation_map[p_context][p_src_text][0];
+ }
+
+ int plural_index = _get_plural_index(p_n);
+ ERR_FAIL_COND_V_MSG(plural_index < 0 || translation_map[p_context][p_src_text].size() < plural_index + 1, StringName(), "Plural index returned or number of plural translations is not valid. Please report this bug.");
+
+ // Cache result so that if the next entry is the same, we can return directly.
+ // _get_plural_index(p_n) can get very costly, especially when evaluating long plural-rule (Arabic)
+ last_plural_key = p_src_text;
+ last_plural_context = p_context;
+ last_plural_n = p_n;
+ last_plural_mapped_index = plural_index;
+
+ return translation_map[p_context][p_src_text][plural_index];
+}
+
+void TranslationPO::erase_message(const StringName &p_src_text, const StringName &p_context) {
+ if (!translation_map.has(p_context)) {
+ return;
+ }
+
+ translation_map[p_context].erase(p_src_text);
+}
+
+void TranslationPO::get_message_list(List<StringName> *r_messages) const {
+ // PHashTranslation uses this function to get the list of msgid.
+ // Return all the keys of translation_map under "" context.
+
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+
+ for (auto E = context_l.front(); E; E = E->next()) {
+ if (String(E->get()) != "") {
+ continue;
+ }
+
+ List<StringName> msgid_l;
+ translation_map[E->get()].get_key_list(&msgid_l);
+
+ for (auto E2 = msgid_l.front(); E2; E2 = E2->next()) {
+ r_messages->push_back(E2->get());
+ }
+ }
+}
+
+int TranslationPO::get_message_count() const {
+ List<StringName> context_l;
+ translation_map.get_key_list(&context_l);
+
+ int count = 0;
+ for (auto E = context_l.front(); E; E = E->next()) {
+ count += translation_map[E->get()].size();
+ }
+ return count;
+}
+
+void TranslationPO::_bind_methods() {
+ ClassDB::bind_method(D_METHOD("get_plural_forms"), &TranslationPO::get_plural_forms);
+ ClassDB::bind_method(D_METHOD("get_plural_rule"), &TranslationPO::get_plural_rule);
+}
diff --git a/core/translation_po.h b/core/translation_po.h
new file mode 100644
index 0000000000..730635f63d
--- /dev/null
+++ b/core/translation_po.h
@@ -0,0 +1,92 @@
+/*************************************************************************/
+/* translation_po.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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_PO_H
+#define TRANSLATION_PO_H
+
+//#define DEBUG_TRANSLATION_PO
+
+#include "core/math/expression.h"
+#include "core/translation.h"
+
+class TranslationPO : public Translation {
+ GDCLASS(TranslationPO, Translation);
+
+ // TLDR: Maps context to a list of source strings and translated strings. In PO terms, maps msgctxt to a list of msgid and msgstr.
+ // The first key corresponds to context, and the second key (of the contained HashMap) corresponds to source string.
+ // The value Vector<StringName> in the second map stores the translated strings. Index 0, 1, 2 matches msgstr[0], msgstr[1], msgstr[2]... in the case of plurals.
+ // Otherwise index 0 mathes to msgstr in a singular translation.
+ // Strings without context have "" as first key.
+ HashMap<StringName, HashMap<StringName, Vector<StringName>>> translation_map;
+
+ int plural_forms = 0; // 0 means no "Plural-Forms" is given in the PO header file. The min for all languages is 1.
+ String plural_rule;
+
+ // Cache temporary variables related to _get_plural_index() to make it faster
+ Vector<String> equi_tests;
+ Vector<String> input_name;
+ mutable Ref<Expression> expr;
+ mutable Array input_val;
+ mutable StringName last_plural_key;
+ mutable StringName last_plural_context;
+ mutable int last_plural_n = -1; // Set it to an impossible value at the beginning.
+ mutable int last_plural_mapped_index = 0;
+
+ void _cache_plural_tests(const String &p_plural_rule);
+ int _get_plural_index(int p_n) const;
+
+ Vector<String> _get_message_list() const override;
+ Dictionary _get_messages() const override;
+ void _set_messages(const Dictionary &p_messages) override;
+
+protected:
+ static void _bind_methods();
+
+public:
+ void get_message_list(List<StringName> *r_messages) const override;
+ int get_message_count() const override;
+ void add_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context = "") override;
+ void add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_xlated_texts, const StringName &p_context = "") override;
+ StringName get_message(const StringName &p_src_text, const StringName &p_context = "") const override;
+ StringName get_plural_message(const StringName &p_src_text, const StringName &p_plural_text, int p_n, const StringName &p_context = "") const override;
+ void erase_message(const StringName &p_src_text, const StringName &p_context = "") override;
+
+ void set_plural_rule(const String &p_plural_rule);
+ int get_plural_forms() const;
+ String get_plural_rule() const;
+
+#ifdef DEBUG_TRANSLATION_PO
+ void print_translation_map();
+#endif
+
+ TranslationPO() {}
+};
+
+#endif // TRANSLATION_PO_H
diff --git a/core/ustring.cpp b/core/ustring.cpp
index 957caf1015..9d2d938eaf 100644
--- a/core/ustring.cpp
+++ b/core/ustring.cpp
@@ -4269,31 +4269,58 @@ String String::unquote() const {
}
#ifdef TOOLS_ENABLED
-String TTR(const String &p_text) {
+String TTR(const String &p_text, const String &p_context) {
if (TranslationServer::get_singleton()) {
- return TranslationServer::get_singleton()->tool_translate(p_text);
+ return TranslationServer::get_singleton()->tool_translate(p_text, p_context);
}
return p_text;
}
-String DTR(const String &p_text) {
+String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
+ if (TranslationServer::get_singleton()) {
+ return TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
+ }
+
+ // Return message based on English plural rule if translation is not possible.
+ if (p_n == 1) {
+ return p_text;
+ }
+ return p_text_plural;
+}
+
+String DTR(const String &p_text, const String &p_context) {
// Comes straight from the XML, so remove indentation and any trailing whitespace.
const String text = p_text.dedent().strip_edges();
if (TranslationServer::get_singleton()) {
- return TranslationServer::get_singleton()->doc_translate(text);
+ return TranslationServer::get_singleton()->doc_translate(text, p_context);
}
return text;
}
+
+String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
+ const String text = p_text.dedent().strip_edges();
+ const String text_plural = p_text_plural.dedent().strip_edges();
+
+ if (TranslationServer::get_singleton()) {
+ return TranslationServer::get_singleton()->doc_translate_plural(text, text_plural, p_n, p_context);
+ }
+
+ // Return message based on English plural rule if translation is not possible.
+ if (p_n == 1) {
+ return text;
+ }
+ return text_plural;
+}
#endif
-String RTR(const String &p_text) {
+String RTR(const String &p_text, const String &p_context) {
if (TranslationServer::get_singleton()) {
- String rtr = TranslationServer::get_singleton()->tool_translate(p_text);
+ String rtr = TranslationServer::get_singleton()->tool_translate(p_text, p_context);
if (rtr == String() || rtr == p_text) {
- return TranslationServer::get_singleton()->translate(p_text);
+ return TranslationServer::get_singleton()->translate(p_text, p_context);
} else {
return rtr;
}
@@ -4301,3 +4328,20 @@ String RTR(const String &p_text) {
return p_text;
}
+
+String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context) {
+ if (TranslationServer::get_singleton()) {
+ String rtr = TranslationServer::get_singleton()->tool_translate_plural(p_text, p_text_plural, p_n, p_context);
+ if (rtr == String() || rtr == p_text || rtr == p_text_plural) {
+ return TranslationServer::get_singleton()->translate_plural(p_text, p_text_plural, p_n, p_context);
+ } else {
+ return rtr;
+ }
+ }
+
+ // Return message based on English plural rule if translation is not possible.
+ if (p_n == 1) {
+ return p_text;
+ }
+ return p_text_plural;
+}
diff --git a/core/ustring.h b/core/ustring.h
index d37346fbd6..7a1c1a5232 100644
--- a/core/ustring.h
+++ b/core/ustring.h
@@ -410,8 +410,10 @@ _FORCE_INLINE_ bool is_str_less(const L *l_ptr, const R *r_ptr) {
// and doc translate for the class reference (DTR).
#ifdef TOOLS_ENABLED
// Gets parsed.
-String TTR(const String &);
-String DTR(const String &);
+String TTR(const String &p_text, const String &p_context = "");
+String TTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
+String DTR(const String &p_text, const String &p_context = "");
+String DTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
// Use for C strings.
#define TTRC(m_value) (m_value)
// Use to avoid parsing (for use later with C strings).
@@ -419,13 +421,16 @@ String DTR(const String &);
#else
#define TTR(m_value) (String())
+#define TTRN(m_value) (String())
#define DTR(m_value) (String())
+#define DTRN(m_value) (String())
#define TTRC(m_value) (m_value)
#define TTRGET(m_value) (m_value)
#endif
// Runtime translate for the public node API.
-String RTR(const String &);
+String RTR(const String &p_text, const String &p_context = "");
+String RTRN(const String &p_text, const String &p_text_plural, int p_n, const String &p_context = "");
bool is_symbol(CharType c);
bool select_word(const String &p_s, int p_col, int &r_beg, int &r_end);