Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

POT Generator: Add support for TRANSLATORS: and NO_TRANSLATE comments #98099

Merged
merged 1 commit into from
Nov 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions doc/classes/EditorTranslationParserPlugin.xml
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,14 @@
<tutorials>
</tutorials>
<methods>
<method name="_get_comments" qualifiers="virtual">
<return type="void" />
<param index="0" name="msgids_comment" type="String[]" />
<param index="1" name="msgids_context_plural_comment" type="String[]" />
<description>
If overridden, called after [method _parse_file] to get comments for the parsed entries. This method should fill the arrays with the same number of elements and in the same order as [method _parse_file].
</description>
</method>
<method name="_get_recognized_extensions" qualifiers="virtual const">
<return type="PackedStringArray" />
<description>
Expand Down
17 changes: 16 additions & 1 deletion editor/editor_translation_parser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@
#include "editor_translation_parser.h"

#include "core/error/error_macros.h"
#include "core/io/file_access.h"
#include "core/object/script_language.h"
#include "core/templates/hash_set.h"

Expand Down Expand Up @@ -65,6 +64,21 @@ Error EditorTranslationParserPlugin::parse_file(const String &p_path, Vector<Str
}
}

void EditorTranslationParserPlugin::get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) {
TypedArray<String> ids_comment;
TypedArray<String> ids_ctx_plural_comment;

if (GDVIRTUAL_CALL(_get_comments, ids_comment, ids_ctx_plural_comment)) {
for (int i = 0; i < ids_comment.size(); i++) {
r_ids_comment->append(ids_comment[i]);
}

for (int i = 0; i < ids_ctx_plural_comment.size(); i++) {
r_ids_ctx_plural_comment->append(ids_ctx_plural_comment[i]);
}
}
}

void EditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_extensions) const {
Vector<String> extensions;
if (GDVIRTUAL_CALL(_get_recognized_extensions, extensions)) {
Expand All @@ -78,6 +92,7 @@ void EditorTranslationParserPlugin::get_recognized_extensions(List<String> *r_ex

void EditorTranslationParserPlugin::_bind_methods() {
GDVIRTUAL_BIND(_parse_file, "path", "msgids", "msgids_context_plural");
GDVIRTUAL_BIND(_get_comments, "msgids_comment", "msgids_context_plural_comment");
GDVIRTUAL_BIND(_get_recognized_extensions);
}

Expand Down
2 changes: 2 additions & 0 deletions editor/editor_translation_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ class EditorTranslationParserPlugin : public RefCounted {
static void _bind_methods();

GDVIRTUAL3(_parse_file, String, TypedArray<String>, TypedArray<Array>)
GDVIRTUAL2(_get_comments, TypedArray<String>, TypedArray<String>)
GDVIRTUAL0RC(Vector<String>, _get_recognized_extensions)

public:
virtual Error parse_file(const String &p_path, Vector<String> *r_ids, Vector<Vector<String>> *r_ids_ctx_plural);
virtual void get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment);
virtual void get_recognized_extensions(List<String> *r_extensions) const;
};

Expand Down
48 changes: 37 additions & 11 deletions editor/pot_generator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,28 +69,35 @@ void POTGenerator::generate_pot(const String &p_file) {
for (int i = 0; i < files.size(); i++) {
Vector<String> msgids;
Vector<Vector<String>> msgids_context_plural;

Vector<String> msgids_comment;
Vector<String> msgids_context_plural_comment;

const String &file_path = files[i];
String file_extension = file_path.get_extension();

if (EditorTranslationParser::get_singleton()->can_parse(file_extension)) {
EditorTranslationParser::get_singleton()->get_parser(file_extension)->parse_file(file_path, &msgids, &msgids_context_plural);
EditorTranslationParser::get_singleton()->get_parser(file_extension)->get_comments(&msgids_comment, &msgids_context_plural_comment);
} else {
ERR_PRINT("Unrecognized file extension " + file_extension + " in generate_pot()");
return;
}

for (int j = 0; j < msgids_context_plural.size(); j++) {
const Vector<String> &entry = msgids_context_plural[j];
_add_new_msgid(entry[0], entry[1], entry[2], file_path);
const String &comment = (j < msgids_context_plural_comment.size()) ? msgids_context_plural_comment[j] : String();
_add_new_msgid(entry[0], entry[1], entry[2], file_path, comment);
}
for (int j = 0; j < msgids.size(); j++) {
_add_new_msgid(msgids[j], "", "", file_path);
const String &comment = (j < msgids_comment.size()) ? msgids_comment[j] : String();
_add_new_msgid(msgids[j], "", "", file_path, comment);
}
}

if (GLOBAL_GET("internationalization/locale/translation_add_builtin_strings_to_pot")) {
for (const Vector<String> &extractable_msgids : get_extractable_message_list()) {
_add_new_msgid(extractable_msgids[0], extractable_msgids[1], extractable_msgids[2], "");
_add_new_msgid(extractable_msgids[0], extractable_msgids[1], extractable_msgids[2], "", "");
}
}

Expand Down Expand Up @@ -136,15 +143,25 @@ void POTGenerator::_write_to_pot(const String &p_file) {
String context = v_msgid_data[i].ctx;
String plural = v_msgid_data[i].plural;
const HashSet<String> &locations = v_msgid_data[i].locations;
const HashSet<String> &comments = v_msgid_data[i].comments;

// Put the blank line at the start, to avoid a double at the end when closing the file.
file->store_line("");

// Write comments.
bool is_first_comment = true;
for (const String &E : comments) {
if (is_first_comment) {
file->store_line("#. TRANSLATORS: " + E.replace("\n", "\n#. "));
} else {
file->store_line("#. " + E.replace("\n", "\n#. "));
}
is_first_comment = false;
}

// Write file locations.
for (const String &E : locations) {
if (!E.is_empty()) {
file->store_line("#: " + E.trim_prefix("res://").replace("\n", "\\n"));
}
file->store_line("#: " + E.trim_prefix("res://").replace("\n", "\\n"));
}

// Write context.
Expand Down Expand Up @@ -199,7 +216,7 @@ void POTGenerator::_write_msgid(Ref<FileAccess> r_file, const String &p_id, bool
}
}

void POTGenerator::_add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location) {
void POTGenerator::_add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location, const String &p_comment) {
// Insert new location if msgid under same context exists already.
if (all_translation_strings.has(p_msgid)) {
Vector<MsgidData> &v_mdata = all_translation_strings[p_msgid];
Expand All @@ -208,18 +225,27 @@ void POTGenerator::_add_new_msgid(const String &p_msgid, const String &p_context
if (!v_mdata[i].plural.is_empty() && !p_plural.is_empty() && v_mdata[i].plural != p_plural) {
WARN_PRINT("Redefinition of plural message (msgid_plural), under the same message (msgid) and context (msgctxt)");
}
v_mdata.write[i].locations.insert(p_location);
if (!p_location.is_empty()) {
v_mdata.write[i].locations.insert(p_location);
}
if (!p_comment.is_empty()) {
v_mdata.write[i].comments.insert(p_comment);
}
return;
}
}
}

// Add a new entry of msgid, context, plural and location - context and plural might be empty if the inserted msgid doesn't associated
// context or plurals.
// Add a new entry.
MsgidData mdata;
mdata.ctx = p_context;
mdata.plural = p_plural;
mdata.locations.insert(p_location);
if (!p_location.is_empty()) {
mdata.locations.insert(p_location);
}
if (!p_comment.is_empty()) {
mdata.comments.insert(p_comment);
}
all_translation_strings[p_msgid].push_back(mdata);
}

Expand Down
3 changes: 2 additions & 1 deletion editor/pot_generator.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,14 @@ class POTGenerator {
String ctx;
String plural;
HashSet<String> locations;
HashSet<String> comments;
};
// Store msgid as key and the additional data around the msgid - if it's under a context, has plurals and its file locations.
HashMap<String, Vector<MsgidData>> all_translation_strings;

void _write_to_pot(const String &p_file);
void _write_msgid(Ref<FileAccess> r_file, const String &p_id, bool p_plural);
void _add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location);
void _add_new_msgid(const String &p_msgid, const String &p_context, const String &p_plural, const String &p_location, const String &p_comment);

#ifdef DEBUG_POT
void _print_all_translation_strings();
Expand Down
94 changes: 85 additions & 9 deletions modules/gdscript/editor/gdscript_translation_parser_plugin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve

ids = r_ids;
ids_ctx_plural = r_ids_ctx_plural;

ids_comment.clear();
ids_ctx_plural_comment.clear();

Ref<GDScript> gdscript = loaded_res;
String source_code = gdscript->get_source_code();

Expand All @@ -62,18 +66,90 @@ Error GDScriptEditorTranslationParserPlugin::parse_file(const String &p_path, Ve
err = analyzer.analyze();
ERR_FAIL_COND_V_MSG(err, err, "Failed to analyze GDScript with GDScriptAnalyzer.");

comment_data = &parser.comment_data;

// Traverse through the parsed tree from GDScriptParser.
GDScriptParser::ClassNode *c = parser.get_tree();
_traverse_class(c);

comment_data = nullptr;

return OK;
}

void GDScriptEditorTranslationParserPlugin::get_comments(Vector<String> *r_ids_comment, Vector<String> *r_ids_ctx_plural_comment) {
r_ids_comment->append_array(ids_comment);
r_ids_ctx_plural_comment->append_array(ids_ctx_plural_comment);
}

bool GDScriptEditorTranslationParserPlugin::_is_constant_string(const GDScriptParser::ExpressionNode *p_expression) {
ERR_FAIL_NULL_V(p_expression, false);
return p_expression->is_constant && p_expression->reduced_value.is_string();
}

String GDScriptEditorTranslationParserPlugin::_parse_comment(int p_line, bool &r_skip) const {
// Parse inline comment.
if (comment_data->has(p_line)) {
const String stripped_comment = comment_data->get(p_line).comment.trim_prefix("#").strip_edges();

if (stripped_comment.begins_with("TRANSLATORS:")) {
return stripped_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
}
if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
r_skip = true;
return String();
}
}

// Parse multiline comment.
String multiline_comment;
for (int line = p_line - 1; comment_data->has(line) && comment_data->get(line).new_line; line--) {
const String stripped_comment = comment_data->get(line).comment.trim_prefix("#").strip_edges();

if (stripped_comment.is_empty()) {
continue;
}

if (multiline_comment.is_empty()) {
multiline_comment = stripped_comment;
} else {
multiline_comment = stripped_comment + "\n" + multiline_comment;
}

if (stripped_comment.begins_with("TRANSLATORS:")) {
return multiline_comment.trim_prefix("TRANSLATORS:").strip_edges(true, false);
}
if (stripped_comment == "NO_TRANSLATE" || stripped_comment.begins_with("NO_TRANSLATE:")) {
r_skip = true;
return String();
}
}

return String();
}

void GDScriptEditorTranslationParserPlugin::_add_id(const String &p_id, int p_line) {
bool skip = false;
const String comment = _parse_comment(p_line, skip);
if (skip) {
return;
}

ids->push_back(p_id);
ids_comment.push_back(comment);
}

void GDScriptEditorTranslationParserPlugin::_add_id_ctx_plural(const Vector<String> &p_id_ctx_plural, int p_line) {
bool skip = false;
const String comment = _parse_comment(p_line, skip);
if (skip) {
return;
}

ids_ctx_plural->push_back(p_id_ctx_plural);
ids_ctx_plural_comment.push_back(comment);
}

void GDScriptEditorTranslationParserPlugin::_traverse_class(const GDScriptParser::ClassNode *p_class) {
for (int i = 0; i < p_class->members.size(); i++) {
const GDScriptParser::ClassNode::Member &m = p_class->members[i];
Expand Down Expand Up @@ -253,7 +329,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_assignment(const GDScriptPar

if (assignee_name != StringName() && assignment_patterns.has(assignee_name) && _is_constant_string(p_assignment->assigned_value)) {
// If the assignment is towards one of the extract patterns (text, tooltip_text etc.), and the value is a constant string, we collect the string.
ids->push_back(p_assignment->assigned_value->reduced_value);
_add_id(p_assignment->assigned_value->reduced_value, p_assignment->assigned_value->start_line);
} else if (assignee_name == fd_filters) {
// Extract from `get_node("FileDialog").filters = <filter array>`.
_extract_fd_filter_array(p_assignment->assigned_value);
Expand Down Expand Up @@ -287,7 +363,7 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
ids_ctx_plural->push_back(id_ctx_plural);
_add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (function_name == trn_func || function_name == atrn_func) {
// Extract from `tr_n(id, plural, n, ctx)` or `atr_n(id, plural, n, ctx)`.
Expand All @@ -307,20 +383,20 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}
if (extract_id_ctx_plural) {
ids_ctx_plural->push_back(id_ctx_plural);
_add_id_ctx_plural(id_ctx_plural, p_call->start_line);
}
} else if (first_arg_patterns.has(function_name)) {
if (!p_call->arguments.is_empty() && _is_constant_string(p_call->arguments[0])) {
ids->push_back(p_call->arguments[0]->reduced_value);
_add_id(p_call->arguments[0]->reduced_value, p_call->arguments[0]->start_line);
}
} else if (second_arg_patterns.has(function_name)) {
if (p_call->arguments.size() > 1 && _is_constant_string(p_call->arguments[1])) {
ids->push_back(p_call->arguments[1]->reduced_value);
_add_id(p_call->arguments[1]->reduced_value, p_call->arguments[1]->start_line);
}
} else if (function_name == fd_add_filter) {
// Extract the 'JPE Images' in this example - get_node("FileDialog").add_filter("*.jpg; JPE Images").
if (!p_call->arguments.is_empty()) {
_extract_fd_filter_string(p_call->arguments[0]);
_extract_fd_filter_string(p_call->arguments[0], p_call->arguments[0]->start_line);
}
} else if (function_name == fd_set_filter) {
// Extract from `get_node("FileDialog").set_filters(<filter array>)`.
Expand All @@ -330,12 +406,12 @@ void GDScriptEditorTranslationParserPlugin::_assess_call(const GDScriptParser::C
}
}

void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression) {
void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_string(const GDScriptParser::ExpressionNode *p_expression, int p_line) {
// Extract the name in "extension ; name".
if (_is_constant_string(p_expression)) {
PackedStringArray arr = p_expression->reduced_value.operator String().split(";", true);
ERR_FAIL_COND_MSG(arr.size() != 2, "Argument for setting FileDialog has bad format.");
ids->push_back(arr[1].strip_edges());
_add_id(arr[1].strip_edges(), p_line);
}
}

Expand All @@ -355,7 +431,7 @@ void GDScriptEditorTranslationParserPlugin::_extract_fd_filter_array(const GDScr

if (array_node) {
for (int i = 0; i < array_node->elements.size(); i++) {
_extract_fd_filter_string(array_node->elements[i]);
_extract_fd_filter_string(array_node->elements[i], array_node->elements[i]->start_line);
}
}
}
Expand Down
Loading
Loading