瀏覽代碼

Add auto translate mode for items in PopupMenu and OptionButton

Haoyu Qiu 4 月之前
父節點
當前提交
82a0290871

+ 16 - 0
doc/classes/OptionButton.xml

@@ -42,6 +42,13 @@
 				Clears all the items in the [OptionButton].
 				Clears all the items in the [OptionButton].
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="get_item_icon" qualifiers="const">
 			<return type="Texture2D" />
 			<return type="Texture2D" />
 			<param index="0" name="idx" type="int" />
 			<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.
 				If [code]true[/code], shortcuts are disabled and cannot be used to trigger the button.
 			</description>
 			</description>
 		</method>
 		</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">
 		<method name="set_item_disabled">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="idx" type="int" />
 			<param index="0" name="idx" type="int" />

+ 16 - 0
doc/classes/PopupMenu.xml

@@ -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]).
 				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>
 			</description>
 		</method>
 		</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">
 		<method name="get_item_icon" qualifiers="const">
 			<return type="Texture2D" />
 			<return type="Texture2D" />
 			<param index="0" name="index" type="int" />
 			<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.
 				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>
 			</description>
 		</method>
 		</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">
 		<method name="set_item_checked">
 			<return type="void" />
 			<return type="void" />
 			<param index="0" name="index" type="int" />
 			<param index="0" name="index" type="int" />

+ 3 - 1
editor/editor_node.cpp

@@ -4676,6 +4676,7 @@ void EditorNode::_update_recent_scenes() {
 
 
 	recent_scenes->add_separator();
 	recent_scenes->add_separator();
 	recent_scenes->add_shortcut(ED_SHORTCUT("editor/clear_recent", TTRC("Clear Recent Scenes")));
 	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();
 	recent_scenes->reset_size();
 }
 }
 
 
@@ -5625,6 +5626,7 @@ void EditorNode::_update_layouts_menu() {
 		}
 		}
 
 
 		editor_layouts->add_item(layout);
 		editor_layouts->add_item(layout);
+		editor_layouts->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED);
 	}
 	}
 }
 }
 
 
@@ -7443,6 +7445,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);
 	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 = 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);
 	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));
 	recent_scenes->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_open_recent_scene));
 
 
@@ -7572,7 +7575,6 @@ EditorNode::EditorNode() {
 	settings_menu->add_submenu_node_item(TTR("Editor Docks"), editor_dock_manager->get_docks_menu());
 	settings_menu->add_submenu_node_item(TTR("Editor Docks"), editor_dock_manager->get_docks_menu());
 
 
 	editor_layouts = memnew(PopupMenu);
 	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);
 	settings_menu->add_submenu_node_item(TTR("Editor Layout"), editor_layouts);
 	editor_layouts->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_layout_menu_option));
 	editor_layouts->connect(SceneStringName(id_pressed), callable_mp(this, &EditorNode::_layout_menu_option));
 	settings_menu->add_separator();
 	settings_menu->add_separator();

+ 3 - 1
editor/plugins/script_editor_plugin.cpp

@@ -824,7 +824,8 @@ void ScriptEditor::_update_recent_scripts() {
 
 
 	recent_scripts->add_separator();
 	recent_scripts->add_separator();
 	recent_scripts->add_shortcut(ED_GET_SHORTCUT("script_editor/clear_recent"));
 	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();
 	recent_scripts->reset_size();
 }
 }
@@ -4288,6 +4289,7 @@ ScriptEditor::ScriptEditor(WindowWrapper *p_wrapper) {
 	file_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/reopen_closed_script"), FILE_REOPEN_CLOSED);
 	file_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_editor/reopen_closed_script"), FILE_REOPEN_CLOSED);
 
 
 	recent_scripts = memnew(PopupMenu);
 	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);
 	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));
 	recent_scripts->connect(SceneStringName(id_pressed), callable_mp(this, &ScriptEditor::_open_recent_script));
 
 

+ 1 - 0
editor/scene_create_dialog.cpp

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

+ 10 - 6
editor/script_create_dialog.cpp

@@ -562,6 +562,7 @@ void ScriptCreateDialog::_update_template_menu() {
 					if (!separator) {
 					if (!separator) {
 						template_menu->add_separator();
 						template_menu->add_separator();
 						template_menu->set_item_text(-1, display_name);
 						template_menu->set_item_text(-1, display_name);
+						template_menu->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_ALWAYS);
 						separator = true;
 						separator = true;
 					}
 					}
 					for (ScriptLanguage::ScriptTemplate &t : templates_found) {
 					for (ScriptLanguage::ScriptTemplate &t : templates_found) {
@@ -666,7 +667,7 @@ void ScriptCreateDialog::_update_dialog() {
 			validation_panel->set_message(MSG_ID_PATH, TTR("Built-in script (into scene file)."), EditorValidationPanel::MSG_OK);
 			validation_panel->set_message(MSG_ID_PATH, TTR("Built-in script (into scene file)."), EditorValidationPanel::MSG_OK);
 		}
 		}
 	} else {
 	} else {
-		template_inactive_message = TTR("Using existing script file.");
+		template_inactive_message = TTRC("Using existing script file.");
 		if (load_enabled) {
 		if (load_enabled) {
 			if (is_path_valid) {
 			if (is_path_valid) {
 				validation_panel->set_message(MSG_ID_PATH, TTR("Will load an existing script file."), EditorValidationPanel::MSG_OK);
 				validation_panel->set_message(MSG_ID_PATH, TTR("Will load an existing script file."), EditorValidationPanel::MSG_OK);
@@ -680,16 +681,17 @@ void ScriptCreateDialog::_update_dialog() {
 	if (is_using_templates) {
 	if (is_using_templates) {
 		// Check if at least one suitable template has been found.
 		// Check if at least one suitable template has been found.
 		if (template_menu->get_item_count() == 0 && template_inactive_message.is_empty()) {
 		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 {
 	} else {
-		template_inactive_message = TTR("Empty");
+		template_inactive_message = TTRC("Empty");
 	}
 	}
 
 
 	if (!template_inactive_message.is_empty()) {
 	if (!template_inactive_message.is_empty()) {
 		template_menu->set_disabled(true);
 		template_menu->set_disabled(true);
 		template_menu->clear();
 		template_menu->clear();
 		template_menu->add_item(template_inactive_message);
 		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);
 		validation_panel->set_message(MSG_ID_TEMPLATE, "", EditorValidationPanel::MSG_INFO);
 	}
 	}
 }
 }
@@ -817,11 +819,11 @@ ScriptLanguage::ScriptTemplate ScriptCreateDialog::_parse_template(const ScriptL
 String ScriptCreateDialog::_get_script_origin_label(const ScriptLanguage::TemplateLocation &p_origin) const {
 String ScriptCreateDialog::_get_script_origin_label(const ScriptLanguage::TemplateLocation &p_origin) const {
 	switch (p_origin) {
 	switch (p_origin) {
 		case ScriptLanguage::TEMPLATE_BUILT_IN:
 		case ScriptLanguage::TEMPLATE_BUILT_IN:
-			return TTR("Built-in");
+			return TTRC("Built-in");
 		case ScriptLanguage::TEMPLATE_EDITOR:
 		case ScriptLanguage::TEMPLATE_EDITOR:
-			return TTR("Editor");
+			return TTRC("Editor");
 		case ScriptLanguage::TEMPLATE_PROJECT:
 		case ScriptLanguage::TEMPLATE_PROJECT:
-			return TTR("Project");
+			return TTRC("Project");
 	}
 	}
 	return "";
 	return "";
 }
 }
@@ -864,6 +866,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
 	/* Language */
 	/* Language */
 
 
 	language_menu = memnew(OptionButton);
 	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_custom_minimum_size(Size2(350, 0) * EDSCALE);
 	language_menu->set_expand_icon(true);
 	language_menu->set_expand_icon(true);
 	language_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	language_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
@@ -917,6 +920,7 @@ ScriptCreateDialog::ScriptCreateDialog() {
 	template_inactive_message = "";
 	template_inactive_message = "";
 
 
 	template_menu = memnew(OptionButton);
 	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->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	template_menu->connect(SceneStringName(item_selected), callable_mp(this, &ScriptCreateDialog::_template_changed));
 	template_menu->connect(SceneStringName(item_selected), callable_mp(this, &ScriptCreateDialog::_template_changed));
 	template_hb->add_child(template_menu);
 	template_hb->add_child(template_menu);

+ 5 - 3
editor/shader_create_dialog.cpp

@@ -312,13 +312,13 @@ void ShaderCreateDialog::_type_changed(int p_language) {
 	if (shader_type_data.use_templates) {
 	if (shader_type_data.use_templates) {
 		int last_template = EditorSettings::get_singleton()->get_project_metadata("shader_setup", "last_selected_template", 0);
 		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);
 		template_menu->select(last_template);
 		current_template = last_template;
 		current_template = last_template;
 	} else {
 	} 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()));
 	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.
 
 
 	type_menu = memnew(OptionButton);
 	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_custom_minimum_size(Size2(250, 0) * EDSCALE);
 	type_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	type_menu->set_h_size_flags(Control::SIZE_EXPAND_FILL);
 	gc->add_child(memnew(Label(TTR("Type:"))));
 	gc->add_child(memnew(Label(TTR("Type:"))));
@@ -612,6 +613,7 @@ ShaderCreateDialog::ShaderCreateDialog() {
 	// Modes.
 	// Modes.
 
 
 	mode_menu = memnew(OptionButton);
 	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()) {
 	for (const String &type_name : ShaderTypes::get_singleton()->get_types_list()) {
 		mode_menu->add_item(type_name.capitalize());
 		mode_menu->add_item(type_name.capitalize());
 	}
 	}

+ 14 - 8
scene/gui/button.cpp

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

+ 1 - 0
scene/gui/button.h

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

+ 40 - 0
scene/gui/option_button.cpp

@@ -240,6 +240,21 @@ void OptionButton::set_item_tooltip(int p_idx, const String &p_tooltip) {
 	popup->set_item_tooltip(p_idx, 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) {
 void OptionButton::set_item_disabled(int p_idx, bool p_disabled) {
 	popup->set_item_disabled(p_idx, 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);
 	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 {
 bool OptionButton::is_item_disabled(int p_idx) const {
 	return popup->is_item_disabled(p_idx);
 	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();
 	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) {
 void OptionButton::select(int p_idx) {
 	_select(p_idx, false);
 	_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_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_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_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_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_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_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_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_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_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_disabled", "idx"), &OptionButton::is_item_disabled);
 	ClassDB::bind_method(D_METHOD("is_item_separator", "idx"), &OptionButton::is_item_separator);
 	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()));
 	ClassDB::bind_method(D_METHOD("add_separator", "text"), &OptionButton::add_separator, DEFVAL(String()));

+ 3 - 0
scene/gui/option_button.h

@@ -79,6 +79,7 @@ class OptionButton : public Button {
 protected:
 protected:
 	Size2 get_minimum_size() const override;
 	Size2 get_minimum_size() const override;
 	virtual void _queue_update_size_cache() override;
 	virtual void _queue_update_size_cache() override;
+	virtual String _get_translated_text(const String &p_text) const override;
 
 
 	void _notification(int p_what);
 	void _notification(int p_what);
 	bool _set(const StringName &p_name, const Variant &p_value);
 	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_metadata(int p_idx, const Variant &p_metadata);
 	void set_item_disabled(int p_idx, bool p_disabled);
 	void set_item_disabled(int p_idx, bool p_disabled);
 	void set_item_tooltip(int p_idx, const String &p_tooltip);
 	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;
 	String get_item_text(int p_idx) const;
 	Ref<Texture2D> get_item_icon(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_disabled(int p_idx) const;
 	bool is_item_separator(int p_idx) const;
 	bool is_item_separator(int p_idx) const;
 	String get_item_tooltip(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;
 	bool has_selectable_items() const;
 	int get_selectable_item(bool p_from_last = false) const;
 	int get_selectable_item(bool p_from_last = false) const;

+ 40 - 2
scene/gui/popup_menu.cpp

@@ -1146,7 +1146,7 @@ void PopupMenu::_notification(int p_what) {
 			}
 			}
 			for (int i = 0; i < items.size(); i++) {
 			for (int i = 0; i < items.size(); i++) {
 				Item &item = items.write[i];
 				Item &item = items.write[i];
-				item.xl_text = atr(item.text);
+				item.xl_text = _atr(i, item.text);
 				item.dirty = true;
 				item.dirty = true;
 				if (is_global) {
 				if (is_global) {
 					nmenu->set_item_text(global_menu, i, item.xl_text);
 					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;
 		return;
 	}
 	}
 	items.write[p_idx].text = p_text;
 	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;
 	items.write[p_idx].dirty = true;
 
 
 	if (global_menu.is_valid()) {
 	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) {
 void PopupMenu::set_item_icon(int p_idx, const Ref<Texture2D> &p_icon) {
 	if (p_idx < 0) {
 	if (p_idx < 0) {
 		p_idx += get_item_count();
 		p_idx += get_item_count();
@@ -2009,6 +2023,11 @@ String PopupMenu::get_item_language(int p_idx) const {
 	return items[p_idx].language;
 	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 {
 int PopupMenu::get_item_idx_from_text(const String &text) const {
 	for (int idx = 0; idx < items.size(); idx++) {
 	for (int idx = 0; idx < items.size(); idx++) {
 		if (items[idx].text == text) {
 		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", "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_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_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", "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_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);
 	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", "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_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_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", "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_max_width", "index"), &PopupMenu::get_item_icon_max_width);
 	ClassDB::bind_method(D_METHOD("get_item_icon_modulate", "index"), &PopupMenu::get_item_icon_modulate);
 	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);
 	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) {
 void PopupMenu::popup(const Rect2i &p_bounds) {
 	bool native = global_menu.is_valid();
 	bool native = global_menu.is_valid();
 #ifdef TOOLS_ENABLED
 #ifdef TOOLS_ENABLED

+ 4 - 0
scene/gui/popup_menu.h

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