From 3970ccabf08d6e0d1d32061ac23efeb591dc517a Mon Sep 17 00:00:00 2001 From: kobewi Date: Wed, 13 May 2026 22:45:15 +0200 Subject: [PATCH] Add pseudolocalization preview to the editor --- editor/editor_node.cpp | 18 +++++++++---- editor/editor_node.h | 3 ++- .../editor_translation_preview_button.cpp | 13 +++++++--- .../editor_translation_preview_menu.cpp | 26 ++++++++++++++++--- .../editor_translation_preview_menu.h | 2 ++ 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 6af8f2d749..ada0279cd8 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1036,7 +1036,7 @@ void EditorNode::_notification(int p_what) { // Remember the selected locale to preview node translations. const String preview_locale = EditorSettings::get_singleton()->get_project_metadata("editor_metadata", "preview_locale", String()); if (!preview_locale.is_empty() && TranslationServer::get_singleton()->has_translation_for_locale(preview_locale, true)) { - set_preview_locale(preview_locale); + set_preview_locale(preview_locale, false); } if (Engine::get_singleton()->is_recovery_mode_hint()) { @@ -4616,9 +4616,15 @@ String EditorNode::get_preview_locale() const { return main_domain->is_enabled() ? main_domain->get_locale_override() : String(); } -void EditorNode::set_preview_locale(const String &p_locale) { +bool EditorNode::is_pseudolocalization_enabled() const { + const Ref &main_domain = TranslationServer::get_singleton()->get_main_domain(); + return main_domain->is_pseudolocalization_enabled(); +} + +void EditorNode::set_preview_locale(const String &p_locale, bool p_pseudolocalization) { const String &prev_locale = get_preview_locale(); - if (prev_locale == p_locale) { + bool prev_pseudo = is_pseudolocalization_enabled(); + if (prev_locale == p_locale && prev_pseudo == p_pseudolocalization) { return; } @@ -4627,13 +4633,15 @@ void EditorNode::set_preview_locale(const String &p_locale) { Ref main_domain = TranslationServer::get_singleton()->get_main_domain(); if (p_locale.is_empty()) { // Disable preview. Use the fallback locale. - main_domain->set_enabled(false); main_domain->set_locale_override(TranslationServer::get_singleton()->get_fallback_locale()); } else { // Preview a specific locale. - main_domain->set_enabled(true); main_domain->set_locale_override(p_locale); } + main_domain->set_pseudolocalization_enabled(p_pseudolocalization); + // Texts set in the editor could be identifiers that should never be translated. + // So we need to disable translation entirely. + main_domain->set_enabled(p_pseudolocalization || !p_locale.is_empty()); EditorSettings::get_singleton()->set_project_metadata("editor_metadata", "preview_locale", p_locale); diff --git a/editor/editor_node.h b/editor/editor_node.h index f683e13f16..e8ae3bdcc6 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -863,7 +863,8 @@ public: Node *get_edited_scene() { return editor_data.get_edited_scene_root(); } String get_preview_locale() const; - void set_preview_locale(const String &p_locale); + bool is_pseudolocalization_enabled() const; + void set_preview_locale(const String &p_locale, bool p_pseudolocalization); int new_scene(); Error load_scene(const String &p_scene, bool p_ignore_broken_deps = false, bool p_set_inherited = false, bool p_force_open_imported = false, bool p_update_tabs = true); diff --git a/editor/translations/editor_translation_preview_button.cpp b/editor/translations/editor_translation_preview_button.cpp index 655bb1c1e5..d6f4ab9ac5 100644 --- a/editor/translations/editor_translation_preview_button.cpp +++ b/editor/translations/editor_translation_preview_button.cpp @@ -36,19 +36,24 @@ void EditorTranslationPreviewButton::_update() { const String &locale = EditorNode::get_singleton()->get_preview_locale(); + bool pseudo = EditorNode::get_singleton()->is_pseudolocalization_enabled(); - if (locale.is_empty()) { + if (!pseudo && locale.is_empty()) { hide(); return; } - const String name = TranslationServer::get_singleton()->get_locale_name(locale); - set_text(vformat(TTR("Previewing: %s"), name == locale ? locale : name + " [" + locale + "]")); + if (!locale.is_empty() && locale != TranslationServer::get_singleton()->get_fallback_locale()) { + const String name = TranslationServer::get_singleton()->get_locale_name(locale); + set_text(vformat(TTR("Previewing: %s"), name == locale ? locale : name + " [" + locale + "]") + (pseudo ? " (" + TTR("pseudolocalized") + ")" : "")); + } else { + set_text(TTR("Previewing pseudolocalization")); + } show(); } void EditorTranslationPreviewButton::pressed() { - EditorNode::get_singleton()->set_preview_locale(String()); + EditorNode::get_singleton()->set_preview_locale(String(), false); } void EditorTranslationPreviewButton::_notification(int p_what) { diff --git a/editor/translations/editor_translation_preview_menu.cpp b/editor/translations/editor_translation_preview_menu.cpp index 6a33d00944..d539ae16de 100644 --- a/editor/translations/editor_translation_preview_menu.cpp +++ b/editor/translations/editor_translation_preview_menu.cpp @@ -42,10 +42,13 @@ void EditorTranslationPreviewMenu::_prepare() { add_radio_check_item(TTRC("None")); set_item_metadata(-1, ""); - if (current_preview_locale.is_empty()) { + if (current_preview_locale.is_empty() || current_preview_locale == TranslationServer::get_singleton()->get_fallback_locale()) { set_item_checked(-1, true); } + add_check_item(TTRC("Pseudolocalization"), MENU_ID_PSEUDOLOCALIZATION); + set_item_checked(-1, EditorNode::get_singleton()->is_pseudolocalization_enabled()); + add_separator(); const Vector locales = TranslationServer::get_singleton()->get_loaded_locales(); @@ -68,10 +71,25 @@ void EditorTranslationPreviewMenu::_prepare() { } void EditorTranslationPreviewMenu::_pressed(int p_index) { - for (int i = 0; i < get_item_count(); i++) { - set_item_checked(i, i == p_index); + const String locale = get_item_metadata(p_index); + + int pseudo_index = get_item_index(MENU_ID_PSEUDOLOCALIZATION); + if (p_index == pseudo_index) { + bool pseudo_enabled = !is_item_checked(pseudo_index); + set_item_checked(pseudo_index, pseudo_enabled); + + const String preview_locale = EditorNode::get_singleton()->get_preview_locale(); + EditorNode::get_singleton()->set_preview_locale( + preview_locale == TranslationServer::get_singleton()->get_fallback_locale() ? String() : preview_locale, + pseudo_enabled); + } else { + for (int i = 0; i < get_item_count(); i++) { + if (i != pseudo_index) { + set_item_checked(i, i == p_index); + } + } + EditorNode::get_singleton()->set_preview_locale(locale, EditorNode::get_singleton()->is_pseudolocalization_enabled()); } - EditorNode::get_singleton()->set_preview_locale(get_item_metadata(p_index)); } void EditorTranslationPreviewMenu::_notification(int p_what) { diff --git a/editor/translations/editor_translation_preview_menu.h b/editor/translations/editor_translation_preview_menu.h index 838e78aad1..714f4c408c 100644 --- a/editor/translations/editor_translation_preview_menu.h +++ b/editor/translations/editor_translation_preview_menu.h @@ -35,6 +35,8 @@ class EditorTranslationPreviewMenu : public PopupMenu { GDCLASS(EditorTranslationPreviewMenu, PopupMenu); + static constexpr int MENU_ID_PSEUDOLOCALIZATION = 1; + void _prepare(); void _pressed(int p_index);