From c5a70dbeeb7ca4f194cc7a8c9cdc73fbcde2b0ca Mon Sep 17 00:00:00 2001 From: Haoyu Qiu Date: Sat, 12 Apr 2025 12:41:33 +0800 Subject: [PATCH] Improve EditorAbout dialog auto-translation --- editor/credits_roll.cpp | 2 + editor/editor_about.cpp | 283 +++++++++++++++++++--------------------- editor/editor_about.h | 27 ++-- 3 files changed, 156 insertions(+), 156 deletions(-) diff --git a/editor/credits_roll.cpp b/editor/credits_roll.cpp index 265714d3648..fa345a7115a 100644 --- a/editor/credits_roll.cpp +++ b/editor/credits_roll.cpp @@ -51,6 +51,7 @@ Label *CreditsRoll::_create_label(const String &p_with_text, LabelSize p_size) { switch (p_size) { case LabelSize::NORMAL: { label->add_theme_font_size_override(SceneStringName(font_size), font_size_normal); + label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); } break; case LabelSize::HEADER: { @@ -165,6 +166,7 @@ void CreditsRoll::roll_credits() { _create_nothing(); project_manager = _create_label(TTR("Project Manager", "Job Title"), LabelSize::HEADER); + project_manager->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); _create_label(_build_string(AUTHORS_PROJECT_MANAGERS)); _create_nothing(); diff --git a/editor/editor_about.cpp b/editor/editor_about.cpp index 8b93683c05f..f62f9118ab2 100644 --- a/editor/editor_about.cpp +++ b/editor/editor_about.cpp @@ -39,13 +39,34 @@ #include "editor/gui/editor_version_button.h" #include "editor/themes/editor_scale.h" #include "scene/gui/item_list.h" +#include "scene/gui/rich_text_label.h" +#include "scene/gui/scroll_container.h" #include "scene/gui/separator.h" #include "scene/gui/split_container.h" #include "scene/gui/tab_container.h" +#include "scene/gui/texture_rect.h" +#include "scene/gui/tree.h" #include "scene/resources/style_box.h" void EditorAbout::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_TRANSLATION_CHANGED: { + _about_text_label->set_text( + String(U"© 2014-present ") + TTR("Godot Engine contributors") + ".\n" + + String(U"© 2007-2014 Juan Linietsky, Ariel Manzur.\n")); + + _project_manager_label->set_text(TTR("Project Manager", "Job Title")); + + for (ItemList *il : name_lists) { + for (int i = 0; i < il->get_item_count(); i++) { + const Variant val = il->get_item_metadata(i); + if (val.get_type() == Variant::STRING) { + il->set_item_tooltip(i, val.operator String() + "\n\n" + TTR("Double-click to open in browser.")); + } + } + } + } break; + case NOTIFICATION_THEME_CHANGED: { const Ref font = get_theme_font(SNAME("source"), EditorStringName(EditorFonts)); const int font_size = get_theme_font_size(SNAME("source_size"), EditorStringName(EditorFonts)); @@ -82,23 +103,22 @@ void EditorAbout::_license_tree_selected() { _tpl_text->set_text(selected->get_metadata(0)); } -void EditorAbout::_project_manager_clicked() { - if (!EditorNode::get_singleton()) { - // Don't allow in Project Manager. - return; - } +void EditorAbout::_item_activated(int p_idx, ItemList *p_il) { + const Variant val = p_il->get_item_metadata(p_idx); + if (val.get_type() == Variant::STRING) { + OS::get_singleton()->shell_open(val); + } else { + // Easter egg :D + if (!EditorNode::get_singleton()) { + // Don't allow in Project Manager. + return; + } - if (!credits_roll) { - credits_roll = memnew(CreditsRoll); - add_child(credits_roll); - } - credits_roll->roll_credits(); -} - -void EditorAbout::_item_with_website_selected(int p_id, ItemList *p_il) { - const String website = p_il->get_item_metadata(p_id); - if (!website.is_empty()) { - OS::get_singleton()->shell_open(website); + if (!credits_roll) { + credits_roll = memnew(CreditsRoll); + add_child(credits_roll); + } + credits_roll->roll_credits(); } } @@ -106,101 +126,73 @@ void EditorAbout::_item_list_resized(ItemList *p_il) { p_il->set_fixed_column_width(p_il->get_size().x / 3.0 - 16 * EDSCALE * 2.5); // Weird. Should be 3.0 and that's it?. } -ScrollContainer *EditorAbout::_populate_list(const String &p_name, const List &p_sections, const char *const *const p_src[], const int p_single_column_flags, const bool p_allow_website, const String &p_easter_egg_section) { - ScrollContainer *sc = memnew(ScrollContainer); - sc->set_name(p_name); - sc->set_v_size_flags(Control::SIZE_EXPAND); +Label *EditorAbout::_create_section(Control *p_parent, const String &p_name, const char *const *p_src, BitField p_flags) { + Label *lbl = memnew(Label(p_name)); + lbl->set_theme_type_variation("HeaderSmall"); + lbl->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + p_parent->add_child(lbl); - VBoxContainer *vbc = memnew(VBoxContainer); - vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); - sc->add_child(vbc); + ItemList *il = memnew(ItemList); + il->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); + il->set_h_size_flags(Control::SIZE_EXPAND_FILL); + il->set_same_column_width(true); + il->set_auto_height(true); + il->set_max_columns(p_flags.has_flag(FLAG_SINGLE_COLUMN) ? 1 : 16); + il->add_theme_constant_override("h_separation", 16 * EDSCALE); - Ref empty_stylebox = memnew(StyleBoxEmpty); + if (p_flags.has_flag(FLAG_ALLOW_WEBSITE) || (p_flags.has_flag(FLAG_EASTER_EGG) && EditorNode::get_singleton())) { + Ref empty_stylebox = memnew(StyleBoxEmpty); + il->add_theme_style_override("focus", empty_stylebox); + il->add_theme_style_override("selected", empty_stylebox); - int i = 0; - for (List::ConstIterator itr = p_sections.begin(); itr != p_sections.end(); ++itr, ++i) { - bool single_column = p_single_column_flags & (1 << i); - const char *const *names_ptr = p_src[i]; - if (*names_ptr) { - const String §ion_name = *itr; + il->connect("item_activated", callable_mp(this, &EditorAbout::_item_activated).bind(il)); + } else { + il->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); + il->set_focus_mode(Control::FOCUS_NONE); + } - Label *lbl = memnew(Label); - lbl->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - lbl->set_theme_type_variation("HeaderSmall"); - lbl->set_text(section_name); - vbc->add_child(lbl); + const char *const *names_ptr = p_src; + if (p_flags.has_flag(FLAG_ALLOW_WEBSITE)) { + il->connect(SceneStringName(resized), callable_mp(this, &EditorAbout::_item_list_resized).bind(il)); + il->connect(SceneStringName(focus_exited), callable_mp(il, &ItemList::deselect_all)); - ItemList *il = memnew(ItemList); - il->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); - il->set_h_size_flags(Control::SIZE_EXPAND_FILL); - il->set_same_column_width(true); - il->set_auto_height(true); - il->set_mouse_filter(Control::MOUSE_FILTER_IGNORE); - il->set_focus_mode(Control::FOCUS_NONE); - il->add_theme_constant_override("h_separation", 16 * EDSCALE); - if (p_allow_website) { - il->set_focus_mode(Control::FOCUS_CLICK); - il->set_mouse_filter(Control::MOUSE_FILTER_PASS); + while (*names_ptr) { + const String name = String::utf8(*names_ptr++); + const String identifier = name.get_slicec('<', 0); + const String website = name.get_slice_count("<") == 1 ? "" : name.get_slicec('<', 1).trim_suffix(">"); - il->connect("item_activated", callable_mp(this, &EditorAbout::_item_with_website_selected).bind(il)); - il->connect(SceneStringName(resized), callable_mp(this, &EditorAbout::_item_list_resized).bind(il)); - il->connect(SceneStringName(focus_exited), callable_mp(il, &ItemList::deselect_all)); + il->add_item(identifier, nullptr, !website.is_empty()); - il->add_theme_style_override("focus", empty_stylebox); - il->add_theme_style_override("selected", empty_stylebox); - - while (*names_ptr) { - const String name = String::utf8(*names_ptr++); - const String identifier = name.get_slicec('<', 0); - const String website = name.get_slice_count("<") == 1 ? "" : name.get_slicec('<', 1).trim_suffix(">"); - - const int name_item_id = il->add_item(identifier, nullptr, false); - il->set_item_tooltip_enabled(name_item_id, false); - - if (!website.is_empty()) { - il->set_item_selectable(name_item_id, true); - il->set_item_metadata(name_item_id, website); - il->set_item_tooltip(name_item_id, website + "\n\n" + TTR("Double-click to open in browser.")); - il->set_item_tooltip_enabled(name_item_id, true); - } - - if (!*names_ptr && name.contains(" anonymous ")) { - il->set_item_disabled(name_item_id, true); - } - } + if (website.is_empty()) { + il->set_item_tooltip_enabled(-1, false); } else { - if (section_name == p_easter_egg_section) { - // Easter egg :D - il->set_focus_mode(Control::FOCUS_CLICK); - il->set_mouse_filter(Control::MOUSE_FILTER_PASS); - - il->connect("item_activated", callable_mp(this, &EditorAbout::_project_manager_clicked).unbind(1), CONNECT_DEFERRED); - - il->add_theme_style_override("focus", empty_stylebox); - il->add_theme_style_override("selected", empty_stylebox); - } - - while (*names_ptr) { - il->add_item(String::utf8(*names_ptr++), nullptr, false); - } + il->set_item_metadata(-1, website); } - il->set_max_columns(single_column ? 1 : 16); - name_lists.append(il); - - vbc->add_child(il); - - HSeparator *hs = memnew(HSeparator); - hs->set_modulate(Color(0, 0, 0, 0)); - vbc->add_child(hs); + if (!*names_ptr && name.contains(" anonymous ")) { + il->set_item_disabled(-1, true); + } + } + } else { + while (*names_ptr) { + il->add_item(String::utf8(*names_ptr++), nullptr, false); + il->set_item_tooltip_enabled(-1, false); } } - return sc; + name_lists.append(il); + + p_parent->add_child(il); + + HSeparator *hs = memnew(HSeparator); + hs->set_modulate(Color(0, 0, 0, 0)); + p_parent->add_child(hs); + + return lbl; } EditorAbout::EditorAbout() { - set_title(TTR("Thanks from the Godot community!")); + set_title(TTRC("Thanks from the Godot community!")); set_hide_on_ok(true); VBoxContainer *vbc = memnew(VBoxContainer); @@ -224,13 +216,11 @@ EditorAbout::EditorAbout() { version_info_vbc->add_child(memnew(EditorVersionButton(EditorVersionButton::FORMAT_WITH_NAME_AND_BUILD))); - Label *about_text = memnew(Label); - about_text->set_focus_mode(Control::FOCUS_ACCESSIBILITY); - about_text->set_v_size_flags(Control::SIZE_SHRINK_CENTER); - about_text->set_text( - String::utf8("\xc2\xa9 2014-present ") + TTR("Godot Engine contributors") + "." + - String::utf8("\n\xc2\xa9 2007-2014 Juan Linietsky, Ariel Manzur.\n")); - version_info_vbc->add_child(about_text); + _about_text_label = memnew(Label); + _about_text_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY); + _about_text_label->set_v_size_flags(Control::SIZE_SHRINK_CENTER); + _about_text_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); + version_info_vbc->add_child(_about_text_label); hbc->add_child(version_info_vbc); @@ -241,51 +231,50 @@ EditorAbout::EditorAbout() { tc->set_theme_type_variation("TabContainerOdd"); vbc->add_child(tc); - // Authors. + { + ScrollContainer *sc = memnew(ScrollContainer); + sc->set_name(TTRC("Authors")); + sc->set_v_size_flags(Control::SIZE_EXPAND); + tc->add_child(sc); - List dev_sections; - dev_sections.push_back(TTR("Project Founders")); - dev_sections.push_back(TTR("Lead Developer")); - // TRANSLATORS: This refers to a job title. - const String project_manager = TTR("Project Manager", "Job Title"); - dev_sections.push_back(project_manager); - dev_sections.push_back(TTR("Developers")); - const char *const *dev_src[] = { - AUTHORS_FOUNDERS, - AUTHORS_LEAD_DEVELOPERS, - AUTHORS_PROJECT_MANAGERS, - AUTHORS_DEVELOPERS, - }; - tc->add_child(_populate_list(TTR("Authors"), dev_sections, dev_src, 0b1, false, project_manager)); // First section (Project Founders) is always one column. + VBoxContainer *vb = memnew(VBoxContainer); + vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + sc->add_child(vb); - // Donors. + _create_section(vb, TTRC("Project Founders"), AUTHORS_FOUNDERS, FLAG_SINGLE_COLUMN); + _create_section(vb, TTRC("Lead Developer"), AUTHORS_LEAD_DEVELOPERS); + // The section title will be updated in NOTIFICATION_TRANSLATION_CHANGED. + _project_manager_label = _create_section(vb, "", AUTHORS_PROJECT_MANAGERS, FLAG_EASTER_EGG); + _project_manager_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); + _create_section(vb, TTRC("Developers"), AUTHORS_DEVELOPERS); + } - List donor_sections; - donor_sections.push_back(TTR("Patrons")); - donor_sections.push_back(TTR("Platinum Sponsors")); - donor_sections.push_back(TTR("Gold Sponsors")); - donor_sections.push_back(TTR("Silver Sponsors")); - donor_sections.push_back(TTR("Diamond Members")); - donor_sections.push_back(TTR("Titanium Members")); - donor_sections.push_back(TTR("Platinum Members")); - donor_sections.push_back(TTR("Gold Members")); - const char *const *donor_src[] = { - DONORS_PATRONS, - DONORS_SPONSORS_PLATINUM, - DONORS_SPONSORS_GOLD, - DONORS_SPONSORS_SILVER, - DONORS_MEMBERS_DIAMOND, - DONORS_MEMBERS_TITANIUM, - DONORS_MEMBERS_PLATINUM, - DONORS_MEMBERS_GOLD, - }; - tc->add_child(_populate_list(TTR("Donors"), donor_sections, donor_src, 0b1, true)); // First section (Patron) is one column. + { + ScrollContainer *sc = memnew(ScrollContainer); + sc->set_name(TTRC("Donors")); + sc->set_v_size_flags(Control::SIZE_EXPAND); + tc->add_child(sc); + + VBoxContainer *vb = memnew(VBoxContainer); + vb->set_h_size_flags(Control::SIZE_EXPAND_FILL); + sc->add_child(vb); + + _create_section(vb, TTRC("Patrons"), DONORS_PATRONS, FLAG_ALLOW_WEBSITE | FLAG_SINGLE_COLUMN); + _create_section(vb, TTRC("Platinum Sponsors"), DONORS_SPONSORS_PLATINUM, FLAG_ALLOW_WEBSITE); + _create_section(vb, TTRC("Gold Sponsors"), DONORS_SPONSORS_GOLD, FLAG_ALLOW_WEBSITE); + _create_section(vb, TTRC("Silver Sponsors"), DONORS_SPONSORS_SILVER, FLAG_ALLOW_WEBSITE); + _create_section(vb, TTRC("Diamond Members"), DONORS_MEMBERS_DIAMOND, FLAG_ALLOW_WEBSITE); + _create_section(vb, TTRC("Titanium Members"), DONORS_MEMBERS_TITANIUM, FLAG_ALLOW_WEBSITE); + _create_section(vb, TTRC("Platinum Members"), DONORS_MEMBERS_PLATINUM, FLAG_ALLOW_WEBSITE); + _create_section(vb, TTRC("Gold Members"), DONORS_MEMBERS_GOLD, FLAG_ALLOW_WEBSITE); + } // License. license_text_label = memnew(RichTextLabel); + license_text_label->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); license_text_label->set_threaded(true); - license_text_label->set_name(TTR("License")); + license_text_label->set_name(TTRC("License")); license_text_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); license_text_label->set_v_size_flags(Control::SIZE_EXPAND_FILL); license_text_label->set_text(String::utf8(GODOT_LICENSE_TEXT)); @@ -294,35 +283,37 @@ EditorAbout::EditorAbout() { // Thirdparty License. VBoxContainer *license_thirdparty = memnew(VBoxContainer); - license_thirdparty->set_name(TTR("Third-party Licenses")); + license_thirdparty->set_name(TTRC("Third-party Licenses")); license_thirdparty->set_h_size_flags(Control::SIZE_EXPAND_FILL); tc->add_child(license_thirdparty); - Label *tpl_label = memnew(Label); + Label *tpl_label = memnew(Label(TTRC("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms."))); tpl_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY); tpl_label->set_h_size_flags(Control::SIZE_EXPAND_FILL); tpl_label->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART); - tpl_label->set_text(TTR("Godot Engine relies on a number of third-party free and open source libraries, all compatible with the terms of its MIT license. The following is an exhaustive list of all such third-party components with their respective copyright statements and license terms.")); tpl_label->set_size(Size2(630, 1) * EDSCALE); license_thirdparty->add_child(tpl_label); HSplitContainer *tpl_hbc = memnew(HSplitContainer); + tpl_hbc->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); tpl_hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); tpl_hbc->set_v_size_flags(Control::SIZE_EXPAND_FILL); tpl_hbc->set_split_offset(240 * EDSCALE); license_thirdparty->add_child(tpl_hbc); _tpl_tree = memnew(Tree); - _tpl_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED); _tpl_tree->set_hide_root(true); TreeItem *root = _tpl_tree->create_item(); TreeItem *tpl_ti_all = _tpl_tree->create_item(root); - tpl_ti_all->set_text(0, TTR("All Components")); + tpl_ti_all->set_text(0, TTRC("All Components")); + tpl_ti_all->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS); TreeItem *tpl_ti_tp = _tpl_tree->create_item(root); - tpl_ti_tp->set_text(0, TTR("Components")); + tpl_ti_tp->set_text(0, TTRC("Components")); + tpl_ti_tp->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS); tpl_ti_tp->set_selectable(0, false); TreeItem *tpl_ti_lc = _tpl_tree->create_item(root); - tpl_ti_lc->set_text(0, TTR("Licenses")); + tpl_ti_lc->set_text(0, TTRC("Licenses")); + tpl_ti_lc->set_auto_translate_mode(0, AUTO_TRANSLATE_MODE_ALWAYS); tpl_ti_lc->set_selectable(0, false); String long_text = ""; for (int component_index = 0; component_index < COPYRIGHT_INFO_COUNT; component_index++) { diff --git a/editor/editor_about.h b/editor/editor_about.h index 9e10e1d2ea9..ff50e4ecb7c 100644 --- a/editor/editor_about.h +++ b/editor/editor_about.h @@ -31,13 +31,13 @@ #pragma once #include "scene/gui/dialogs.h" -#include "scene/gui/item_list.h" -#include "scene/gui/rich_text_label.h" -#include "scene/gui/scroll_container.h" -#include "scene/gui/texture_rect.h" -#include "scene/gui/tree.h" class CreditsRoll; +class ItemList; +class Label; +class RichTextLabel; +class TextureRect; +class Tree; /** * NOTE: Do not assume the EditorNode singleton to be available in this class' methods. @@ -47,12 +47,19 @@ class EditorAbout : public AcceptDialog { GDCLASS(EditorAbout, AcceptDialog); private: - void _license_tree_selected(); - void _project_manager_clicked(); - void _item_with_website_selected(int p_id, ItemList *p_il); - void _item_list_resized(ItemList *p_il); - ScrollContainer *_populate_list(const String &p_name, const List &p_sections, const char *const *const p_src[], int p_single_column_flags = 0, bool p_allow_website = false, const String &p_easter_egg_section = String()); + enum SectionFlags { + FLAG_SINGLE_COLUMN = 1 << 0, + FLAG_ALLOW_WEBSITE = 1 << 1, + FLAG_EASTER_EGG = 1 << 2, + }; + void _license_tree_selected(); + void _item_activated(int p_idx, ItemList *p_il); + void _item_list_resized(ItemList *p_il); + Label *_create_section(Control *p_parent, const String &p_name, const char *const *p_src, BitField p_flags = 0); + + Label *_about_text_label = nullptr; + Label *_project_manager_label = nullptr; Tree *_tpl_tree = nullptr; RichTextLabel *license_text_label = nullptr; RichTextLabel *_tpl_text = nullptr;