From 804bd47bc6d65d899b17f19318db82f4b12b3d3a Mon Sep 17 00:00:00 2001 From: Andrew Belt Date: Wed, 27 Nov 2024 17:05:37 -0500 Subject: [PATCH] Move English from most app classes to translation file. Add translated names for getKeyName() and getKeyCommandName(). --- include/widget/event.hpp | 11 ++++++ src/app/Browser.cpp | 49 +++++++++++++----------- src/app/MenuBar.cpp | 2 +- src/app/ParamWidget.cpp | 10 ++--- src/app/PortWidget.cpp | 24 ++++++------ src/plugin/Model.cpp | 30 +++++++-------- src/widget/event.cpp | 49 ++++++++++++++---------- translations/en.json | 82 +++++++++++++++++++++++++++++++++++++++- 8 files changed, 181 insertions(+), 76 deletions(-) diff --git a/include/widget/event.hpp b/include/widget/event.hpp index 18815af4..72e5e06d 100644 --- a/include/widget/event.hpp +++ b/include/widget/event.hpp @@ -43,7 +43,18 @@ namespace widget { struct Widget; +/** Returns the name of a GLFW key macro. +Printable keys return the key string such as "Q", "=", "\t", etc. +Letters are capitalized. +Does not remap keys based on keyboard layout, so GLFW_KEY_Q always returns "Q". +GLFW_KEY_SPACE returns "Space" translated to the current language. +Non-printable characters return the name of the key in the current language. +Key 0 returns "". +*/ std::string getKeyName(int key); +/** Returns the name of a key command/chord/combo. +For example, getKeyCommandName(GLFW_KEY_Q, GLFW_MOD_CONTROL) == "Ctrl+Q" translated to the current language. +*/ std::string getKeyCommandName(int key, int mods = 0); diff --git a/src/app/Browser.cpp b/src/app/Browser.cpp index 5deae484..6cce14e3 100644 --- a/src/app/Browser.cpp +++ b/src/app/Browser.cpp @@ -82,7 +82,7 @@ static ModuleWidget* chooseModel(plugin::Model* model) { mi.lastAdded = system::getUnixTime(); history::ComplexAction* h = new history::ComplexAction; - h->name = "add module"; + h->name = string::translate("Browser.history.addModule"); // Create Module and ModuleWidget INFO("Creating module %s", model->getFullName().c_str()); @@ -306,7 +306,8 @@ struct ModelBox : widget::OpaqueWidget { text += "\n" + model->description; } // Tags - text += "\n\nTags: "; + text += "\n\n"; + text += string::translate("Browser.tooltipTags"); std::vector tags; for (int tagId : model->tagIds) { tags.push_back(tag::getTag(tagId)); @@ -411,13 +412,15 @@ struct TagButton : ui::ChoiceButton { }; -static const std::string sortNames[] = { - "Last updated", - "Last used", - "Most used", - "Brand", - "Module name", - "Random", +static std::vector getSortNames() { + return { + string::translate("Browser.sort.lastUpdated"), + string::translate("Browser.sort.lastUsed"), + string::translate("Browser.sort.mostUsed"), + string::translate("Browser.sort.brand"), + string::translate("Browser.sort.moduleName"), + string::translate("Browser.sort.random"), + }; }; @@ -427,8 +430,8 @@ struct SortButton : ui::ChoiceButton { void onAction(const ActionEvent& e) override; void step() override { - text = "Sort: "; - text += sortNames[settings::browserSort]; + text = string::translate("Browser.sort"); + text += getSortNames()[settings::browserSort]; ChoiceButton::step(); } }; @@ -440,7 +443,7 @@ struct ZoomButton : ui::ChoiceButton { void onAction(const ActionEvent& e) override; void step() override { - text = "Zoom: "; + text = string::translate("Browser.zoom"); text += string::f("%.0f%%", std::pow(2.f, settings::browserZoom) * 100.f); ChoiceButton::step(); } @@ -492,7 +495,7 @@ struct Browser : widget::OpaqueWidget { searchField = new BrowserSearchField; searchField->box.size.x = 150; - searchField->placeholder = "Search modules"; + searchField->placeholder = string::translate("Browser.searchModules"); searchField->browser = this; headerLayout->addChild(searchField); @@ -511,13 +514,13 @@ struct Browser : widget::OpaqueWidget { favoriteButton = new ui::OptionButton; favoriteButton->quantity = favoriteQuantity; - favoriteButton->text = "Favorites"; + favoriteButton->text = string::translate("Browser.favorites"); favoriteButton->box.size.x = 70; headerLayout->addChild(favoriteButton); clearButton = new ClearButton; clearButton->box.size.x = 100; - clearButton->text = "Reset filters"; + clearButton->text = string::translate("Browser.resetFilters"); clearButton->browser = this; headerLayout->addChild(clearButton); @@ -537,7 +540,7 @@ struct Browser : widget::OpaqueWidget { UrlButton* libraryButton = new UrlButton; libraryButton->box.size.x = 150; - libraryButton->text = "Browse VCV Library"; + libraryButton->text = string::translate("Browser.browseLibrary"); libraryButton->url = "https://library.vcvrack.com/"; headerLayout->addChild(libraryButton); @@ -776,7 +779,7 @@ struct Browser : widget::OpaqueWidget { if (w->isVisible()) count++; } - countLabel->text = string::f("%d %s", count, (count == 1) ? "module" : "modules"); + countLabel->text = (count == 1) ? string::translate("Browser.modulesOne") : string::f(string::translate("Browser.modulesMany"), count); } void clear() { @@ -892,7 +895,7 @@ inline void BrandButton::onAction(const ActionEvent& e) { menu->box.size.x = box.size.x; BrandItem* noneItem = new BrandItem; - noneItem->text = "All brands"; + noneItem->text = string::translate("Browser.allBrands"); noneItem->brand = ""; noneItem->browser = browser; menu->addChild(noneItem); @@ -916,7 +919,7 @@ inline void BrandButton::onAction(const ActionEvent& e) { } inline void BrandButton::step() { - text = "Brand"; + text = string::translate("Browser.brand"); if (!browser->brand.empty()) { text += ": "; text += browser->brand; @@ -975,12 +978,12 @@ inline void TagButton::onAction(const ActionEvent& e) { menu->box.size.x = box.size.x; TagItem* noneItem = new TagItem; - noneItem->text = "All tags"; + noneItem->text = string::translate("Browser.allTags"); noneItem->tagId = -1; noneItem->browser = browser; menu->addChild(noneItem); - menu->addChild(createMenuLabel(RACK_MOD_CTRL_NAME "+click to select multiple")); + menu->addChild(createMenuLabel(widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("Browser.tagsSelectMultiple"))); menu->addChild(new ui::MenuSeparator); for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) { @@ -994,7 +997,7 @@ inline void TagButton::onAction(const ActionEvent& e) { } inline void TagButton::step() { - text = "Tags"; + text = string::translate("Browser.tags"); if (!browser->tagIds.empty()) { text += ": "; bool firstTag = true; @@ -1015,7 +1018,7 @@ inline void SortButton::onAction(const ActionEvent& e) { menu->box.size.x = box.size.x; for (int sortId = 0; sortId <= settings::BROWSER_SORT_RANDOM; sortId++) { - menu->addChild(createCheckMenuItem(sortNames[sortId], "", + menu->addChild(createCheckMenuItem(getSortNames()[sortId], "", [=]() {return settings::browserSort == sortId;}, [=]() { settings::browserSort = (settings::BrowserSort) sortId; diff --git a/src/app/MenuBar.cpp b/src/app/MenuBar.cpp index 9dff32a4..74155aeb 100644 --- a/src/app/MenuBar.cpp +++ b/src/app/MenuBar.cpp @@ -1094,7 +1094,7 @@ struct InfoLabel : ui::Label { double fps = std::isfinite(frameDurationAvg) ? 1.0 / frameDurationAvg : 0.0; double meterAverage = APP->engine->getMeterAverage(); double meterMax = APP->engine->getMeterMax(); - text += string::f("%.1f fps %.1f%% avg %.1f%% max", fps, meterAverage * 100, meterMax * 100); + text += string::f(string::translate("MenuBar.infoLabel"), fps, meterAverage * 100, meterMax * 100); text += " "; } diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index 80c8e5aa..a255b9d1 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -76,7 +76,7 @@ struct ParamValueItem : ui::MenuItem { if (oldValue != newValue) { // Push ParamChange history action history::ParamChange* h = new history::ParamChange; - h->name = "set parameter"; + h->name = string::translate("ParamWidget.history.setParam"); h->moduleId = paramWidget->module->id; h->paramId = paramWidget->paramId; h->oldValue = oldValue; @@ -274,20 +274,20 @@ void ParamWidget::createContextMenu() { // Initialize if (pq && pq->resetEnabled && pq->isBounded()) { - menu->addChild(createMenuItem("Initialize", switchQuantity ? "" : "Double-click", [=]() { + menu->addChild(createMenuItem(string::translate("ParamWidget.initialize"), switchQuantity ? "" : string::translate("ParamWidget.doubleClick"), [=]() { this->resetAction(); })); } // Fine if (!switchQuantity) { - menu->addChild(createMenuItem("Fine adjust", RACK_MOD_CTRL_NAME "+drag", NULL, true)); + menu->addChild(createMenuItem(string::translate("ParamWidget.fine"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("ParamWidget.fineDrag"), NULL, true)); } // Unmap engine::ParamHandle* paramHandle = module ? APP->engine->getParamHandle(module->id, paramId) : NULL; if (paramHandle) { - menu->addChild(createMenuItem("Unmap", paramHandle->text, [=]() { + menu->addChild(createMenuItem(string::translate("ParamWidget.unmap"), paramHandle->text, [=]() { APP->engine->updateParamHandle(paramHandle, -1, 0); })); } @@ -306,7 +306,7 @@ void ParamWidget::resetAction() { if (oldValue != newValue) { // Push ParamChange history action history::ParamChange* h = new history::ParamChange; - h->name = "reset parameter"; + h->name = string::translate("ParamWidget.history.reset"); h->moduleId = module->id; h->paramId = paramId; h->oldValue = oldValue; diff --git a/src/app/PortWidget.cpp b/src/app/PortWidget.cpp index e12d84d1..ebb18983 100644 --- a/src/app/PortWidget.cpp +++ b/src/app/PortWidget.cpp @@ -61,14 +61,14 @@ struct PortTooltip : ui::Tooltip { continue; text += "\n"; if (portWidget->type == engine::Port::INPUT) - text += "From "; + text += string::translate("PortWidget.from"); else - text += "To "; + text += string::translate("PortWidget.to"); text += otherPw->module->model->getFullName(); text += ": "; text += otherPw->getPortInfo()->getName(); text += " "; - text += (otherPw->type == engine::Port::INPUT) ? "input" : "output"; + text += (otherPw->type == engine::Port::INPUT) ? string::translate("PortWidget.input") : string::translate("PortWidget.output"); } } Tooltip::step(); @@ -140,11 +140,11 @@ struct PortCableItem : ui::ColorDotMenuItem { ui::Menu* createChildMenu() override { ui::Menu* menu = new ui::Menu; - // menu->addChild(createMenuLabel(string::f("ID: %ld", cw->cable->id))); + // menu->addChild(createMenuLabel(string::f(string::translate("PortWidget.cableId"), cw->cable->id))); for (NVGcolor color : settings::cableColors) { // Include extra leading spaces for the color circle - CableColorItem* item = createMenuItem("Set color"); + CableColorItem* item = createMenuItem(string::translate("PortWidget.setColor")); item->disabled = color::isEqual(color, cw->color); item->cw = cw; item->color = color; @@ -286,7 +286,7 @@ void PortWidget::createContextMenu() { std::vector cws = APP->scene->rack->getCompleteCablesOnPort(this); CableWidget* topCw = cws.empty() ? NULL : cws.back(); - menu->addChild(createMenuItem("Delete top cable", RACK_MOD_SHIFT_NAME "+click", + menu->addChild(createMenuItem(string::translate("PortWidget.deleteTopCable"), widget::getKeyCommandName(0, RACK_MOD_SHIFT) + string::translate("PortWidget.click"), [=]() { if (!weakThis) return; @@ -296,7 +296,7 @@ void PortWidget::createContextMenu() { )); { - PortCloneCableItem* item = createMenuItem("Duplicate top cable", RACK_MOD_CTRL_NAME "+Shift+drag"); + PortCloneCableItem* item = createMenuItem(string::translate("PortWidget.cloneTopCable"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("PortWidget.drag")); item->disabled = !topCw; item->pw = this; item->cw = topCw; @@ -304,7 +304,7 @@ void PortWidget::createContextMenu() { } { - PortCreateCableItem* item = createMenuItem("Create cable on top", RACK_MOD_CTRL_NAME "+drag"); + PortCreateCableItem* item = createMenuItem(string::translate("PortWidget.createCableTop"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("PortWidget.drag")); item->pw = this; menu->addChild(item); } @@ -317,7 +317,7 @@ void PortWidget::createContextMenu() { std::string label = get(settings::cableLabels, colorId); if (label == "") label = string::f("#%lld", (long long) (colorId + 1)); - PortCreateCableColorItem* item = createMenuItem("Create cable: " + label); + PortCreateCableColorItem* item = createMenuItem(string::translate("PortWidget.createCable") + label); item->pw = this; item->color = color; item->colorId = colorId; @@ -326,7 +326,7 @@ void PortWidget::createContextMenu() { if (!cws.empty()) { menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel("Click+drag to grab cable")); + menu->addChild(createMenuLabel(string::translate("PortWidget.clickDrag") + string::translate("PortWidget.grabCable"))); // Cable items for (auto it = cws.rbegin(); it != cws.rend(); it++) { @@ -342,7 +342,7 @@ void PortWidget::createContextMenu() { } if (cws.size() > 1) { - PortAllCablesItem* item = createMenuItem("All cables"); + PortAllCablesItem* item = createMenuItem(string::translate("PortWidget.allCables")); item->pw = this; item->cws = cws; menu->addChild(item); @@ -431,7 +431,7 @@ void PortWidget::onDragStart(const DragStartEvent& e) { internal->history = NULL; } internal->history = new history::ComplexAction; - internal->history->name = "move cable"; + internal->history->name = string::translate("PortWidget.history.moveCable"); std::vector cws; int mods = APP->window->getMods(); diff --git a/src/plugin/Model.cpp b/src/plugin/Model.cpp index 20d92be7..2d0c8752 100644 --- a/src/plugin/Model.cpp +++ b/src/plugin/Model.cpp @@ -106,16 +106,16 @@ std::string Model::getManualUrl() { void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { // plugin - menu->addChild(createMenuItem("Plugin: " + plugin->name, "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.plugin") + plugin->name, "", [=]() { system::openBrowser(plugin->pluginUrl); }, plugin->pluginUrl == "")); // version - menu->addChild(createMenuLabel("Version: " + plugin->version)); + menu->addChild(createMenuLabel(string::translate("Model.version") + plugin->version)); // author if (plugin->author != "") { - menu->addChild(createMenuItem("Author: " + plugin->author, "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.author") + plugin->author, "", [=]() { system::openBrowser(plugin->authorUrl); }, plugin->authorUrl.empty())); } @@ -123,17 +123,17 @@ void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { // license std::string license = plugin->license; if (string::startsWith(license, "https://") || string::startsWith(license, "http://")) { - menu->addChild(createMenuItem("License: Open in browser", "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.licenseBrowser"), "", [=]() { system::openBrowser(license); })); } else if (license != "") { - menu->addChild(createMenuLabel("License: " + license)); + menu->addChild(createMenuLabel(string::translate("Model.license") + license)); } // tags if (!tagIds.empty()) { - menu->addChild(createMenuLabel("Tags:")); + menu->addChild(createMenuLabel(string::translate("Model.tags"))); for (int tagId : tagIds) { menu->addChild(createMenuLabel("• " + tag::getTag(tagId))); } @@ -142,13 +142,13 @@ void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { menu->addChild(new ui::MenuSeparator); // VCV Library page - menu->addChild(createMenuItem("VCV Library page", "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.library"), "", [=]() { system::openBrowser("https://library.vcvrack.com/" + plugin->slug + "/" + slug); })); // modularGridUrl if (modularGridUrl != "") { - menu->addChild(createMenuItem("ModularGrid page", "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.modularGrid"), "", [=]() { system::openBrowser(modularGridUrl); })); } @@ -156,42 +156,42 @@ void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { // manual std::string manualUrl = getManualUrl(); if (manualUrl != "") { - menu->addChild(createMenuItem("User manual", widget::getKeyCommandName(GLFW_KEY_F1, RACK_MOD_CTRL), [=]() { + menu->addChild(createMenuItem(string::translate("Model.manual"), widget::getKeyCommandName(GLFW_KEY_F1, RACK_MOD_CTRL), [=]() { system::openBrowser(manualUrl); })); } // donate if (plugin->donateUrl != "") { - menu->addChild(createMenuItem("Donate", "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.donate"), "", [=]() { system::openBrowser(plugin->donateUrl); })); } // source code if (plugin->sourceUrl != "") { - menu->addChild(createMenuItem("Source code", "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.source"), "", [=]() { system::openBrowser(plugin->sourceUrl); })); } // changelog if (plugin->changelogUrl != "") { - menu->addChild(createMenuItem("Changelog", "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.changelog"), "", [=]() { system::openBrowser(plugin->changelogUrl); })); } // author email if (plugin->authorEmail != "") { - menu->addChild(createMenuItem("Author email", "Copy to clipboard", [=]() { + menu->addChild(createMenuItem(string::translate("Model.authorEmail"), string::translate("Model.authorEmailCopy"), [=]() { glfwSetClipboardString(APP->window->win, plugin->authorEmail.c_str()); })); } // plugin folder if (plugin->path != "") { - menu->addChild(createMenuItem("Open plugin folder", "", [=]() { + menu->addChild(createMenuItem(string::translate("Model.pluginDir"), "", [=]() { system::openDirectory(plugin->path); })); } @@ -200,7 +200,7 @@ void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { std::string favoriteRightText = inBrowser ? (RACK_MOD_CTRL_NAME "+click") : ""; if (isFavorite()) favoriteRightText += " " CHECKMARK_STRING; - menu->addChild(createMenuItem("Favorite", favoriteRightText, + menu->addChild(createMenuItem(string::translate("Model.favorite"), favoriteRightText, [=]() { setFavorite(!isFavorite()); } diff --git a/src/widget/event.cpp b/src/widget/event.cpp index 08914cd1..ca7f0674 100644 --- a/src/widget/event.cpp +++ b/src/widget/event.cpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace rack { @@ -10,9 +11,12 @@ namespace widget { std::string getKeyName(int key) { + if (key < 32) + return ""; + // glfwGetKeyName overrides switch (key) { - case GLFW_KEY_SPACE: return "Space"; + case GLFW_KEY_SPACE: return string::translate("key.space"); } // Printable characters @@ -22,21 +26,21 @@ std::string getKeyName(int key) { // Unprintable keys with names switch (key) { - case GLFW_KEY_ESCAPE: return "Escape"; - case GLFW_KEY_ENTER: return "Enter"; - case GLFW_KEY_TAB: return "Tab"; - case GLFW_KEY_BACKSPACE: return "Backspace"; - case GLFW_KEY_INSERT: return "Insert"; - case GLFW_KEY_DELETE: return "Delete"; - case GLFW_KEY_RIGHT: return "Right"; - case GLFW_KEY_LEFT: return "Left"; - case GLFW_KEY_DOWN: return "Down"; - case GLFW_KEY_UP: return "Up"; - case GLFW_KEY_PAGE_UP: return "Page Up"; - case GLFW_KEY_PAGE_DOWN: return "Page Down"; - case GLFW_KEY_HOME: return "Home"; - case GLFW_KEY_END: return "End"; - case GLFW_KEY_KP_ENTER: return "Enter"; + case GLFW_KEY_ESCAPE: return string::translate("key.escape"); + case GLFW_KEY_ENTER: return string::translate("key.enter"); + case GLFW_KEY_TAB: return string::translate("key.tab"); + case GLFW_KEY_BACKSPACE: return string::translate("key.backspace"); + case GLFW_KEY_INSERT: return string::translate("key.insert"); + case GLFW_KEY_DELETE: return string::translate("key.delete"); + case GLFW_KEY_RIGHT: return string::translate("key.right"); + case GLFW_KEY_LEFT: return string::translate("key.left"); + case GLFW_KEY_DOWN: return string::translate("key.down"); + case GLFW_KEY_UP: return string::translate("key.up"); + case GLFW_KEY_PAGE_UP: return string::translate("key.pageUp"); + case GLFW_KEY_PAGE_DOWN: return string::translate("key.pageDown"); + case GLFW_KEY_HOME: return string::translate("key.home"); + case GLFW_KEY_END: return string::translate("key.end"); + case GLFW_KEY_KP_ENTER: return string::translate("key.enter"); } if (GLFW_KEY_F1 <= key && key <= GLFW_KEY_F25) @@ -49,13 +53,20 @@ std::string getKeyName(int key) { std::string getKeyCommandName(int key, int mods) { std::string modsName; if (mods & RACK_MOD_CTRL) { - modsName += RACK_MOD_CTRL_NAME "+"; +#if defined ARCH_MAC + modsName += "⌘"; +#else + modsName += string::translate("key.ctrl"); +#endif + modsName += "+"; } if (mods & GLFW_MOD_SHIFT) { - modsName += RACK_MOD_SHIFT_NAME "+"; + modsName += string::translate("key.shift"); + modsName += "+"; } if (mods & GLFW_MOD_ALT) { - modsName += RACK_MOD_ALT_NAME "+"; + modsName += string::translate("key.alt"); + modsName += "+"; } return modsName + getKeyName(key); } diff --git a/translations/en.json b/translations/en.json index 7d50f1a7..a9a05378 100644 --- a/translations/en.json +++ b/translations/en.json @@ -1,5 +1,25 @@ { "language": "English", + "translators": "", + "key.space": "Space", + "key.escape": "Escape", + "key.enter": "Enter", + "key.tab": "Tab", + "key.backspace": "Backspace", + "key.insert": "Insert", + "key.delete": "Delete", + "key.right": "Right", + "key.left": "Left", + "key.down": "Down", + "key.up": "Up", + "key.pageUp": "Page Up", + "key.pageDown": "Page Down", + "key.home": "Home", + "key.end": "End", + "key.ctrl": "Ctrl", + "key.shift": "Shift", + "key.alt": "Alt", + "MenuBar.infoLabel": "%.1f fps %.1f%% avg %.1f%% max", "MenuBar.file": "File", "MenuBar.file.new": "New", "MenuBar.file.open": "Open", @@ -146,5 +166,65 @@ "RackWidget.duplicate": "Duplicate", "RackWidget.duplicateWithCables": "with cables", "RackWidget.delete": "Delete", - "RackWidget.history.clearCables": "clear cables" + "RackWidget.history.clearCables": "clear cables", + "Model.plugin": "Plugin: ", + "Model.version": "Version: ", + "Model.author": "Author: ", + "Model.licenseBrowser": "License: Open in browser", + "Model.license": "License: ", + "Model.tags": "Tags:", + "Model.library": "VCV Library page", + "Model.modularGrid": "ModularGrid page", + "Model.manual": "User manual", + "Model.donate": "Donate", + "Model.source": "Source code", + "Model.changelog": "Changelog", + "Model.authorEmail": "Author email", + "Model.authorEmailCopy": "Copy to clipboard", + "Model.pluginDir": "Open plugin folder", + "Model.favorite": "Favorite", + "Browser.history.addModule": "add module", + "Browser.tooltipTags": "Tags: ", + "Browser.sort.lastUpdated": "Last updated", + "Browser.sort.lastUsed": "Last used", + "Browser.sort.mostUsed": "Most used", + "Browser.sort.brand": "Brand", + "Browser.sort.moduleName": "Module name", + "Browser.sort.random": "Random", + "Browser.sort": "Sort: ", + "Browser.zoom": "Zoom: ", + "Browser.searchModules": "Search modules", + "Browser.favorites": "Favorites", + "Browser.resetFilters": "Reset filters", + "Browser.browseLibrary": "Browse VCV Library", + "Browser.modulesOne": "1 module", + "Browser.modulesMany": "%d modules", + "Browser.allBrands": "All brands", + "Browser.brand": "Brand", + "Browser.allTags": "All tags", + "Browser.tagsSelectMultiple": "click to select multiple", + "Browser.tags": "Tags", + "ParamWidget.history.setParam": "set parameter", + "ParamWidget.initialize": "Initialize", + "ParamWidget.doubleClick": "Double-click", + "ParamWidget.fine": "Fine adjust", + "ParamWidget.fineDrag": "drag", + "ParamWidget.unmap": "Unmap", + "ParamWidget.history.reset": "reset parameter", + "PortWidget.from": "From ", + "PortWidget.to": "To ", + "PortWidget.input": "input", + "PortWidget.output": "output", + "PortWidget.cableId": "ID: %ld", + "PortWidget.setColor": "Set color", + "PortWidget.deleteTopCable": "Delete top cable", + "PortWidget.click": "click", + "PortWidget.cloneTopCable": "Duplicate top cable", + "PortWidget.createCableTop": "Create cable on top", + "PortWidget.drag": "drag", + "PortWidget.createCable": "Create cable: ", + "PortWidget.clickDrag": "Click+drag", + "PortWidget.grabCable": " to grab cable", + "PortWidget.allCables": "All cables", + "PortWidget.history.moveCable": "move cable" } \ No newline at end of file