summaryrefslogtreecommitdiff
path: root/core/translation.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'core/translation.cpp')
-rw-r--r--core/translation.cpp313
1 files changed, 70 insertions, 243 deletions
diff --git a/core/translation.cpp b/core/translation.cpp
index ea29b93319..8c8ca06740 100644
--- a/core/translation.cpp
+++ b/core/translation.cpp
@@ -42,41 +42,6 @@
// - https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
// - https://lh.2xlibre.net/locales/
-#ifdef DEBUG_TRANSLATION
-void Translation::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
-
static const char *locale_list[] = {
"aa", // Afar
"aa_DJ", // Afar (Djibouti)
@@ -830,113 +795,31 @@ static const char *locale_renames[][2] = {
///////////////////////////////////////////////
Dictionary Translation::_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;
+ for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
+ d[E->key()] = E->value();
}
-
return d;
}
-void Translation::_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> Translation::_get_message_list() const {
- ////This one I'm really not sure what the use case of this function is. So I just follow what it does before.
- // 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());
+ Vector<String> msgs;
+ msgs.resize(translation_map.size());
+ int idx = 0;
+ for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
+ msgs.set(idx, E->key());
+ idx += 1;
}
- return v;
-}
-
-int Translation::_get_plural_index(int p_n) const {
- // Apply plural rule to a p_n passed in, and get a number between [0;number of plural forms)
-
- Ref<Expression> expr;
- expr.instance();
-
- Vector<String> input_name;
- input_name.push_back("n");
-
- Array input_val;
- input_val.push_back(p_n);
-
- int result = _get_plural_index(plural_rule, input_name, input_val, expr);
- ERR_FAIL_COND_V_MSG(result < 0, 0, "_get_plural_index() returns a negative number after evaluating a plural rule expression.");
-
- return result;
+ return msgs;
}
-int Translation::_get_plural_index(const String &p_plural_rule, const Vector<String> &p_input_name, const Array &p_input_value, Ref<Expression> &r_expr) const {
- // Evaluate recursively until we find the first condition that is true.
- // 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)
- // "n != 1" (English)
-
- // Parse expression.
- int first_ques_mark = p_plural_rule.find("?");
- String equi_test = p_plural_rule.substr(0, first_ques_mark);
- Error err = r_expr->parse(equi_test, p_input_name);
- ERR_FAIL_COND_V_MSG(err != OK, p_input_value[0], "Cannot parse expression. Error: " + r_expr->get_error_text());
-
- // Evaluate expression.
- Variant result = r_expr->execute(p_input_value);
- ERR_FAIL_COND_V_MSG(r_expr->has_execute_failed(), p_input_value[0], "Cannot evaluate expression.");
-
- // Base case of recursion. Variant result will either map to a bool or an integer, in both cases returning it will give the correct plural index.
- if (first_ques_mark == -1) {
- return result;
- }
-
- if (bool(result)) {
- return p_plural_rule.substr(first_ques_mark + 1, p_plural_rule.find(":") - (first_ques_mark + 1)).to_int();
+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()];
}
-
- String after_colon = p_plural_rule.substr(p_plural_rule.find(":") + 1, p_plural_rule.length());
- return _get_plural_index(after_colon, p_input_name, p_input_value, r_expr);
}
void Translation::set_locale(const String &p_locale) {
@@ -957,125 +840,50 @@ void Translation::set_locale(const String &p_locale) {
}
}
-void Translation::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);
- // Strip away '(' and ')' to ease evaluating the expression later on.
- plural_rule = plural_rule.replacen("(", "");
- plural_rule = plural_rule.replacen(")", "");
-}
-
void Translation::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);
- }
+ translation_map[p_src_text] = p_xlated_text;
}
-void Translation::add_plural_message(const StringName &p_src_text, const Vector<String> &p_plural_texts, const StringName &p_context) {
- ERR_FAIL_COND_MSG(p_plural_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_texts.size(); i++) {
- map_id_str[p_src_text].push_back(p_plural_texts[i]);
- }
-}
-
-int Translation::get_plural_forms() const {
- return plural_forms;
-}
-
-String Translation::get_plural_rule() const {
- return plural_rule;
+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 (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
- return StringName();
+ 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");
}
- 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 check add_message() or add_plural_message() to make sure a translation is always added.");
-
- return translation_map[p_context][p_src_text][0];
-}
-StringName Translation::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, p_src_text, "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 (!translation_map.has(p_context) || !translation_map[p_context].has(p_src_text)) {
+ const Map<StringName, StringName>::Element *E = translation_map.find(p_src_text);
+ if (!E) {
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 check add_message() or add_plural_message() to make sure a translation is always added.");
- // Return based on English plural rule if locale's plural rule is not registered (normally due to missing or invalid "Plural-Forms" in PO file header).
- if (plural_forms <= 0) {
- if (p_n == 1) {
- return p_src_text;
- } else {
- return p_plural_text;
- }
- }
+ return E->get();
+}
- return translation_map[p_context][p_src_text][_get_plural_index(p_n)];
+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 (!translation_map.has(p_context)) {
- return;
+ 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[p_context].erase(p_src_text);
+ translation_map.erase(p_src_text);
}
void Translation::get_message_list(List<StringName> *r_messages) const {
- ////This is the function that PHashTranslation uses to get the list of msgid.
- ////Right now I just return the msgid list under "" context, and make no changes to PHashTranslation at all.
- ////So PHashTranslation will be functioning like last time, it will not handle context and plurals translation.
-
- // 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());
- }
+ for (const Map<StringName, StringName>::Element *E = translation_map.front(); E; E = E->next()) {
+ r_messages->push_back(E->key());
}
}
int Translation::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;
+ return translation_map.size();
}
void Translation::_bind_methods() {
@@ -1088,8 +896,6 @@ void Translation::_bind_methods() {
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("get_plural_forms"), &Translation::get_plural_forms);
- ClassDB::bind_method(D_METHOD("get_plural_rule"), &Translation::get_plural_rule);
ClassDB::bind_method(D_METHOD("_set_messages"), &Translation::_set_messages);
ClassDB::bind_method(D_METHOD("_get_messages"), &Translation::_get_messages);
@@ -1227,6 +1033,30 @@ 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();
}
@@ -1240,10 +1070,10 @@ StringName TranslationServer::translate(const StringName &p_message, const Strin
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);
+ 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);
+ res = _get_message_from_translations(p_message, p_context, fallback, false);
}
if (!res) {
@@ -1257,31 +1087,29 @@ StringName TranslationServer::translate_plural(const StringName &p_message, cons
if (!enabled) {
if (p_n == 1) {
return p_message;
- } else {
- return p_message_plural;
}
+ 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, p_message_plural, p_n);
+ 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, p_message_plural, p_n);
+ 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;
- } else {
- return p_message_plural;
}
+ return p_message_plural;
}
return res;
}
-StringName TranslationServer::_get_message_from_translations(const StringName &p_message, const StringName &p_context, const String &p_locale, const String &p_message_plural, int p_n) const {
+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
@@ -1312,7 +1140,7 @@ StringName TranslationServer::_get_message_from_translations(const StringName &p
}
StringName r;
- if (p_n == -1) {
+ 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);
@@ -1406,9 +1234,8 @@ StringName TranslationServer::tool_translate_plural(const StringName &p_message,
if (p_n == 1) {
return p_message;
- } else {
- return p_message_plural;
}
+ return p_message_plural;
}
void TranslationServer::set_doc_translation(const Ref<Translation> &p_translation) {
@@ -1435,9 +1262,8 @@ StringName TranslationServer::doc_translate_plural(const StringName &p_message,
if (p_n == 1) {
return p_message;
- } else {
- return p_message_plural;
}
+ return p_message_plural;
}
void TranslationServer::_bind_methods() {
@@ -1446,11 +1272,12 @@ void TranslationServer::_bind_methods() {
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);