mirror of
https://github.com/OrcaSlicer/OrcaSlicer.git
synced 2026-09-27 10:51:22 +00:00
Get speed dial to work with translations
This commit is contained in:
@@ -346,6 +346,27 @@ void ActionRegistry::init()
|
||||
upsert(NativeCommands::make_action(c));
|
||||
}
|
||||
|
||||
void ActionRegistry::relocalize_builtins()
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
if (!m_started)
|
||||
return;
|
||||
|
||||
// Drop only the built-in commands; plugins and the dynamically materialised families are
|
||||
// either unlocalized or rebuilt per snapshot. remove() only erases the map entry, and upsert()
|
||||
// re-seeds favourites/stats from config, so key-based ids keep their pinned state.
|
||||
std::vector<std::string> stale;
|
||||
for (const auto& [id, action] : m_actions)
|
||||
if (action->source_key() == kOrcaSourceKey && action->kind == AppActionKind::Command)
|
||||
stale.push_back(id);
|
||||
for (const std::string& id : stale)
|
||||
remove(id);
|
||||
|
||||
NativeCommands::rebuild_catalog();
|
||||
for (const NativeCommand& c : NativeCommands::catalog())
|
||||
upsert(NativeCommands::make_action(c));
|
||||
}
|
||||
|
||||
void ActionRegistry::refresh_source(const std::string& plugin_key, ActionChange change)
|
||||
{
|
||||
assert(wxThread::IsMain());
|
||||
|
||||
@@ -159,6 +159,12 @@ public:
|
||||
// updates together.
|
||||
void init();
|
||||
|
||||
// Rebuilds the built-in command actions in the current UI locale. The command catalog copies
|
||||
// translated titles/groups at construction, so after a live language switch the stored titles
|
||||
// are stale until this runs. Ids are key-based and upsert re-seeds persisted state, so
|
||||
// favourites/run history survive. UI thread only. No-op before init().
|
||||
void relocalize_builtins();
|
||||
|
||||
// Takes ownership, seeds persisted state, then inserts the action or replaces
|
||||
// the action with the same id. A null action is ignored.
|
||||
void upsert(std::unique_ptr<AppAction> action);
|
||||
|
||||
@@ -4607,6 +4607,12 @@ void GUI_App::recreate_GUI(const wxString &msg_name)
|
||||
BOOST_LOG_TRIVIAL(info) << __FUNCTION__ << "recreate_GUI enter";
|
||||
m_is_recreating_gui = true;
|
||||
|
||||
// The palette injects its translated strings once, at creation; drop the cached dialog so the
|
||||
// next open rebuilds it in the current locale (and can't outlive the old mainframe).
|
||||
if (m_speed_dial_dialog) {
|
||||
m_speed_dial_dialog->Destroy();
|
||||
m_speed_dial_dialog = nullptr;
|
||||
}
|
||||
|
||||
mainframe->shutdown();
|
||||
ProgressDialog dlg(msg_name, msg_name, 100, nullptr, wxPD_AUTO_HIDE);
|
||||
@@ -8523,6 +8529,9 @@ void GUI_App::open_preferences(size_t open_on_tab, const std::string& highlight_
|
||||
this->plater_->get_current_canvas3D()->force_set_focus();
|
||||
return;
|
||||
}
|
||||
// Built-in Speed Dial command titles are copied from the catalog at init and don't follow a
|
||||
// live locale switch; rebuild them in the new language before the GUI (and palette) rebuilds.
|
||||
m_action_registry.relocalize_builtins();
|
||||
}
|
||||
|
||||
if (need_recreate_gui)
|
||||
|
||||
@@ -282,16 +282,17 @@ std::vector<NativeCommand> build_command_catalog()
|
||||
add_with_icon("calib_vfa", _u8L("VFA Calibration"), _u8L("Calibration"), "calib_sf", [](const std::string&) { return calib_command(CalibKind::VFA); });
|
||||
|
||||
// ---- View ----
|
||||
// Titles are built with _u8L here (not via a variable) so xgettext can extract them.
|
||||
for (auto [key, dir, title] :
|
||||
std::initializer_list<std::tuple<const char*, const char*, const char*>>{{"view_top", "top", "View: Top"},
|
||||
{"view_bottom", "bottom", "View: Bottom"},
|
||||
{"view_front", "front", "View: Front"},
|
||||
{"view_rear", "rear", "View: Rear"},
|
||||
{"view_left", "left", "View: Left"},
|
||||
{"view_right", "right", "View: Right"},
|
||||
{"view_iso", "iso", "View: Isometric"}}) {
|
||||
std::initializer_list<std::tuple<const char*, const char*, std::string>>{{"view_top", "top", _u8L("View: Top")},
|
||||
{"view_bottom", "bottom", _u8L("View: Bottom")},
|
||||
{"view_front", "front", _u8L("View: Front")},
|
||||
{"view_rear", "rear", _u8L("View: Rear")},
|
||||
{"view_left", "left", _u8L("View: Left")},
|
||||
{"view_right", "right", _u8L("View: Right")},
|
||||
{"view_iso", "iso", _u8L("View: Isometric")}}) {
|
||||
std::string k = key, d = dir;
|
||||
add(k, Slic3r::GUI::I18N::translate_utf8(title), _u8L("View"),
|
||||
add(k, title, _u8L("View"),
|
||||
[d](const std::string&) { return view_command(wxGetApp().plater(), d); });
|
||||
}
|
||||
add("view_default", _u8L("View: Default"), _u8L("View"), [](const std::string&) {
|
||||
@@ -626,12 +627,24 @@ std::vector<NativeCommand> build_command_catalog()
|
||||
return out;
|
||||
}
|
||||
|
||||
std::vector<NativeCommand>& catalog_storage()
|
||||
{
|
||||
static std::vector<NativeCommand> commands = build_command_catalog();
|
||||
return commands;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const std::vector<NativeCommand>& NativeCommands::catalog()
|
||||
{
|
||||
static const std::vector<NativeCommand> commands = build_command_catalog();
|
||||
return commands;
|
||||
return catalog_storage();
|
||||
}
|
||||
|
||||
void NativeCommands::rebuild_catalog()
|
||||
{
|
||||
// build_command_catalog() re-runs _u8L under the current locale, so replacing the storage
|
||||
// refreshes every translated title/group after a language switch.
|
||||
catalog_storage() = build_command_catalog();
|
||||
}
|
||||
|
||||
std::unique_ptr<AppAction> NativeCommands::make_action(const NativeCommand& command)
|
||||
|
||||
@@ -24,9 +24,13 @@ struct NativeCommand
|
||||
};
|
||||
|
||||
namespace NativeCommands {
|
||||
// The full built-in command catalog, built once on first use. UI thread only.
|
||||
// The full built-in command catalog. Built on first use and reused; call rebuild_catalog() after a
|
||||
// live UI language switch so the translated titles/groups match the new locale. UI thread only.
|
||||
const std::vector<NativeCommand>& catalog();
|
||||
|
||||
// Rebuilds the catalog in the current locale. UI thread only.
|
||||
void rebuild_catalog();
|
||||
|
||||
// Dispatches `key` to its runner (unknown keys return a quiet Info). UI thread only.
|
||||
AppActionRunResult run(const std::string& key, const std::string& param = {});
|
||||
|
||||
|
||||
@@ -67,6 +67,53 @@ void focus_webview(wxWebView* browser, bool page_ready)
|
||||
browser->RunScript("focusInput();");
|
||||
}
|
||||
|
||||
// Localized strings for the Speed Dial page, injected as a document-start user script. The page's
|
||||
// T() reads window.ORCA_UI_STRINGS, so these flow through the same .po pipeline as the rest of the
|
||||
// UI (the JS literals are only a fallback before the script runs / in the node vm test).
|
||||
// %% is a literal '%': T() collapses it after substituting %s. Keep the shortcut tokens out of the
|
||||
// translated text so the platform prefix (Alt+/⌥+, Ctrl+/⌘+) stays correct.
|
||||
nlohmann::json speed_dial_ui_strings()
|
||||
{
|
||||
const std::string alt = GUI::shortkey_alt_prefix();
|
||||
const std::string ctrl = GUI::shortkey_ctrl_prefix();
|
||||
return {
|
||||
{"shortcut_alt", alt},
|
||||
{"shortcut_ctrl", ctrl},
|
||||
|
||||
{"sd_search", _u8L("Search actions")},
|
||||
{"sd_clear", _u8L("Clear")},
|
||||
{"sd_search_n", _u8L("Search %s actions")},
|
||||
{"sd_recent", _u8L("Recent")},
|
||||
{"sd_plugins", _u8L("Plugins")},
|
||||
{"sd_other", _u8L("Other")},
|
||||
{"sd_no_match_total", _u8L("No actions match (Total: %s)")},
|
||||
{"sd_no_actions", _u8L("No actions yet")},
|
||||
{"sd_no_tabs_match", _u8L("No tabs match")},
|
||||
{"sd_no_tabs", _u8L("No tabs")},
|
||||
{"sd_result_count", _u8L("Showing %s of %s actions")},
|
||||
{"sd_result_count_all", _u8L("%s actions")},
|
||||
{"sd_tab_count", _u8L("%s tabs")},
|
||||
{"sd_tab_match_count", _u8L("%s matches")},
|
||||
{"sd_favs_full", _u8L("Favourites are full (%s max)")},
|
||||
{"sd_go_to_pct", _u8L("Go to %s%% of the layer range")},
|
||||
{"sd_enter_pct", _u8L("Enter a layer percentage (0-100)")},
|
||||
{"sd_go_layer_ph", _u8L("Go to layer %% (0-100)")},
|
||||
{"sd_go_tab_ph", _u8L("Go to tab")},
|
||||
{"sd_fav_slot", _u8L("Favourite %s (%s)")},
|
||||
{"sd_pin_fav", _u8L("Pin to favourites (%s)")},
|
||||
{"sd_unpin_fav", _u8L("Unpin from favourites (%s)")},
|
||||
{"sd_remove_fav", _u8L("Remove from favourites")},
|
||||
{"sd_move_left", _u8L("Move left")},
|
||||
{"sd_move_right", _u8L("Move right")},
|
||||
{"sd_unpin", _u8L("Unpin")},
|
||||
{"sd_mode_advanced", _u8L("Advanced")},
|
||||
{"sd_mode_expert", _u8L("Expert")},
|
||||
{"sd_mode_develop", _u8L("Developer")},
|
||||
{"sd_wiki_f1", _u8L("Wiki (F1)")},
|
||||
{"sd_no_wiki", _u8L("No wiki page for this action")},
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SpeedDialWebDialog::SpeedDialWebDialog(wxWindow* parent)
|
||||
@@ -99,6 +146,18 @@ SpeedDialWebDialog::SpeedDialWebDialog(wxWindow* parent)
|
||||
|
||||
SpeedDialWebDialog::~SpeedDialWebDialog() { m_alive->store(false, std::memory_order_release); }
|
||||
|
||||
// Document-start hook: hand the page its translated strings before speeddial.js runs, so the first
|
||||
// paint is already localized. The table is built when the dialog is created; a live language switch
|
||||
// rebuilds the GUI (and with it this dialog), so the next open re-injects the new locale.
|
||||
void SpeedDialWebDialog::add_user_scripts()
|
||||
{
|
||||
if (wxWebView* wv = browser()) {
|
||||
const std::string js = "window.ORCA_UI_STRINGS = " +
|
||||
speed_dial_ui_strings().dump(-1, ' ', false, nlohmann::json::error_handler_t::ignore) + ";";
|
||||
wv->AddUserScript(wxString::FromUTF8(js));
|
||||
}
|
||||
}
|
||||
|
||||
void SpeedDialWebDialog::request_show()
|
||||
{
|
||||
if (IsShown()) {
|
||||
|
||||
@@ -17,6 +17,7 @@ public:
|
||||
void request_show();
|
||||
|
||||
private:
|
||||
void add_user_scripts() override;
|
||||
void on_script_message(const nlohmann::json& payload) override;
|
||||
void handle_web_command(const nlohmann::json& payload);
|
||||
void resize_to_content(int height);
|
||||
|
||||
Reference in New Issue
Block a user