Make `--doctool` locale aware

* Adds `indent(str)` to `String`:
    * Indent the (multiline) string with the given indentation.
    * This method is added in order to keep the translated XML correctly
      indented.
* Moves the loading of tool/doc translation into
  `editor/editor_translation.{h,cpp}`.
    * This will be used from both `EditorSettings` and the doc tool from
      `main`.
* Makes use of doc translation when generating XML class references, and
  setup the translation locale based on `-l LOCALE` CLI parameter.

The XML class reference won't be translated if `-l LOCALE` parameter is
not given, or when it's `-l en`.
This commit is contained in:
Haoyu Qiu 2021-12-15 23:01:06 +08:00
parent edd3ca4501
commit e4e4e475f8
10 changed files with 238 additions and 63 deletions

View File

@ -3507,6 +3507,27 @@ char32_t String::unicode_at(int p_idx) const {
return operator[](p_idx); return operator[](p_idx);
} }
String String::indent(const String &p_prefix) const {
String new_string;
int line_start = 0;
for (int i = 0; i < length(); i++) {
const char32_t c = operator[](i);
if (c == '\n') {
if (i == line_start) {
new_string += c; // Leave empty lines empty.
} else {
new_string += p_prefix + substr(line_start, i - line_start + 1);
}
line_start = i + 1;
}
}
if (line_start != length()) {
new_string += p_prefix + substr(line_start);
}
return new_string;
}
String String::dedent() const { String String::dedent() const {
String new_string; String new_string;
String indent; String indent;

View File

@ -356,6 +356,7 @@ public:
String left(int p_pos) const; String left(int p_pos) const;
String right(int p_pos) const; String right(int p_pos) const;
String indent(const String &p_prefix) const;
String dedent() const; String dedent() const;
String strip_edges(bool left = true, bool right = true) const; String strip_edges(bool left = true, bool right = true) const;
String strip_escapes() const; String strip_escapes() const;

View File

@ -1411,6 +1411,7 @@ static void _register_variant_builtin_methods() {
bind_method(String, get_basename, sarray(), varray()); bind_method(String, get_basename, sarray(), varray());
bind_method(String, plus_file, sarray("file"), varray()); bind_method(String, plus_file, sarray("file"), varray());
bind_method(String, unicode_at, sarray("at"), varray()); bind_method(String, unicode_at, sarray("at"), varray());
bind_method(String, indent, sarray("prefix"), varray());
bind_method(String, dedent, sarray(), varray()); bind_method(String, dedent, sarray(), varray());
bind_method(String, hash, sarray(), varray()); bind_method(String, hash, sarray(), varray());
bind_method(String, md5_text, sarray(), varray()); bind_method(String, md5_text, sarray(), varray());

View File

@ -124,7 +124,7 @@
<method name="dedent" qualifiers="const"> <method name="dedent" qualifiers="const">
<return type="String" /> <return type="String" />
<description> <description>
Returns a copy of the string with indentation (leading tabs and spaces) removed. Returns a copy of the string with indentation (leading tabs and spaces) removed. See also [method indent] to add indentation.
</description> </description>
</method> </method>
<method name="ends_with" qualifiers="const"> <method name="ends_with" qualifiers="const">
@ -243,6 +243,15 @@
<description> <description>
</description> </description>
</method> </method>
<method name="indent" qualifiers="const">
<return type="String" />
<argument index="0" name="prefix" type="String" />
<description>
Returns a copy of the string with lines indented with [code]prefix[/code].
For example, the string can be indented with two tabs using [code]"\t\t"[/code], or four spaces using [code]" "[/code]. The prefix can be any string so it can also be used to comment out strings with e.g. [code]"# "[/code]. See also [method dedent] to remove indentation.
[b]Note:[/b] Empty lines are kept empty.
</description>
</method>
<method name="insert" qualifiers="const"> <method name="insert" qualifiers="const">
<return type="String" /> <return type="String" />
<argument index="0" name="position" type="int" /> <argument index="0" name="position" type="int" />

View File

@ -37,12 +37,42 @@
#include "core/io/dir_access.h" #include "core/io/dir_access.h"
#include "core/io/marshalls.h" #include "core/io/marshalls.h"
#include "core/object/script_language.h" #include "core/object/script_language.h"
#include "core/string/translation.h"
#include "core/version.h" #include "core/version.h"
#include "scene/resources/theme.h" #include "scene/resources/theme.h"
// Used for a hack preserving Mono properties on non-Mono builds. // Used for a hack preserving Mono properties on non-Mono builds.
#include "modules/modules_enabled.gen.h" // For mono. #include "modules/modules_enabled.gen.h" // For mono.
static String _get_indent(const String &p_text) {
String indent;
bool has_text = false;
int line_start = 0;
for (int i = 0; i < p_text.length(); i++) {
const char32_t c = p_text[i];
if (c == '\n') {
line_start = i + 1;
} else if (c > 32) {
has_text = true;
indent = p_text.substr(line_start, i - line_start);
break; // Indentation of the first line that has text.
}
}
if (!has_text) {
return p_text;
}
return indent;
}
static String _translate_doc_string(const String &p_text) {
const String indent = _get_indent(p_text);
const String message = p_text.dedent().strip_edges();
const String translated = TranslationServer::get_singleton()->doc_translate(message, "");
// No need to restore stripped edges because they'll be stripped again later.
return translated.indent(indent);
}
void DocTools::merge_from(const DocTools &p_data) { void DocTools::merge_from(const DocTools &p_data) {
for (KeyValue<String, DocData::ClassDoc> &E : class_list) { for (KeyValue<String, DocData::ClassDoc> &E : class_list) {
DocData::ClassDoc &c = E.value; DocData::ClassDoc &c = E.value;
@ -1289,7 +1319,7 @@ static void _write_method_doc(FileAccess *f, const String &p_name, Vector<DocDat
} }
_write_string(f, 3, "<description>"); _write_string(f, 3, "<description>");
_write_string(f, 4, m.description.strip_edges().xml_escape()); _write_string(f, 4, _translate_doc_string(m.description).strip_edges().xml_escape());
_write_string(f, 3, "</description>"); _write_string(f, 3, "</description>");
_write_string(f, 2, "</" + p_name + ">"); _write_string(f, 2, "</" + p_name + ">");
@ -1327,11 +1357,11 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
_write_string(f, 0, header); _write_string(f, 0, header);
_write_string(f, 1, "<brief_description>"); _write_string(f, 1, "<brief_description>");
_write_string(f, 2, c.brief_description.strip_edges().xml_escape()); _write_string(f, 2, _translate_doc_string(c.brief_description).strip_edges().xml_escape());
_write_string(f, 1, "</brief_description>"); _write_string(f, 1, "</brief_description>");
_write_string(f, 1, "<description>"); _write_string(f, 1, "<description>");
_write_string(f, 2, c.description.strip_edges().xml_escape()); _write_string(f, 2, _translate_doc_string(c.description).strip_edges().xml_escape());
_write_string(f, 1, "</description>"); _write_string(f, 1, "</description>");
_write_string(f, 1, "<tutorials>"); _write_string(f, 1, "<tutorials>");
@ -1366,7 +1396,7 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
_write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\" overrides=\"" + p.overrides + "\"" + additional_attributes + " />"); _write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\" overrides=\"" + p.overrides + "\"" + additional_attributes + " />");
} else { } else {
_write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\"" + additional_attributes + ">"); _write_string(f, 2, "<member name=\"" + p.name + "\" type=\"" + p.type + "\" setter=\"" + p.setter + "\" getter=\"" + p.getter + "\"" + additional_attributes + ">");
_write_string(f, 3, p.description.strip_edges().xml_escape()); _write_string(f, 3, _translate_doc_string(p.description).strip_edges().xml_escape());
_write_string(f, 2, "</member>"); _write_string(f, 2, "</member>");
} }
} }
@ -1392,7 +1422,7 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
_write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\">"); _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\">");
} }
} }
_write_string(f, 3, k.description.strip_edges().xml_escape()); _write_string(f, 3, _translate_doc_string(k.description).strip_edges().xml_escape());
_write_string(f, 2, "</constant>"); _write_string(f, 2, "</constant>");
} }
@ -1412,7 +1442,7 @@ Error DocTools::save_classes(const String &p_default_path, const Map<String, Str
_write_string(f, 2, "<theme_item name=\"" + ti.name + "\" data_type=\"" + ti.data_type + "\" type=\"" + ti.type + "\">"); _write_string(f, 2, "<theme_item name=\"" + ti.name + "\" data_type=\"" + ti.data_type + "\" type=\"" + ti.type + "\">");
} }
_write_string(f, 3, ti.description.strip_edges().xml_escape()); _write_string(f, 3, _translate_doc_string(ti.description).strip_edges().xml_escape());
_write_string(f, 2, "</theme_item>"); _write_string(f, 2, "</theme_item>");
} }

View File

@ -33,21 +33,17 @@
#include "core/config/project_settings.h" #include "core/config/project_settings.h"
#include "core/input/input_map.h" #include "core/input/input_map.h"
#include "core/io/certs_compressed.gen.h" #include "core/io/certs_compressed.gen.h"
#include "core/io/compression.h"
#include "core/io/config_file.h" #include "core/io/config_file.h"
#include "core/io/dir_access.h" #include "core/io/dir_access.h"
#include "core/io/file_access.h" #include "core/io/file_access.h"
#include "core/io/file_access_memory.h"
#include "core/io/ip.h" #include "core/io/ip.h"
#include "core/io/resource_loader.h" #include "core/io/resource_loader.h"
#include "core/io/resource_saver.h" #include "core/io/resource_saver.h"
#include "core/io/translation_loader_po.h"
#include "core/os/keyboard.h" #include "core/os/keyboard.h"
#include "core/os/os.h" #include "core/os/os.h"
#include "core/version.h" #include "core/version.h"
#include "editor/doc_translations.gen.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_translations.gen.h" #include "editor/editor_translation.h"
#include "scene/main/node.h" #include "scene/main/node.h"
#include "scene/main/scene_tree.h" #include "scene/main/scene_tree.h"
#include "scene/main/window.h" #include "scene/main/window.h"
@ -369,16 +365,11 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
} }
String best; String best;
EditorTranslationList *etl = _editor_translations; for (const String &locale : get_editor_locales()) {
while (etl->data) {
const String &locale = etl->lang;
// Skip locales which we can't render properly (see above comment). // Skip locales which we can't render properly (see above comment).
// Test against language code without regional variants (e.g. ur_PK). // Test against language code without regional variants (e.g. ur_PK).
String lang_code = locale.get_slice("_", 0); String lang_code = locale.get_slice("_", 0);
if (locales_to_skip.find(lang_code) != -1) { if (locales_to_skip.find(lang_code) != -1) {
etl++;
continue; continue;
} }
@ -392,8 +383,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
if (best.is_empty() && host_lang.begins_with(locale)) { if (best.is_empty() && host_lang.begins_with(locale)) {
best = locale; best = locale;
} }
etl++;
} }
if (best.is_empty()) { if (best.is_empty()) {
@ -922,50 +911,10 @@ void EditorSettings::setup_language() {
return; // Default, nothing to do. return; // Default, nothing to do.
} }
// Load editor translation for configured/detected locale. // Load editor translation for configured/detected locale.
EditorTranslationList *etl = _editor_translations; load_editor_translations(lang);
while (etl->data) {
if (etl->lang == lang) {
Vector<uint8_t> data;
data.resize(etl->uncomp_size);
Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE);
FileAccessMemory *fa = memnew(FileAccessMemory);
fa->open_custom(data.ptr(), data.size());
Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);
if (tr.is_valid()) {
tr->set_locale(etl->lang);
TranslationServer::get_singleton()->set_tool_translation(tr);
break;
}
}
etl++;
}
// Load class reference translation. // Load class reference translation.
DocTranslationList *dtl = _doc_translations; load_doc_translations(lang);
while (dtl->data) {
if (dtl->lang == lang) {
Vector<uint8_t> data;
data.resize(dtl->uncomp_size);
Compression::decompress(data.ptrw(), dtl->uncomp_size, dtl->data, dtl->comp_size, Compression::MODE_DEFLATE);
FileAccessMemory *fa = memnew(FileAccessMemory);
fa->open_custom(data.ptr(), data.size());
Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);
if (tr.is_valid()) {
tr->set_locale(dtl->lang);
TranslationServer::get_singleton()->set_doc_translation(tr);
break;
}
}
dtl++;
}
} }
void EditorSettings::setup_network() { void EditorSettings::setup_network() {

View File

@ -0,0 +1,99 @@
/*************************************************************************/
/* editor_translation.cpp */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 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 "editor/editor_translation.h"
#include "core/io/compression.h"
#include "core/io/file_access_memory.h"
#include "core/io/translation_loader_po.h"
#include "editor/doc_translations.gen.h"
#include "editor/editor_translations.gen.h"
Vector<String> get_editor_locales() {
Vector<String> locales;
EditorTranslationList *etl = _editor_translations;
while (etl->data) {
const String &locale = etl->lang;
locales.push_back(locale);
etl++;
}
return locales;
}
void load_editor_translations(const String &p_locale) {
EditorTranslationList *etl = _editor_translations;
while (etl->data) {
if (etl->lang == p_locale) {
Vector<uint8_t> data;
data.resize(etl->uncomp_size);
Compression::decompress(data.ptrw(), etl->uncomp_size, etl->data, etl->comp_size, Compression::MODE_DEFLATE);
FileAccessMemory *fa = memnew(FileAccessMemory);
fa->open_custom(data.ptr(), data.size());
Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);
if (tr.is_valid()) {
tr->set_locale(etl->lang);
TranslationServer::get_singleton()->set_tool_translation(tr);
break;
}
}
etl++;
}
}
void load_doc_translations(const String &p_locale) {
DocTranslationList *dtl = _doc_translations;
while (dtl->data) {
if (dtl->lang == p_locale) {
Vector<uint8_t> data;
data.resize(dtl->uncomp_size);
Compression::decompress(data.ptrw(), dtl->uncomp_size, dtl->data, dtl->comp_size, Compression::MODE_DEFLATE);
FileAccessMemory *fa = memnew(FileAccessMemory);
fa->open_custom(data.ptr(), data.size());
Ref<Translation> tr = TranslationLoaderPO::load_translation(fa);
if (tr.is_valid()) {
tr->set_locale(dtl->lang);
TranslationServer::get_singleton()->set_doc_translation(tr);
break;
}
}
dtl++;
}
}

View File

@ -0,0 +1,41 @@
/*************************************************************************/
/* editor_translation.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/*************************************************************************/
/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */
/* Copyright (c) 2014-2021 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 EDITOR_TRANSLATION_H
#define EDITOR_TRANSLATION_H
#include "core/string/ustring.h"
#include "core/templates/vector.h"
Vector<String> get_editor_locales();
void load_editor_translations(const String &p_locale);
void load_doc_translations(const String &p_locale);
#endif // EDITOR_TRANSLATION_H

View File

@ -83,6 +83,7 @@
#include "editor/doc_tools.h" #include "editor/doc_tools.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_settings.h" #include "editor/editor_settings.h"
#include "editor/editor_translation.h"
#include "editor/progress_dialog.h" #include "editor/progress_dialog.h"
#include "editor/project_manager.h" #include "editor/project_manager.h"
#ifndef NO_EDITOR_SPLASH #ifndef NO_EDITOR_SPLASH
@ -1937,7 +1938,6 @@ Error Main::setup2(Thread::ID p_main_tid_override) {
} }
_start_success = true; _start_success = true;
locale = String();
ClassDB::set_current_api(ClassDB::API_NONE); //no more APIs are registered at this point ClassDB::set_current_api(ClassDB::API_NONE); //no more APIs are registered at this point
@ -2049,6 +2049,11 @@ bool Main::start() {
// Needed to instance editor-only classes for their default values // Needed to instance editor-only classes for their default values
Engine::get_singleton()->set_editor_hint(true); Engine::get_singleton()->set_editor_hint(true);
// Translate the class reference only when `-l LOCALE` parameter is given.
if (!locale.is_empty() && locale != "en") {
load_doc_translations(locale);
}
{ {
DirAccessRef da = DirAccess::open(doc_tool_path); DirAccessRef da = DirAccess::open(doc_tool_path);
ERR_FAIL_COND_V_MSG(!da, false, "Argument supplied to --doctool must be a valid directory path."); ERR_FAIL_COND_V_MSG(!da, false, "Argument supplied to --doctool must be a valid directory path.");

View File

@ -1132,6 +1132,25 @@ TEST_CASE("[String] c-escape/unescape") {
CHECK(s.c_escape().c_unescape() == s); CHECK(s.c_escape().c_unescape() == s);
} }
TEST_CASE("[String] indent") {
static const char *input[] = {
"",
"aaa\nbbb",
"\tcontains\n\tindent",
"empty\n\nline",
};
static const char *expected[] = {
"",
"\taaa\n\tbbb",
"\t\tcontains\n\t\tindent",
"\tempty\n\n\tline",
};
for (int i = 0; i < 3; i++) {
CHECK(String(input[i]).indent("\t") == expected[i]);
}
}
TEST_CASE("[String] dedent") { TEST_CASE("[String] dedent") {
String s = " aaa\n bbb"; String s = " aaa\n bbb";
String t = "aaa\nbbb"; String t = "aaa\nbbb";