From 7df893ab6b650ecdc278768517791073589b42d7 Mon Sep 17 00:00:00 2001 From: Relintai Date: Mon, 2 Oct 2023 20:48:04 +0200 Subject: [PATCH] Ported from godot: Add context support for editor translation - timothyqiu https://github.com/godotengine/godot/commit/878cf8262a94367f4ffb7c706773075cd563ee38 --- core/io/translation_loader_po.cpp | 56 +++++++++++++++++++++++++------ core/io/translation_loader_po.h | 2 +- core/string/translation.cpp | 45 +++++++++++++++++++++++-- core/string/translation.h | 16 ++++++++- core/string/ustring.cpp | 6 ++-- core/string/ustring.h | 2 +- editor/editor_translation.cpp | 4 +-- 7 files changed, 110 insertions(+), 21 deletions(-) diff --git a/core/io/translation_loader_po.cpp b/core/io/translation_loader_po.cpp index fc42dae91..4b2a10bc0 100644 --- a/core/io/translation_loader_po.cpp +++ b/core/io/translation_loader_po.cpp @@ -33,25 +33,34 @@ #include "core/os/file_access.h" #include "core/string/translation.h" -RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { +RES TranslationLoaderPO::load_translation(FileAccess *f, bool p_use_context, Error *r_error) { enum Status { STATUS_NONE, STATUS_READING_ID, STATUS_READING_STRING, + STATUS_READING_CONTEXT, }; Status status = STATUS_NONE; String msg_id; String msg_str; + String msg_context; String config; if (r_error) { *r_error = ERR_FILE_CORRUPT; } - Ref translation = Ref(memnew(Translation)); + Ref translation; + if (p_use_context) { + translation = Ref(memnew(ContextTranslation)); + } else { + translation.instance(); + } + int line = 1; + bool entered_context = false; bool skip_this = false; bool skip_next = false; bool is_eof = false; @@ -63,14 +72,31 @@ 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) { 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("msgctxt")) { + if (status != STATUS_READING_STRING) { + memdelete(f); + ERR_FAIL_V_MSG(RES(), "Unexpected 'msgctxt', was expecting '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 != "") { + translation->add_context_message(msg_id, msg_str, msg_context); + } + msg_context = ""; + l = l.substr(7, l.length()).strip_edges(); + status = STATUS_READING_CONTEXT; + entered_context = true; + } + if (l.begins_with("msgid")) { if (status == STATUS_READING_ID) { memdelete(f); @@ -78,8 +104,8 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { } if (msg_id != "") { - if (!skip_this) { - translation->add_message(msg_id, msg_str); + if (!skip_this && !entered_context) { + translation->add_context_message(msg_id, msg_str, msg_context); } } else if (config == "") { config = msg_str; @@ -87,16 +113,21 @@ RES TranslationLoaderPO::load_translation(FileAccess *f, Error *r_error) { 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 (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 +139,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 +177,10 @@ 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; } line++; @@ -155,10 +188,11 @@ 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_context_message(msg_id, msg_str, msg_context); } } else if (config == "") { config = msg_str; @@ -197,7 +231,7 @@ RES TranslationLoaderPO::load(const String &p_path, const String &p_original_pat FileAccess *f = FileAccess::open(p_path, FileAccess::READ); ERR_FAIL_COND_V_MSG(!f, RES(), "Cannot open file '" + p_path + "'."); - return load_translation(f, r_error); + return load_translation(f, false, r_error); } void TranslationLoaderPO::get_recognized_extensions(List *p_extensions) const { diff --git a/core/io/translation_loader_po.h b/core/io/translation_loader_po.h index 3a8a5fe87..13d1273ab 100644 --- a/core/io/translation_loader_po.h +++ b/core/io/translation_loader_po.h @@ -36,7 +36,7 @@ class TranslationLoaderPO : public ResourceFormatLoader { public: - static RES load_translation(FileAccess *f, Error *r_error = nullptr); + static RES load_translation(FileAccess *f, bool p_use_context, Error *r_error = nullptr); virtual RES load(const String &p_path, const String &p_original_path = "", Error *r_error = NULL, bool p_no_subresource_cache = false); virtual void get_recognized_extensions(List *p_extensions) const; virtual bool handles_type(const String &p_type) const; diff --git a/core/string/translation.cpp b/core/string/translation.cpp index cdb1856f5..a7fee0d09 100644 --- a/core/string/translation.cpp +++ b/core/string/translation.cpp @@ -870,9 +870,24 @@ void Translation::set_locale(const String &p_locale) { } } +void Translation::add_context_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) { + if (p_context != StringName()) { + WARN_PRINT("Translation class doesn't handle context."); + } + add_message(p_src_text, p_xlated_text); +} + +StringName Translation::get_context_message(const StringName &p_src_text, const StringName &p_context) const { + if (p_context != StringName()) { + WARN_PRINT("Translation class doesn't handle context."); + } + return get_message(p_src_text); +} + void Translation::add_message(const StringName &p_src_text, const StringName &p_xlated_text) { translation_map[p_src_text] = p_xlated_text; } + StringName Translation::get_message(const StringName &p_src_text) const { if (get_script_instance()) { return get_script_instance()->call("_get_message", p_src_text); @@ -923,6 +938,32 @@ Translation::Translation() : /////////////////////////////////////////////// +void ContextTranslation::add_context_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context) { + if (p_context == StringName()) { + add_message(p_src_text, p_xlated_text); + } else { + context_translation_map[p_context][p_src_text] = p_xlated_text; + } +} + +StringName ContextTranslation::get_context_message(const StringName &p_src_text, const StringName &p_context) const { + if (p_context == StringName()) { + return get_message(p_src_text); + } + + const RBMap>::Element *context = context_translation_map.find(p_context); + if (!context) { + return StringName(); + } + const RBMap::Element *message = context->get().find(p_src_text); + if (!message) { + return StringName(); + } + return message->get(); +} + +/////////////////////////////////////////////// + bool TranslationServer::is_locale_valid(const String &p_locale) { const char **ptr = locale_list; @@ -1258,9 +1299,9 @@ void TranslationServer::set_tool_translation(const Ref &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_context_message(p_message, p_context); if (r) { return r; } diff --git a/core/string/translation.h b/core/string/translation.h index 042a9dda7..d39944f60 100644 --- a/core/string/translation.h +++ b/core/string/translation.h @@ -59,9 +59,23 @@ public: void get_message_list(List *r_messages) const; int get_message_count() const; + // Not exposed to scripting. For easy usage of `ContextTranslation`. + virtual void add_context_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context); + virtual StringName get_context_message(const StringName &p_src_text, const StringName &p_context) const; + Translation(); }; +class ContextTranslation : public Translation { + GDCLASS(ContextTranslation, Translation); + + RBMap> context_translation_map; + +public: + virtual void add_context_message(const StringName &p_src_text, const StringName &p_xlated_text, const StringName &p_context); + virtual StringName get_context_message(const StringName &p_src_text, const StringName &p_context) const; +}; + class TranslationServer : public Object { GDCLASS(TranslationServer, Object); @@ -107,7 +121,7 @@ public: static String get_language_code(const String &p_locale); void set_tool_translation(const Ref &p_translation); - StringName tool_translate(const StringName &p_message) const; + StringName tool_translate(const StringName &p_message, const StringName &p_context) const; void set_doc_translation(const Ref &p_translation); StringName doc_translate(const StringName &p_message) const; diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 87d86b6d1..177f52c3a 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -5774,9 +5774,9 @@ String rtoss(double p_val) { } #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; @@ -5800,7 +5800,7 @@ String DTR(const String &p_text) { String RTR(const String &p_text) { if (TranslationServer::get_singleton()) { - String rtr = TranslationServer::get_singleton()->tool_translate(p_text); + String rtr = TranslationServer::get_singleton()->tool_translate(p_text, StringName()); if (rtr == String() || rtr == p_text) { return TranslationServer::get_singleton()->translate(p_text); } else { diff --git a/core/string/ustring.h b/core/string/ustring.h index 19a8120a2..88b1428b8 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -615,7 +615,7 @@ _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 TTR(const String &p_text, const String &p_context = ""); String DTR(const String &); // Use for C strings. #define TTRC(m_value) (m_value) diff --git a/editor/editor_translation.cpp b/editor/editor_translation.cpp index 381cd4b53..49ef1f4d2 100644 --- a/editor/editor_translation.cpp +++ b/editor/editor_translation.cpp @@ -66,7 +66,7 @@ void load_editor_translations(const String &p_locale) { FileAccessMemory *fa = memnew(FileAccessMemory); fa->open_custom(data.ptr(), data.size()); - Ref tr = TranslationLoaderPO::load_translation(fa); + Ref tr = TranslationLoaderPO::load_translation(fa, true); if (tr.is_valid()) { tr->set_locale(etl->lang); @@ -91,7 +91,7 @@ void load_doc_translations(const String &p_locale) { FileAccessMemory *fa = memnew(FileAccessMemory); fa->open_custom(data.ptr(), data.size()); - Ref tr = TranslationLoaderPO::load_translation(fa); + Ref tr = TranslationLoaderPO::load_translation(fa, true); if (tr.is_valid()) { tr->set_locale(dtl->lang);