You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-18 14:21:41 +00:00
Add CSV translation template generation
This commit is contained in:
@@ -40,8 +40,7 @@ class Translation : public Resource {
|
||||
OBJ_SAVE_TYPE(Translation);
|
||||
RES_BASE_EXTENSION("translation");
|
||||
|
||||
String locale = "en";
|
||||
|
||||
public:
|
||||
struct MessageKey {
|
||||
StringName msgctxt;
|
||||
StringName msgid;
|
||||
@@ -56,6 +55,9 @@ class Translation : public Resource {
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
String locale = "en";
|
||||
|
||||
HashMap<MessageKey, Vector<StringName>, MessageKey> translation_map;
|
||||
|
||||
mutable PluralRules *plural_rules_cache = nullptr;
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
<description>
|
||||
[EditorTranslationParserPlugin] is invoked when a file is being parsed to extract strings that require translation. To define the parsing and string extraction logic, override the [method _parse_file] method in script.
|
||||
The return value should be an [Array] of [PackedStringArray]s, one for each extracted translatable string. Each entry should contain [code][msgid, msgctxt, msgid_plural, comment, source_line][/code], where all except [code]msgid[/code] are optional. Empty strings will be ignored.
|
||||
The extracted strings will be written into a POT file selected by user under "POT Generation" in "Localization" tab in "Project Settings" menu.
|
||||
Below shows an example of a custom parser that extracts strings from a CSV file to write into a POT.
|
||||
The extracted strings will be written into a translation template file selected by user under "Template Generation" in "Localization" tab in "Project Settings" menu.
|
||||
Below shows an example of a custom parser that extracts strings from a CSV file to write into a template.
|
||||
[codeblocks]
|
||||
[gdscript]
|
||||
@tool
|
||||
|
||||
@@ -1037,7 +1037,7 @@
|
||||
</methods>
|
||||
<members>
|
||||
<member name="auto_translate_mode" type="int" setter="set_auto_translate_mode" getter="get_auto_translate_mode" enum="Node.AutoTranslateMode" default="0">
|
||||
Defines if any text should automatically change to its translated version depending on the current locale (for nodes such as [Label], [RichTextLabel], [Window], etc.). Also decides if the node's strings should be parsed for POT generation.
|
||||
Defines if any text should automatically change to its translated version depending on the current locale (for nodes such as [Label], [RichTextLabel], [Window], etc.). Also decides if the node's strings should be parsed for translation template generation.
|
||||
[b]Note:[/b] For the root node, auto translate mode can also be set via [member ProjectSettings.internationalization/rendering/root_node_auto_translate].
|
||||
</member>
|
||||
<member name="editor_description" type="String" setter="set_editor_description" getter="get_editor_description" default="""">
|
||||
@@ -1397,7 +1397,7 @@
|
||||
</constant>
|
||||
<constant name="AUTO_TRANSLATE_MODE_DISABLED" value="2" enum="AutoTranslateMode">
|
||||
Never automatically translate. This is the inverse of [constant AUTO_TRANSLATE_MODE_ALWAYS].
|
||||
String parsing for POT generation will be skipped for this node and children that are set to [constant AUTO_TRANSLATE_MODE_INHERIT].
|
||||
String parsing for translation template generation will be skipped for this node and children that are set to [constant AUTO_TRANSLATE_MODE_INHERIT].
|
||||
</constant>
|
||||
</constants>
|
||||
</class>
|
||||
|
||||
@@ -121,7 +121,10 @@ Error ResourceImporterCSVTranslation::import(ResourceUID::ID p_source_id, const
|
||||
column_to_translation[i] = translation;
|
||||
}
|
||||
|
||||
ERR_FAIL_COND_V_MSG(column_to_translation.is_empty(), ERR_PARSE_ERROR, "Error importing CSV translation: The CSV file must have at least one column for key and one column for translation.");
|
||||
if (column_to_translation.is_empty()) {
|
||||
WARN_PRINT(vformat("CSV file '%s' does not contain any translation.", p_source_file));
|
||||
return OK;
|
||||
}
|
||||
}
|
||||
|
||||
// Parse content rows.
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
#include "editor/gui/editor_file_dialog.h"
|
||||
#include "editor/settings/editor_settings.h"
|
||||
#include "editor/translations/editor_translation_parser.h"
|
||||
#include "editor/translations/pot_generator.h"
|
||||
#include "editor/translations/template_generator.h"
|
||||
#include "scene/gui/control.h"
|
||||
#include "scene/gui/tab_container.h"
|
||||
|
||||
@@ -45,8 +45,8 @@ void LocalizationEditor::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
translation_list->connect("button_clicked", callable_mp(this, &LocalizationEditor::_translation_delete));
|
||||
translation_pot_list->connect("button_clicked", callable_mp(this, &LocalizationEditor::_pot_delete));
|
||||
translation_pot_add_builtin->set_pressed(GLOBAL_GET("internationalization/locale/translation_add_builtin_strings_to_pot"));
|
||||
template_source_list->connect("button_clicked", callable_mp(this, &LocalizationEditor::_template_source_delete));
|
||||
template_add_builtin->set_pressed(GLOBAL_GET("internationalization/locale/translation_add_builtin_strings_to_pot"));
|
||||
|
||||
List<String> tfn;
|
||||
ResourceLoader::get_recognized_extensions_for_type("Translation", &tfn);
|
||||
@@ -62,8 +62,9 @@ void LocalizationEditor::_notification(int p_what) {
|
||||
translation_res_option_file_open_dialog->add_filter("*." + E);
|
||||
}
|
||||
|
||||
_update_pot_file_extensions();
|
||||
pot_generate_dialog->add_filter("*.pot");
|
||||
_update_template_source_file_extensions();
|
||||
template_generate_dialog->add_filter("*.pot");
|
||||
template_generate_dialog->add_filter("*.csv");
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_DRAG_END: {
|
||||
@@ -342,12 +343,12 @@ void LocalizationEditor::_translation_res_option_delete(Object *p_item, int p_co
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void LocalizationEditor::_pot_add(const PackedStringArray &p_paths) {
|
||||
PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
void LocalizationEditor::_template_source_add(const PackedStringArray &p_paths) {
|
||||
PackedStringArray sources = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
int count = 0;
|
||||
for (const String &path : p_paths) {
|
||||
if (!pot_translations.has(path)) {
|
||||
pot_translations.push_back(path);
|
||||
if (!sources.has(path)) {
|
||||
sources.push_back(path);
|
||||
count += 1;
|
||||
}
|
||||
}
|
||||
@@ -356,8 +357,8 @@ void LocalizationEditor::_pot_add(const PackedStringArray &p_paths) {
|
||||
}
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(vformat(TTRN("Add %d file for POT generation", "Add %d files for POT generation", count), count));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files", pot_translations);
|
||||
undo_redo->create_action(vformat(TTRN("Add %d file for template generation", "Add %d files for template generation", count), count));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files", sources);
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files", GLOBAL_GET("internationalization/locale/translations_pot_files"));
|
||||
undo_redo->add_do_method(this, "update_translations");
|
||||
undo_redo->add_undo_method(this, "update_translations");
|
||||
@@ -366,7 +367,7 @@ void LocalizationEditor::_pot_add(const PackedStringArray &p_paths) {
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void LocalizationEditor::_pot_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button) {
|
||||
void LocalizationEditor::_template_source_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button) {
|
||||
if (p_mouse_button != MouseButton::LEFT) {
|
||||
return;
|
||||
}
|
||||
@@ -376,15 +377,15 @@ void LocalizationEditor::_pot_delete(Object *p_item, int p_column, int p_button,
|
||||
|
||||
int idx = ti->get_metadata(0);
|
||||
|
||||
PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
PackedStringArray sources = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
|
||||
ERR_FAIL_INDEX(idx, pot_translations.size());
|
||||
ERR_FAIL_INDEX(idx, sources.size());
|
||||
|
||||
pot_translations.remove_at(idx);
|
||||
sources.remove_at(idx);
|
||||
|
||||
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
|
||||
undo_redo->create_action(TTR("Remove file from POT generation"));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files", pot_translations);
|
||||
undo_redo->create_action(TTR("Remove file from template generation"));
|
||||
undo_redo->add_do_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files", sources);
|
||||
undo_redo->add_undo_property(ProjectSettings::get_singleton(), "internationalization/locale/translations_pot_files", GLOBAL_GET("internationalization/locale/translations_pot_files"));
|
||||
undo_redo->add_do_method(this, "update_translations");
|
||||
undo_redo->add_undo_method(this, "update_translations");
|
||||
@@ -393,30 +394,35 @@ void LocalizationEditor::_pot_delete(Object *p_item, int p_column, int p_button,
|
||||
undo_redo->commit_action();
|
||||
}
|
||||
|
||||
void LocalizationEditor::_pot_file_open() {
|
||||
pot_file_open_dialog->popup_file_dialog();
|
||||
void LocalizationEditor::_template_source_file_open() {
|
||||
template_source_open_dialog->popup_file_dialog();
|
||||
}
|
||||
|
||||
void LocalizationEditor::_pot_generate_open() {
|
||||
pot_generate_dialog->popup_file_dialog();
|
||||
void LocalizationEditor::_template_generate_open() {
|
||||
template_generate_dialog->popup_file_dialog();
|
||||
}
|
||||
|
||||
void LocalizationEditor::_pot_add_builtin_toggled() {
|
||||
ProjectSettings::get_singleton()->set_setting("internationalization/locale/translation_add_builtin_strings_to_pot", translation_pot_add_builtin->is_pressed());
|
||||
void LocalizationEditor::_template_add_builtin_toggled() {
|
||||
ProjectSettings::get_singleton()->set_setting("internationalization/locale/translation_add_builtin_strings_to_pot", template_add_builtin->is_pressed());
|
||||
ProjectSettings::get_singleton()->save();
|
||||
|
||||
const PackedStringArray sources = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
if (sources.is_empty()) {
|
||||
template_generate_button->set_disabled(!template_add_builtin->is_pressed());
|
||||
}
|
||||
}
|
||||
|
||||
void LocalizationEditor::_pot_generate(const String &p_file) {
|
||||
void LocalizationEditor::_template_generate(const String &p_file) {
|
||||
EditorSettings::get_singleton()->set_project_metadata("pot_generator", "last_pot_path", p_file);
|
||||
POTGenerator::get_singleton()->generate_pot(p_file);
|
||||
TranslationTemplateGenerator::get_singleton()->generate(p_file);
|
||||
}
|
||||
|
||||
void LocalizationEditor::_update_pot_file_extensions() {
|
||||
pot_file_open_dialog->clear_filters();
|
||||
void LocalizationEditor::_update_template_source_file_extensions() {
|
||||
template_source_open_dialog->clear_filters();
|
||||
List<String> translation_parse_file_extensions;
|
||||
EditorTranslationParser::get_singleton()->get_recognized_extensions(&translation_parse_file_extensions);
|
||||
for (const String &E : translation_parse_file_extensions) {
|
||||
pot_file_open_dialog->add_filter("*." + E);
|
||||
template_source_open_dialog->add_filter("*." + E);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,15 +432,15 @@ void LocalizationEditor::connect_filesystem_dock_signals(FileSystemDock *p_fs_do
|
||||
}
|
||||
|
||||
void LocalizationEditor::_filesystem_files_moved(const String &p_old_file, const String &p_new_file) {
|
||||
// Update POT files if the moved file is a part of them.
|
||||
PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
if (pot_translations.has(p_old_file)) {
|
||||
pot_translations.erase(p_old_file);
|
||||
ProjectSettings::get_singleton()->set_setting("internationalization/locale/translations_pot_files", pot_translations);
|
||||
// Update source files if the moved file is a part of them.
|
||||
PackedStringArray sources = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
if (sources.has(p_old_file)) {
|
||||
sources.erase(p_old_file);
|
||||
ProjectSettings::get_singleton()->set_setting("internationalization/locale/translations_pot_files", sources);
|
||||
|
||||
PackedStringArray new_file;
|
||||
new_file.push_back(p_new_file);
|
||||
_pot_add(new_file);
|
||||
_template_source_add(new_file);
|
||||
}
|
||||
|
||||
// Update remaps if the moved file is a part of them.
|
||||
@@ -488,11 +494,11 @@ void LocalizationEditor::_filesystem_files_moved(const String &p_old_file, const
|
||||
}
|
||||
|
||||
void LocalizationEditor::_filesystem_file_removed(const String &p_file) {
|
||||
// Check if the POT files are affected.
|
||||
PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
if (pot_translations.has(p_file)) {
|
||||
pot_translations.erase(p_file);
|
||||
ProjectSettings::get_singleton()->set_setting("internationalization/locale/translations_pot_files", pot_translations);
|
||||
// Check if the source files are affected.
|
||||
PackedStringArray sources = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
if (sources.has(p_file)) {
|
||||
sources.erase(p_file);
|
||||
ProjectSettings::get_singleton()->set_setting("internationalization/locale/translations_pot_files", sources);
|
||||
}
|
||||
|
||||
// Check if the remaps are affected.
|
||||
@@ -701,24 +707,24 @@ void LocalizationEditor::update_translations() {
|
||||
}
|
||||
}
|
||||
|
||||
// Update translation POT files.
|
||||
translation_pot_list->clear();
|
||||
root = translation_pot_list->create_item(nullptr);
|
||||
translation_pot_list->set_hide_root(true);
|
||||
PackedStringArray pot_translations = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
for (int i = 0; i < pot_translations.size(); i++) {
|
||||
TreeItem *t = translation_pot_list->create_item(root);
|
||||
// Update translation source files.
|
||||
template_source_list->clear();
|
||||
root = template_source_list->create_item(nullptr);
|
||||
template_source_list->set_hide_root(true);
|
||||
PackedStringArray sources = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
for (int i = 0; i < sources.size(); i++) {
|
||||
TreeItem *t = template_source_list->create_item(root);
|
||||
t->set_editable(0, false);
|
||||
t->set_text(0, pot_translations[i].replace_first("res://", ""));
|
||||
t->set_tooltip_text(0, pot_translations[i]);
|
||||
t->set_text(0, sources[i].replace_first("res://", ""));
|
||||
t->set_tooltip_text(0, sources[i]);
|
||||
t->set_metadata(0, i);
|
||||
t->add_button(0, get_editor_theme_icon(SNAME("Remove")), 0, false, TTRC("Remove"));
|
||||
}
|
||||
|
||||
// New translation parser plugin might extend possible file extensions in POT generation.
|
||||
_update_pot_file_extensions();
|
||||
// New translation parser plugin might extend possible file extensions in template generation.
|
||||
_update_template_source_file_extensions();
|
||||
|
||||
pot_generate_button->set_disabled(pot_translations.is_empty());
|
||||
template_generate_button->set_disabled(sources.is_empty() && !template_add_builtin->is_pressed());
|
||||
|
||||
updating_translations = false;
|
||||
}
|
||||
@@ -844,7 +850,7 @@ LocalizationEditor::LocalizationEditor() {
|
||||
|
||||
{
|
||||
VBoxContainer *tvb = memnew(VBoxContainer);
|
||||
tvb->set_name(TTRC("POT Generation"));
|
||||
tvb->set_name(TTRC("Template Generation"));
|
||||
translations->add_child(tvb);
|
||||
|
||||
HBoxContainer *thb = memnew(HBoxContainer);
|
||||
@@ -855,35 +861,35 @@ LocalizationEditor::LocalizationEditor() {
|
||||
tvb->add_child(thb);
|
||||
|
||||
Button *addtr = memnew(Button(TTRC("Add...")));
|
||||
addtr->connect(SceneStringName(pressed), callable_mp(this, &LocalizationEditor::_pot_file_open));
|
||||
addtr->connect(SceneStringName(pressed), callable_mp(this, &LocalizationEditor::_template_source_file_open));
|
||||
thb->add_child(addtr);
|
||||
|
||||
pot_generate_button = memnew(Button(TTRC("Generate POT")));
|
||||
pot_generate_button->connect(SceneStringName(pressed), callable_mp(this, &LocalizationEditor::_pot_generate_open));
|
||||
thb->add_child(pot_generate_button);
|
||||
template_generate_button = memnew(Button(TTRC("Generate")));
|
||||
template_generate_button->connect(SceneStringName(pressed), callable_mp(this, &LocalizationEditor::_template_generate_open));
|
||||
thb->add_child(template_generate_button);
|
||||
|
||||
translation_pot_list = memnew(Tree);
|
||||
translation_pot_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tvb->add_child(translation_pot_list);
|
||||
trees.push_back(translation_pot_list);
|
||||
tree_data_types[translation_pot_list] = "localization_editor_pot_item";
|
||||
tree_settings[translation_pot_list] = "internationalization/locale/translations_pot_files";
|
||||
template_source_list = memnew(Tree);
|
||||
template_source_list->set_v_size_flags(Control::SIZE_EXPAND_FILL);
|
||||
tvb->add_child(template_source_list);
|
||||
trees.push_back(template_source_list);
|
||||
tree_data_types[template_source_list] = "localization_editor_pot_item";
|
||||
tree_settings[template_source_list] = "internationalization/locale/translations_pot_files";
|
||||
|
||||
translation_pot_add_builtin = memnew(CheckBox(TTRC("Add Built-in Strings to POT")));
|
||||
translation_pot_add_builtin->set_tooltip_text(TTRC("Add strings from built-in components such as certain Control nodes."));
|
||||
translation_pot_add_builtin->connect(SceneStringName(pressed), callable_mp(this, &LocalizationEditor::_pot_add_builtin_toggled));
|
||||
tvb->add_child(translation_pot_add_builtin);
|
||||
template_add_builtin = memnew(CheckBox(TTRC("Add Built-in Strings")));
|
||||
template_add_builtin->set_tooltip_text(TTRC("Add strings from built-in components such as certain Control nodes."));
|
||||
template_add_builtin->connect(SceneStringName(pressed), callable_mp(this, &LocalizationEditor::_template_add_builtin_toggled));
|
||||
tvb->add_child(template_add_builtin);
|
||||
|
||||
pot_generate_dialog = memnew(EditorFileDialog);
|
||||
pot_generate_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
|
||||
pot_generate_dialog->set_current_path(EditorSettings::get_singleton()->get_project_metadata("pot_generator", "last_pot_path", String()));
|
||||
pot_generate_dialog->connect("file_selected", callable_mp(this, &LocalizationEditor::_pot_generate));
|
||||
add_child(pot_generate_dialog);
|
||||
template_generate_dialog = memnew(EditorFileDialog);
|
||||
template_generate_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
|
||||
template_generate_dialog->set_current_path(EditorSettings::get_singleton()->get_project_metadata("pot_generator", "last_pot_path", String()));
|
||||
template_generate_dialog->connect("file_selected", callable_mp(this, &LocalizationEditor::_template_generate));
|
||||
add_child(template_generate_dialog);
|
||||
|
||||
pot_file_open_dialog = memnew(EditorFileDialog);
|
||||
pot_file_open_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
|
||||
pot_file_open_dialog->connect("files_selected", callable_mp(this, &LocalizationEditor::_pot_add));
|
||||
add_child(pot_file_open_dialog);
|
||||
template_source_open_dialog = memnew(EditorFileDialog);
|
||||
template_source_open_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
|
||||
template_source_open_dialog->connect("files_selected", callable_mp(this, &LocalizationEditor::_template_source_add));
|
||||
add_child(template_source_open_dialog);
|
||||
}
|
||||
|
||||
for (Tree *tree : trees) {
|
||||
|
||||
@@ -51,11 +51,11 @@ class LocalizationEditor : public VBoxContainer {
|
||||
Tree *translation_remap = nullptr;
|
||||
Tree *translation_remap_options = nullptr;
|
||||
|
||||
Tree *translation_pot_list = nullptr;
|
||||
CheckBox *translation_pot_add_builtin = nullptr;
|
||||
EditorFileDialog *pot_file_open_dialog = nullptr;
|
||||
EditorFileDialog *pot_generate_dialog = nullptr;
|
||||
Button *pot_generate_button = nullptr;
|
||||
Tree *template_source_list = nullptr;
|
||||
CheckBox *template_add_builtin = nullptr;
|
||||
EditorFileDialog *template_source_open_dialog = nullptr;
|
||||
EditorFileDialog *template_generate_dialog = nullptr;
|
||||
Button *template_generate_button = nullptr;
|
||||
|
||||
bool updating_translations = false;
|
||||
String localization_changed;
|
||||
@@ -79,13 +79,13 @@ class LocalizationEditor : public VBoxContainer {
|
||||
void _translation_res_option_popup(bool p_arrow_clicked);
|
||||
void _translation_res_option_selected(const String &p_locale);
|
||||
|
||||
void _pot_add(const PackedStringArray &p_paths);
|
||||
void _pot_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button);
|
||||
void _pot_file_open();
|
||||
void _pot_generate_open();
|
||||
void _pot_add_builtin_toggled();
|
||||
void _pot_generate(const String &p_file);
|
||||
void _update_pot_file_extensions();
|
||||
void _template_source_add(const PackedStringArray &p_paths);
|
||||
void _template_source_delete(Object *p_item, int p_column, int p_button, MouseButton p_mouse_button);
|
||||
void _template_source_file_open();
|
||||
void _template_generate_open();
|
||||
void _template_add_builtin_toggled();
|
||||
void _template_generate(const String &p_file);
|
||||
void _update_template_source_file_extensions();
|
||||
|
||||
void _filesystem_files_moved(const String &p_old_file, const String &p_new_file);
|
||||
void _filesystem_file_removed(const String &p_file);
|
||||
|
||||
@@ -1,260 +0,0 @@
|
||||
/**************************************************************************/
|
||||
/* pot_generator.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "pot_generator.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "core/error/error_macros.h"
|
||||
#include "editor/translations/editor_translation.h"
|
||||
#include "editor/translations/editor_translation_parser.h"
|
||||
|
||||
POTGenerator *POTGenerator::singleton = nullptr;
|
||||
|
||||
#ifdef DEBUG_POT
|
||||
void POTGenerator::_print_all_translation_strings() {
|
||||
for (HashMap<String, Vector<POTGenerator::MsgidData>>::Element E = all_translation_strings.front(); E; E = E.next()) {
|
||||
Vector<MsgidData> v_md = all_translation_strings[E.key()];
|
||||
for (int i = 0; i < v_md.size(); i++) {
|
||||
print_line("++++++");
|
||||
print_line("msgid: " + E.key());
|
||||
print_line("context: " + v_md[i].ctx);
|
||||
print_line("msgid_plural: " + v_md[i].plural);
|
||||
for (const String &F : v_md[i].locations) {
|
||||
print_line("location: " + F);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void POTGenerator::generate_pot(const String &p_file) {
|
||||
Vector<String> files = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
|
||||
if (files.is_empty()) {
|
||||
WARN_PRINT("No files selected for POT generation.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Clear all_translation_strings of the previous round.
|
||||
all_translation_strings.clear();
|
||||
|
||||
// Collect all translatable strings according to files order in "POT Generation" setting.
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
Vector<Vector<String>> translations;
|
||||
|
||||
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, &translations);
|
||||
} else {
|
||||
ERR_PRINT("Unrecognized file extension " + file_extension + " in generate_pot()");
|
||||
return;
|
||||
}
|
||||
|
||||
for (const Vector<String> &translation : translations) {
|
||||
ERR_CONTINUE(translation.is_empty());
|
||||
const String &msgctxt = (translation.size() > 1) ? translation[1] : String();
|
||||
const String &msgid_plural = (translation.size() > 2) ? translation[2] : String();
|
||||
const String &comment = (translation.size() > 3) ? translation[3] : String();
|
||||
const int source_line = (translation.size() > 4) ? translation[4].to_int() : 0;
|
||||
String location = file_path;
|
||||
if (source_line > 0) {
|
||||
location += vformat(":%d", source_line);
|
||||
}
|
||||
_add_new_msgid(translation[0], msgctxt, msgid_plural, location, 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], "", "");
|
||||
}
|
||||
}
|
||||
|
||||
_write_to_pot(p_file);
|
||||
}
|
||||
|
||||
void POTGenerator::_write_to_pot(const String &p_file) {
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
|
||||
if (err != OK) {
|
||||
ERR_PRINT("Failed to open " + p_file);
|
||||
return;
|
||||
}
|
||||
|
||||
String project_name = GLOBAL_GET("application/config/name").operator String().replace("\n", "\\n");
|
||||
Vector<String> files = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
String extracted_files = "";
|
||||
for (int i = 0; i < files.size(); i++) {
|
||||
extracted_files += "# " + files[i].replace("\n", "\\n") + "\n";
|
||||
}
|
||||
const String header =
|
||||
"# LANGUAGE translation for " + project_name + " for the following files:\n" +
|
||||
extracted_files +
|
||||
"#\n"
|
||||
"# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n"
|
||||
"#\n"
|
||||
"#, fuzzy\n"
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
"\"Project-Id-Version: " +
|
||||
project_name +
|
||||
"\\n\"\n"
|
||||
"\"MIME-Version: 1.0\\n\"\n"
|
||||
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
|
||||
"\"Content-Transfer-Encoding: 8-bit\\n\"\n";
|
||||
|
||||
file->store_string(header);
|
||||
|
||||
for (const KeyValue<String, Vector<MsgidData>> &E_pair : all_translation_strings) {
|
||||
String msgid = E_pair.key;
|
||||
const Vector<MsgidData> &v_msgid_data = E_pair.value;
|
||||
for (int i = 0; i < v_msgid_data.size(); i++) {
|
||||
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) {
|
||||
file->store_line("#: " + E.trim_prefix("res://").replace("\n", "\\n"));
|
||||
}
|
||||
|
||||
// Write context.
|
||||
if (!context.is_empty()) {
|
||||
file->store_line("msgctxt " + context.json_escape().quote());
|
||||
}
|
||||
|
||||
// Write msgid.
|
||||
_write_msgid(file, msgid, false);
|
||||
|
||||
// Write msgid_plural.
|
||||
if (!plural.is_empty()) {
|
||||
_write_msgid(file, plural, true);
|
||||
file->store_line("msgstr[0] \"\"");
|
||||
file->store_line("msgstr[1] \"\"");
|
||||
} else {
|
||||
file->store_line("msgstr \"\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void POTGenerator::_write_msgid(Ref<FileAccess> r_file, const String &p_id, bool p_plural) {
|
||||
if (p_plural) {
|
||||
r_file->store_string("msgid_plural ");
|
||||
} else {
|
||||
r_file->store_string("msgid ");
|
||||
}
|
||||
|
||||
if (p_id.is_empty()) {
|
||||
r_file->store_line("\"\"");
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<String> lines = p_id.split("\n");
|
||||
const String &last_line = lines[lines.size() - 1]; // `lines` cannot be empty.
|
||||
int pot_line_count = lines.size();
|
||||
if (last_line.is_empty()) {
|
||||
pot_line_count--;
|
||||
}
|
||||
|
||||
if (pot_line_count > 1) {
|
||||
r_file->store_line("\"\"");
|
||||
}
|
||||
|
||||
for (int i = 0; i < lines.size() - 1; i++) {
|
||||
r_file->store_line((lines[i] + "\n").json_escape().quote());
|
||||
}
|
||||
|
||||
if (!last_line.is_empty()) {
|
||||
r_file->store_line(last_line.json_escape().quote());
|
||||
}
|
||||
}
|
||||
|
||||
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];
|
||||
for (int i = 0; i < v_mdata.size(); i++) {
|
||||
if (v_mdata[i].ctx == 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)");
|
||||
}
|
||||
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.
|
||||
MsgidData mdata;
|
||||
mdata.ctx = p_context;
|
||||
mdata.plural = p_plural;
|
||||
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);
|
||||
}
|
||||
|
||||
POTGenerator *POTGenerator::get_singleton() {
|
||||
if (!singleton) {
|
||||
singleton = memnew(POTGenerator);
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
POTGenerator::~POTGenerator() {
|
||||
memdelete(singleton);
|
||||
singleton = nullptr;
|
||||
}
|
||||
285
editor/translations/template_generator.cpp
Normal file
285
editor/translations/template_generator.cpp
Normal file
@@ -0,0 +1,285 @@
|
||||
/**************************************************************************/
|
||||
/* template_generator.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* 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 "template_generator.h"
|
||||
|
||||
#include "core/config/project_settings.h"
|
||||
#include "editor/translations/editor_translation.h"
|
||||
#include "editor/translations/editor_translation_parser.h"
|
||||
|
||||
TranslationTemplateGenerator::MessageMap TranslationTemplateGenerator::parse(const Vector<String> &p_sources, bool p_add_builtin) const {
|
||||
Vector<Vector<String>> raw;
|
||||
|
||||
for (const String &path : p_sources) {
|
||||
Vector<Vector<String>> parsed_from_file;
|
||||
|
||||
const String &extension = path.get_extension();
|
||||
ERR_CONTINUE_MSG(!EditorTranslationParser::get_singleton()->can_parse(extension), vformat("Cannot parse file '%s': unrecognized file extension. Skipping.", path));
|
||||
|
||||
EditorTranslationParser::get_singleton()->get_parser(extension)->parse_file(path, &parsed_from_file);
|
||||
|
||||
for (const Vector<String> &entry : parsed_from_file) {
|
||||
ERR_CONTINUE(entry.is_empty());
|
||||
|
||||
const String &msgctxt = (entry.size() > 1) ? entry[1] : String();
|
||||
const String &msgid_plural = (entry.size() > 2) ? entry[2] : String();
|
||||
const String &comment = (entry.size() > 3) ? entry[3] : String();
|
||||
const int source_line = (entry.size() > 4) ? entry[4].to_int() : 0;
|
||||
const String &location = source_line > 0 ? vformat("%s:%d", path, source_line) : path;
|
||||
|
||||
raw.push_back({ entry[0], msgctxt, msgid_plural, comment, location });
|
||||
}
|
||||
}
|
||||
|
||||
if (p_add_builtin) {
|
||||
for (const Vector<String> &extractable_msgids : get_extractable_message_list()) {
|
||||
raw.push_back({ extractable_msgids[0], extractable_msgids[1], extractable_msgids[2], String(), String() });
|
||||
}
|
||||
}
|
||||
|
||||
MessageMap result;
|
||||
for (const Vector<String> &entry : raw) {
|
||||
const String &msgid = entry[0];
|
||||
const String &msgctxt = entry[1];
|
||||
const String &plural = entry[2];
|
||||
const String &comment = entry[3];
|
||||
const String &location = entry[4];
|
||||
|
||||
const Translation::MessageKey key = { msgctxt, msgid };
|
||||
MessageData &mdata = result[key];
|
||||
if (!mdata.plural.is_empty() && !plural.is_empty() && mdata.plural != plural) {
|
||||
WARN_PRINT(vformat(R"(Skipping different plural definitions for msgid "%s" msgctxt "%s": "%s" and "%s")", msgid, msgctxt, mdata.plural, plural));
|
||||
continue;
|
||||
}
|
||||
mdata.plural = plural;
|
||||
if (!location.is_empty()) {
|
||||
mdata.locations.insert(location);
|
||||
}
|
||||
if (!comment.is_empty()) {
|
||||
mdata.comments.insert(comment);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void TranslationTemplateGenerator::generate(const String &p_file) {
|
||||
const Vector<String> files = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
const bool add_builtin = GLOBAL_GET("internationalization/locale/translation_add_builtin_strings_to_pot");
|
||||
|
||||
const MessageMap &map = parse(files, add_builtin);
|
||||
if (map.is_empty()) {
|
||||
WARN_PRINT("No translatable strings found.");
|
||||
return;
|
||||
}
|
||||
|
||||
Error err;
|
||||
Ref<FileAccess> file = FileAccess::open(p_file, FileAccess::WRITE, &err);
|
||||
ERR_FAIL_COND_MSG(err != OK, "Failed to open " + p_file);
|
||||
|
||||
const String ext = p_file.get_extension().to_lower();
|
||||
if (ext == "pot") {
|
||||
_write_to_pot(file, map);
|
||||
} else if (ext == "csv") {
|
||||
_write_to_csv(file, map);
|
||||
} else {
|
||||
ERR_FAIL_MSG("Unrecognized translation template file extension: " + ext);
|
||||
}
|
||||
}
|
||||
|
||||
static void _write_pot_field(Ref<FileAccess> p_file, const String &p_name, const String &p_value) {
|
||||
p_file->store_string(p_name + " ");
|
||||
|
||||
if (p_value.is_empty()) {
|
||||
p_file->store_line("\"\"");
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<String> lines = p_value.split("\n");
|
||||
DEV_ASSERT(lines.size() > 0);
|
||||
|
||||
const String &last_line = lines[lines.size() - 1];
|
||||
const int pot_line_count = last_line.is_empty() ? lines.size() - 1 : lines.size();
|
||||
|
||||
if (pot_line_count > 1) {
|
||||
p_file->store_line("\"\"");
|
||||
}
|
||||
|
||||
for (int i = 0; i < lines.size() - 1; i++) {
|
||||
p_file->store_line((lines[i] + "\n").json_escape().quote());
|
||||
}
|
||||
if (!last_line.is_empty()) {
|
||||
p_file->store_line(last_line.json_escape().quote());
|
||||
}
|
||||
}
|
||||
|
||||
void TranslationTemplateGenerator::_write_to_pot(Ref<FileAccess> p_file, const MessageMap &p_map) const {
|
||||
const String project_name = GLOBAL_GET("application/config/name").operator String().replace("\n", "\\n");
|
||||
const Vector<String> files = GLOBAL_GET("internationalization/locale/translations_pot_files");
|
||||
String extracted_files;
|
||||
for (const String &file : files) {
|
||||
extracted_files += "# " + file.replace("\n", "\\n") + "\n";
|
||||
}
|
||||
const String header =
|
||||
"# LANGUAGE translation for " + project_name + " for the following files:\n" +
|
||||
extracted_files +
|
||||
"#\n"
|
||||
"# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.\n"
|
||||
"#\n"
|
||||
"#, fuzzy\n"
|
||||
"msgid \"\"\n"
|
||||
"msgstr \"\"\n"
|
||||
"\"Project-Id-Version: " +
|
||||
project_name +
|
||||
"\\n\"\n"
|
||||
"\"MIME-Version: 1.0\\n\"\n"
|
||||
"\"Content-Type: text/plain; charset=UTF-8\\n\"\n"
|
||||
"\"Content-Transfer-Encoding: 8-bit\\n\"\n";
|
||||
p_file->store_string(header);
|
||||
|
||||
for (const KeyValue<Translation::MessageKey, MessageData> &E : p_map) {
|
||||
// Put the blank line at the start, to avoid a double at the end when closing the file.
|
||||
p_file->store_line("");
|
||||
|
||||
// Write comments.
|
||||
bool is_first_comment = true;
|
||||
for (const String &comment : E.value.comments) {
|
||||
if (is_first_comment) {
|
||||
p_file->store_line("#. TRANSLATORS: " + comment.replace("\n", "\n#. "));
|
||||
} else {
|
||||
p_file->store_line("#. " + comment.replace("\n", "\n#. "));
|
||||
}
|
||||
is_first_comment = false;
|
||||
}
|
||||
|
||||
// Write file locations.
|
||||
for (const String &location : E.value.locations) {
|
||||
p_file->store_line("#: " + location.trim_prefix("res://").replace("\n", "\\n"));
|
||||
}
|
||||
|
||||
// Write context.
|
||||
const String msgctxt = E.key.msgctxt;
|
||||
if (!msgctxt.is_empty()) {
|
||||
p_file->store_line("msgctxt " + msgctxt.json_escape().quote());
|
||||
}
|
||||
|
||||
// Write msgid.
|
||||
_write_pot_field(p_file, "msgid", E.key.msgid);
|
||||
|
||||
// Write msgid_plural.
|
||||
if (E.value.plural.is_empty()) {
|
||||
p_file->store_line("msgstr \"\"");
|
||||
} else {
|
||||
_write_pot_field(p_file, "msgid_plural", E.value.plural);
|
||||
p_file->store_line("msgstr[0] \"\"");
|
||||
p_file->store_line("msgstr[1] \"\"");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static String _join_strings(const HashSet<String> &p_strings) {
|
||||
String result;
|
||||
bool is_first = true;
|
||||
for (const String &s : p_strings) {
|
||||
if (!is_first) {
|
||||
result += '\n';
|
||||
}
|
||||
result += s;
|
||||
is_first = false;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void TranslationTemplateGenerator::_write_to_csv(Ref<FileAccess> p_file, const MessageMap &p_map) const {
|
||||
// Avoid adding unnecessary columns.
|
||||
bool context_used = false;
|
||||
bool plural_used = false;
|
||||
bool comments_used = false;
|
||||
bool locations_used = false;
|
||||
{
|
||||
for (const KeyValue<Translation::MessageKey, MessageData> &E : p_map) {
|
||||
if (!context_used && !E.key.msgctxt.is_empty()) {
|
||||
context_used = true;
|
||||
}
|
||||
if (!plural_used && !E.value.plural.is_empty()) {
|
||||
plural_used = true;
|
||||
}
|
||||
if (!comments_used && !E.value.comments.is_empty()) {
|
||||
comments_used = true;
|
||||
}
|
||||
if (!locations_used && !E.value.locations.is_empty()) {
|
||||
locations_used = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vector<String> header = { "key" };
|
||||
if (context_used) {
|
||||
header.push_back("?context");
|
||||
}
|
||||
if (plural_used) {
|
||||
header.push_back("?plural");
|
||||
}
|
||||
if (comments_used) {
|
||||
header.push_back("_comments");
|
||||
}
|
||||
if (locations_used) {
|
||||
header.push_back("_locations");
|
||||
}
|
||||
p_file->store_csv_line(header);
|
||||
|
||||
for (const KeyValue<Translation::MessageKey, MessageData> &E : p_map) {
|
||||
Vector<String> line = { E.key.msgid };
|
||||
if (context_used) {
|
||||
line.push_back(E.key.msgctxt);
|
||||
}
|
||||
if (plural_used) {
|
||||
line.push_back(E.value.plural);
|
||||
}
|
||||
if (comments_used) {
|
||||
line.push_back(_join_strings(E.value.comments));
|
||||
}
|
||||
if (locations_used) {
|
||||
line.push_back(_join_strings(E.value.locations));
|
||||
}
|
||||
p_file->store_csv_line(line);
|
||||
}
|
||||
}
|
||||
|
||||
TranslationTemplateGenerator *TranslationTemplateGenerator::get_singleton() {
|
||||
if (!singleton) {
|
||||
singleton = memnew(TranslationTemplateGenerator);
|
||||
}
|
||||
return singleton;
|
||||
}
|
||||
|
||||
TranslationTemplateGenerator::~TranslationTemplateGenerator() {
|
||||
memdelete(singleton);
|
||||
singleton = nullptr;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/**************************************************************************/
|
||||
/* pot_generator.h */
|
||||
/* template_generator.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
@@ -31,34 +31,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/io/file_access.h"
|
||||
#include "core/templates/hash_map.h"
|
||||
#include "core/templates/hash_set.h"
|
||||
#include "core/string/translation.h"
|
||||
|
||||
//#define DEBUG_POT
|
||||
class TranslationTemplateGenerator {
|
||||
static inline TranslationTemplateGenerator *singleton = nullptr;
|
||||
|
||||
class POTGenerator {
|
||||
static POTGenerator *singleton;
|
||||
|
||||
struct MsgidData {
|
||||
String ctx;
|
||||
struct MessageData {
|
||||
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, const String &p_comment);
|
||||
using MessageMap = HashMap<Translation::MessageKey, MessageData, Translation::MessageKey>;
|
||||
|
||||
#ifdef DEBUG_POT
|
||||
void _print_all_translation_strings();
|
||||
#endif
|
||||
MessageMap parse(const Vector<String> &p_sources, bool p_add_builtin) const;
|
||||
|
||||
void _write_to_pot(Ref<FileAccess> p_file, const MessageMap &p_map) const;
|
||||
void _write_to_csv(Ref<FileAccess> p_file, const MessageMap &p_map) const;
|
||||
|
||||
public:
|
||||
static POTGenerator *get_singleton();
|
||||
void generate_pot(const String &p_file);
|
||||
static TranslationTemplateGenerator *get_singleton();
|
||||
|
||||
~POTGenerator();
|
||||
void generate(const String &p_file);
|
||||
|
||||
~TranslationTemplateGenerator();
|
||||
};
|
||||
Reference in New Issue
Block a user