Merge pull request #103848 from timothyqiu/popup-i18n

Add auto translate mode for items in `PopupMenu` and `OptionButton`
This commit is contained in:
Thaddeus Crews 2025-03-17 10:52:26 -05:00
commit 8ca72e1490
No known key found for this signature in database
GPG Key ID: 62181B86FE9E5D84
13 changed files with 156 additions and 21 deletions

View File

@ -42,6 +42,13 @@
Clears all the items in the [OptionButton].
</description>
</method>
<method name="get_item_auto_translate_mode" qualifiers="const">
<return type="int" enum="Node.AutoTranslateMode" />
<param index="0" name="idx" type="int" />
<description>
Returns the auto translate mode of the item at index [param idx].
</description>
</method>
<method name="get_item_icon" qualifiers="const">
<return type="Texture2D" />
<param index="0" name="idx" type="int" />
@ -153,6 +160,15 @@
If [code]true[/code], shortcuts are disabled and cannot be used to trigger the button.
</description>
</method>
<method name="set_item_auto_translate_mode">
<return type="void" />
<param index="0" name="idx" type="int" />
<param index="1" name="mode" type="int" enum="Node.AutoTranslateMode" />
<description>
Sets the auto translate mode of the item at index [param idx].
Items use [constant Node.AUTO_TRANSLATE_MODE_INHERIT] by default, which uses the same auto translate mode as the [OptionButton] itself.
</description>
</method>
<method name="set_item_disabled">
<return type="void" />
<param index="0" name="idx" type="int" />

View File

@ -237,6 +237,13 @@
Returns the accelerator of the item at the given [param index]. An accelerator is a keyboard shortcut that can be pressed to trigger the menu button even if it's not currently open. The return value is an integer which is generally a combination of [enum KeyModifierMask]s and [enum Key]s using bitwise OR such as [code]KEY_MASK_CTRL | KEY_A[/code] ([kbd]Ctrl + A[/kbd]). If no accelerator is defined for the specified [param index], [method get_item_accelerator] returns [code]0[/code] (corresponding to [constant @GlobalScope.KEY_NONE]).
</description>
</method>
<method name="get_item_auto_translate_mode" qualifiers="const">
<return type="int" enum="Node.AutoTranslateMode" />
<param index="0" name="index" type="int" />
<description>
Returns the auto translate mode of the item at the given [param index].
</description>
</method>
<method name="get_item_icon" qualifiers="const">
<return type="Texture2D" />
<param index="0" name="index" type="int" />
@ -462,6 +469,15 @@
Mark the item at the given [param index] as a separator, which means that it would be displayed as a line. If [code]false[/code], sets the type of the item to plain text.
</description>
</method>
<method name="set_item_auto_translate_mode">
<return type="void" />
<param index="0" name="index" type="int" />
<param index="1" name="mode" type="int" enum="Node.AutoTranslateMode" />
<description>
Sets the auto translate mode of the item at the given [param index].
Items use [constant Node.AUTO_TRANSLATE_MODE_INHERIT] by default, which uses the same auto translate mode as the [PopupMenu] itself.
</description>
</method>
<method name="set_item_checked">
<return type="void" />
<param index="0" name="index" type="int" />

View File

@ -4688,6 +4688,7 @@ void EditorNode::_update_recent_scenes() {
recent_scenes->add_separator();
recent_scenes->add_shortcut(ED_SHORTCUT("editor/clear_recent", TTRC("Clear Recent Scenes")));
recent_scenes->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
recent_scenes->reset_size();
}
@ -5639,6 +5640,7 @@ void EditorNode::_update_layouts_menu() {
}
editor_layouts->add_item(layout);
editor_layouts->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED);
}
}
@ -7448,6 +7450,7 @@ EditorNode::EditorNode() {
file_menu->add_shortcut(ED_SHORTCUT_AND_COMMAND("editor/reopen_closed_scene", TTRC("Reopen Closed Scene"), KeyModifierMask::CMD_OR_CTRL + KeyModifierMask::SHIFT + Key::T), FILE_OPEN_PREV);
recent_scenes = memnew(PopupMenu);
recent_scenes->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
file_menu->add_submenu_node_item(TTR("Open Recent"), recent_scenes, FILE_OPEN_RECENT);
recent_scenes->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_open_recent_scene));
@ -7576,7 +7579,6 @@ EditorNode::EditorNode() {
settings_menu->add_submenu_node_item(TTR("Editor Docks"), editor_dock_manager->get_docks_menu());
editor_layouts = memnew(PopupMenu);
editor_layouts->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
settings_menu->add_submenu_node_item(TTR("Editor Layout"), editor_layouts);
editor_layouts->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_layout_menu_option));
settings_menu->add_separator();

View File

@ -824,7 +824,8 @@ void ScriptEditor::_update_recent_scripts() {
recent_scripts->add_separator();
recent_scripts->add_shortcut(ED_GET_SHORTCUT("script_editor/clear_recent"));
recent_scripts->set_item_disabled(recent_scripts->get_item_id(recent_scripts->get_item_count() - 1), rc.is_empty());
recent_scripts->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
recent_scripts->set_item_disabled(-1, rc.is_empty());
recent_scripts->reset_size();
}
@ -4278,6 +4279,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
file_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/reopen_closed_script"), FILE_REOPEN_CLOSED);
recent_scripts = memnew(PopupMenu);
recent_scripts->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
file_menu->get_popup()->add_submenu_node_item(TTR("Open Recent"), recent_scripts, FILE_OPEN_RECENT);
recent_scripts->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_open_recent_script));

View File

@ -261,6 +261,7 @@ SceneCreateDialog::SceneCreateDialog() {
ResourceSaver::get_recognized_extensions(sd, &extensions);
scene_extension_picker = memnew(OptionButton);
scene_extension_picker->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
hb->add_child(scene_extension_picker);
for (const String &E : extensions) {
scene_extension_picker->add_item("." + E);

View File

@ -566,6 +566,7 @@ void ScriptCreateDialog::_update_template_menu() {
if (!separator) {
template_menu->add_separator();
template_menu->set_item_text(-1, display_name);
template_menu->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
separator = true;
}
for (ScriptLanguage::ScriptTemplate &t : templates_found) {
@ -670,7 +671,7 @@ void ScriptCreateDialog::_update_dialog() {
validation_panel->set_message(MSG_ID_PATH, TTR("Built-in script (into scene file)."), EditorValidationPanel::MSG_OK);
}
} else {
template_inactive_message = TTR("Using existing script file.");
template_inactive_message = TTRC("Using existing script file.");
if (load_enabled) {
if (is_path_valid) {
validation_panel->set_message(MSG_ID_PATH, TTR("Will load an existing script file."), EditorValidationPanel::MSG_OK);
@ -684,16 +685,17 @@ void ScriptCreateDialog::_update_dialog() {
if (is_using_templates) {
// Check if at least one suitable template has been found.
if (template_menu->get_item_count() == 0 && template_inactive_message.is_empty()) {
template_inactive_message = TTR("No suitable template.");
template_inactive_message = TTRC("No suitable template.");
}
} else {
template_inactive_message = TTR("Empty");
template_inactive_message = TTRC("Empty");
}
if (!template_inactive_message.is_empty()) {
template_menu->set_disabled(true);
template_menu->clear();
template_menu->add_item(template_inactive_message);
template_menu->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
validation_panel->set_message(MSG_ID_TEMPLATE, "", EditorValidationPanel::MSG_INFO);
}
}
@ -821,11 +823,11 @@ ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptL
String ScriptCreateDialog::_get_script_origin_label(const ScriptLanguage::TemplateLocation &p_origin) const {
switch (p_origin) {
case ScriptLanguage::TEMPLATE_BUILT_IN:
return TTR("Built-in");
return TTRC("Built-in");
case ScriptLanguage::TEMPLATE_EDITOR:
return TTR("Editor");
return TTRC("Editor");
case ScriptLanguage::TEMPLATE_PROJECT:
return TTR("Project");
return TTRC("Project");
}
return "";
}
@ -868,6 +870,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
/* Language */
language_menu = memnew(OptionButton);
language_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
language_menu->set_custom_minimum_size(Size2(350, 0) * EDSCALE);
language_menu->set_expand_icon(true);
language_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@ -921,6 +924,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
template_inactive_message = "";
template_menu = memnew(OptionButton);
template_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
template_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
template_menu->connect(SceneStringName(item_selected), callable_mp(this, &ScriptCreateDialog::_template_changed));
template_hb->add_child(template_menu);

View File

@ -312,13 +312,13 @@ void ShaderCreateDialog::_type_changed(int p_language) {
if (shader_type_data.use_templates) {
int last_template = EditorSettings::get_singleton()->get_project_metadata("shader_setup", "last_selected_template", 0);
template_menu->add_item(TTR("Default"));
template_menu->add_item(TTR("Empty"));
template_menu->add_item(TTRC("Default"));
template_menu->add_item(TTRC("Empty"));
template_menu->select(last_template);
current_template = last_template;
} else {
template_menu->add_item(TTR("N/A"));
template_menu->add_item(TTRC("N/A"));
}
EditorSettings::get_singleton()->set_project_metadata("shader_setup", "last_selected_language", type_menu->get_item_text(type_menu->get_selected()));
@ -572,6 +572,7 @@ ShaderCreateDialog::ShaderCreateDialog() {
// Type.
type_menu = memnew(OptionButton);
type_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
type_menu->set_custom_minimum_size(Size2(250, 0) * EDSCALE);
type_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
gc->add_child(memnew(Label(TTR("Type:"))));
@ -612,6 +613,7 @@ ShaderCreateDialog::ShaderCreateDialog() {
// Modes.
mode_menu = memnew(OptionButton);
mode_menu->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
for (const String &type_name : ShaderTypes::get_singleton()->get_types_list()) {
mode_menu->add_item(type_name.capitalize());
}

View File

@ -48,6 +48,10 @@ void Button::_set_internal_margin(Side p_side, float p_value) {
void Button::_queue_update_size_cache() {
}
String Button::_get_translated_text(const String &p_text) const {
return atr(p_text);
}
void Button::_update_theme_item_cache() {
Control::_update_theme_item_cache();
@ -186,7 +190,7 @@ void Button::_notification(int p_what) {
} break;
case NOTIFICATION_TRANSLATION_CHANGED: {
xl_text = atr(text);
xl_text = _get_translated_text(text);
_shape();
update_minimum_size();
@ -598,14 +602,16 @@ TextServer::OverrunBehavior Button::get_text_overrun_behavior() const {
}
void Button::set_text(const String &p_text) {
if (text != p_text) {
text = p_text;
xl_text = atr(text);
_shape();
queue_redraw();
update_minimum_size();
const String translated_text = _get_translated_text(p_text);
if (text == p_text && xl_text == translated_text) {
return;
}
text = p_text;
xl_text = translated_text;
_shape();
queue_redraw();
update_minimum_size();
}
String Button::get_text() const {

View File

@ -110,6 +110,7 @@ protected:
void _set_internal_margin(Side p_side, float p_value);
virtual void _queue_update_size_cache();
virtual String _get_translated_text(const String &p_text) const;
Size2 _fit_icon_size(const Size2 &p_size) const;
Ref<StyleBox> _get_current_stylebox() const;

View File

@ -240,6 +240,21 @@ void OptionButton::set_item_tooltip(int p_idx, const String &p_tooltip) {
popup->set_item_tooltip(p_idx, p_tooltip);
}
void OptionButton::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode) {
if (p_idx < 0) {
p_idx += get_item_count();
}
if (popup->get_item_auto_translate_mode(p_idx) == p_mode) {
return;
}
popup->set_item_auto_translate_mode(p_idx, p_mode);
if (current == p_idx) {
set_text(popup->get_item_text(p_idx));
}
_queue_update_size_cache();
}
void OptionButton::set_item_disabled(int p_idx, bool p_disabled) {
popup->set_item_disabled(p_idx, p_disabled);
}
@ -272,6 +287,10 @@ String OptionButton::get_item_tooltip(int p_idx) const {
return popup->get_item_tooltip(p_idx);
}
Node::AutoTranslateMode OptionButton::get_item_auto_translate_mode(int p_idx) const {
return popup->get_item_auto_translate_mode(p_idx);
}
bool OptionButton::is_item_disabled(int p_idx) const {
return popup->is_item_disabled(p_idx);
}
@ -431,6 +450,25 @@ void OptionButton::_queue_update_size_cache() {
callable_mp(this, &OptionButton::_refresh_size_cache).call_deferred();
}
String OptionButton::_get_translated_text(const String &p_text) const {
if (0 <= current && current < popup->get_item_count()) {
AutoTranslateMode mode = popup->get_item_auto_translate_mode(current);
switch (mode) {
case AUTO_TRANSLATE_MODE_INHERIT: {
return atr(p_text);
} break;
case AUTO_TRANSLATE_MODE_ALWAYS: {
return tr(p_text);
} break;
case AUTO_TRANSLATE_MODE_DISABLED: {
return p_text;
} break;
}
ERR_FAIL_V_MSG(atr(p_text), "Unexpected auto translate mode: " + itos(mode));
}
return atr(p_text);
}
void OptionButton::select(int p_idx) {
_select(p_idx, false);
}
@ -510,12 +548,14 @@ void OptionButton::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_id", "idx", "id"), &OptionButton::set_item_id);
ClassDB::bind_method(D_METHOD("set_item_metadata", "idx", "metadata"), &OptionButton::set_item_metadata);
ClassDB::bind_method(D_METHOD("set_item_tooltip", "idx", "tooltip"), &OptionButton::set_item_tooltip);
ClassDB::bind_method(D_METHOD("set_item_auto_translate_mode", "idx", "mode"), &OptionButton::set_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_item_text", "idx"), &OptionButton::get_item_text);
ClassDB::bind_method(D_METHOD("get_item_icon", "idx"), &OptionButton::get_item_icon);
ClassDB::bind_method(D_METHOD("get_item_id", "idx"), &OptionButton::get_item_id);
ClassDB::bind_method(D_METHOD("get_item_index", "id"), &OptionButton::get_item_index);
ClassDB::bind_method(D_METHOD("get_item_metadata", "idx"), &OptionButton::get_item_metadata);
ClassDB::bind_method(D_METHOD("get_item_tooltip", "idx"), &OptionButton::get_item_tooltip);
ClassDB::bind_method(D_METHOD("get_item_auto_translate_mode", "idx"), &OptionButton::get_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("is_item_disabled", "idx"), &OptionButton::is_item_disabled);
ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &OptionButton::is_item_separator);
ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));

View File

@ -79,6 +79,7 @@ class OptionButton : public Button {
protected:
Size2 get_minimum_size() const override;
virtual void _queue_update_size_cache() override;
virtual String _get_translated_text(const String &p_text) const override;
void _notification(int p_what);
bool _set(const StringName &p_name, const Variant &p_value);
@ -105,6 +106,7 @@ public:
void set_item_metadata(int p_idx, const Variant &p_metadata);
void set_item_disabled(int p_idx, bool p_disabled);
void set_item_tooltip(int p_idx, const String &p_tooltip);
void set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode);
String get_item_text(int p_idx) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
@ -114,6 +116,7 @@ public:
bool is_item_disabled(int p_idx) const;
bool is_item_separator(int p_idx) const;
String get_item_tooltip(int p_idx) const;
AutoTranslateMode get_item_auto_translate_mode(int p_idx) const;
bool has_selectable_items() const;
int get_selectable_item(bool p_from_last = false) const;

View File

@ -1146,7 +1146,7 @@ void PopupMenu::_notification(int p_what) {
}
for (int i = 0; i < items.size(); i++) {
Item &item = items.write[i];
item.xl_text = atr(item.text);
item.xl_text = _atr(i, item.text);
item.dirty = true;
if (is_global) {
nmenu->set_item_text(global_menu, i, item.xl_text);
@ -1731,7 +1731,7 @@ void PopupMenu::set_item_text(int p_idx, const String &p_text) {
return;
}
items.write[p_idx].text = p_text;
items.write[p_idx].xl_text = atr(p_text);
items.write[p_idx].xl_text = _atr(p_idx, p_text);
items.write[p_idx].dirty = true;
if (global_menu.is_valid()) {
@ -1769,6 +1769,20 @@ void PopupMenu::set_item_language(int p_idx, const String &p_language) {
}
}
void PopupMenu::set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode) {
if (p_idx < 0) {
p_idx += get_item_count();
}
ERR_FAIL_INDEX(p_idx, items.size());
if (items[p_idx].auto_translate_mode == p_mode) {
return;
}
items.write[p_idx].auto_translate_mode = p_mode;
items.write[p_idx].xl_text = _atr(p_idx, items[p_idx].text);
items.write[p_idx].dirty = true;
control->queue_redraw();
}
void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
if (p_idx < 0) {
p_idx += get_item_count();
@ -2009,6 +2023,11 @@ String PopupMenu::get_item_language(int p_idx) const {
return items[p_idx].language;
}
Node::AutoTranslateMode PopupMenu::get_item_auto_translate_mode(int p_idx) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), AUTO_TRANSLATE_MODE_INHERIT);
return items[p_idx].auto_translate_mode;
}
int PopupMenu::get_item_idx_from_text(const String &text) const {
for (int idx = 0; idx < items.size(); idx++) {
if (items[idx].text == text) {
@ -2818,6 +2837,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_item_text", "index", "text"), &PopupMenu::set_item_text);
ClassDB::bind_method(D_METHOD("set_item_text_direction", "index", "direction"), &PopupMenu::set_item_text_direction);
ClassDB::bind_method(D_METHOD("set_item_language", "index", "language"), &PopupMenu::set_item_language);
ClassDB::bind_method(D_METHOD("set_item_auto_translate_mode", "index", "mode"), &PopupMenu::set_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("set_item_icon", "index", "icon"), &PopupMenu::set_item_icon);
ClassDB::bind_method(D_METHOD("set_item_icon_max_width", "index", "width"), &PopupMenu::set_item_icon_max_width);
ClassDB::bind_method(D_METHOD("set_item_icon_modulate", "index", "modulate"), &PopupMenu::set_item_icon_modulate);
@ -2844,6 +2864,7 @@ void PopupMenu::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_item_text", "index"), &PopupMenu::get_item_text);
ClassDB::bind_method(D_METHOD("get_item_text_direction", "index"), &PopupMenu::get_item_text_direction);
ClassDB::bind_method(D_METHOD("get_item_language", "index"), &PopupMenu::get_item_language);
ClassDB::bind_method(D_METHOD("get_item_auto_translate_mode", "index"), &PopupMenu::get_item_auto_translate_mode);
ClassDB::bind_method(D_METHOD("get_item_icon", "index"), &PopupMenu::get_item_icon);
ClassDB::bind_method(D_METHOD("get_item_icon_max_width", "index"), &PopupMenu::get_item_icon_max_width);
ClassDB::bind_method(D_METHOD("get_item_icon_modulate", "index"), &PopupMenu::get_item_icon_modulate);
@ -2983,6 +3004,23 @@ void PopupMenu::_native_popup(const Rect2i &p_rect) {
NativeMenu::get_singleton()->popup(global_menu, popup_pos);
}
String PopupMenu::_atr(int p_idx, const String &p_text) const {
ERR_FAIL_INDEX_V(p_idx, items.size(), atr(p_text));
switch (items[p_idx].auto_translate_mode) {
case AUTO_TRANSLATE_MODE_INHERIT: {
return atr(p_text);
} break;
case AUTO_TRANSLATE_MODE_ALWAYS: {
return tr(p_text);
} break;
case AUTO_TRANSLATE_MODE_DISABLED: {
return p_text;
} break;
}
ERR_FAIL_V_MSG(atr(p_text), "Unexpected auto translate mode: " + itos(items[p_idx].auto_translate_mode));
}
void PopupMenu::popup(const Rect2i &p_bounds) {
bool native = global_menu.is_valid();
#ifdef TOOLS_ENABLED

View File

@ -54,6 +54,7 @@ class PopupMenu : public Popup {
String language;
Control::TextDirection text_direction = Control::TEXT_DIRECTION_AUTO;
AutoTranslateMode auto_translate_mode = AUTO_TRANSLATE_MODE_INHERIT;
bool checked = false;
enum {
@ -215,6 +216,7 @@ class PopupMenu : public Popup {
void _set_item_checkable_type(int p_index, int p_checkable_type);
int _get_item_checkable_type(int p_index) const;
void _native_popup(const Rect2i &p_rect);
String _atr(int p_idx, const String &p_text) const;
protected:
virtual Rect2i _popup_adjust_rect() const override;
@ -279,6 +281,7 @@ public:
void set_item_text_direction(int p_idx, Control::TextDirection p_text_direction);
void set_item_language(int p_idx, const String &p_language);
void set_item_auto_translate_mode(int p_idx, AutoTranslateMode p_mode);
void set_item_icon(int p_idx, const Ref<Texture2D> &p_icon);
void set_item_icon_max_width(int p_idx, int p_width);
void set_item_icon_modulate(int p_idx, const Color &p_modulate);
@ -306,6 +309,7 @@ public:
String get_item_xl_text(int p_idx) const;
Control::TextDirection get_item_text_direction(int p_idx) const;
String get_item_language(int p_idx) const;
AutoTranslateMode get_item_auto_translate_mode(int p_idx) const;
int get_item_idx_from_text(const String &text) const;
Ref<Texture2D> get_item_icon(int p_idx) const;
int get_item_icon_max_width(int p_idx) const;