diff --git a/adapters/standalone.cpp b/adapters/standalone.cpp index d43103c6..4c5f98ed 100644 --- a/adapters/standalone.cpp +++ b/adapters/standalone.cpp @@ -59,7 +59,7 @@ int main(int argc, char* argv[]) { // Handle will be closed by Windows when the process ends HANDLE instanceMutex = CreateMutexW(NULL, true, string::UTF8toUTF16(APP_NAME).c_str()); if (GetLastError() == ERROR_ALREADY_EXISTS) { - osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, "VCV Rack is already running. Multiple Rack instances are not supported."); + osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, string::translate("standalone.multipleInstances")); exit(1); } (void) instanceMutex; @@ -174,7 +174,8 @@ int main(int argc, char* argv[]) { } catch (Exception& e) { std::string msg = e.what(); - msg += "\n\nReset settings to default?"; + msg += "\n\n"; + msg += string::translate("standalone.resetSettings"); if (!osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK_CANCEL, msg.c_str())) { exit(1); } @@ -184,7 +185,7 @@ int main(int argc, char* argv[]) { // Check existence of the system res/ directory std::string resDir = asset::system("res"); if (!system::isDirectory(resDir)) { - std::string message = string::f("VCV Rack's resource directory \"%s\" does not exist. Make sure Rack is correctly installed and launched.", resDir.c_str()); + std::string message = string::f(string::translate("standalone.resDir"), resDir); osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, message.c_str()); exit(1); } @@ -196,8 +197,7 @@ int main(int argc, char* argv[]) { rtaudioInit(); #if defined ARCH_MAC if (rtaudioIsMicrophoneBlocked()) { - std::string msg = "VCV Rack cannot access audio input because Microphone permission is blocked."; - msg += "\n\nGive permission to Rack by opening Apple's System Settings and enabling Privacy & Security > Microphone > " + APP_NAME + " " + APP_VERSION_MAJOR + " " + APP_EDITION_NAME + "."; + std::string msg = string::f(string::translate("standalone.micPermission"), APP_NAME + " " + APP_VERSION_MAJOR + " " + APP_EDITION_NAME); osdialog_message(OSDIALOG_ERROR, OSDIALOG_OK, msg.c_str()); } #endif @@ -252,7 +252,7 @@ int main(int argc, char* argv[]) { #endif // Initialize patch - if (logger::wasTruncated() && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, "VCV Rack crashed during the last session, possibly due to a buggy module in your patch. Clear your patch and start over?")) { + if (logger::wasTruncated() && osdialog_message(OSDIALOG_INFO, OSDIALOG_YES_NO, string::translate("standalone.crashed").c_str())) { // Do nothing, which leaves a blank patch } else { diff --git a/src/app/AudioDisplay.cpp b/src/app/AudioDisplay.cpp index 8fc3f484..0eebfc7a 100644 --- a/src/app/AudioDisplay.cpp +++ b/src/app/AudioDisplay.cpp @@ -49,14 +49,14 @@ static void appendAudioDriverMenu(ui::Menu* menu, audio::Port* port) { void AudioDriverChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); - menu->addChild(createMenuLabel("Audio driver")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver"))); appendAudioDriverMenu(menu, port); } void AudioDriverChoice::step() { text = ""; if (box.size.x >= 200.0) - text += "Driver: "; + text += string::translate("AudioDisplay.driver"); audio::Driver* driver = port ? port->getDriver() : NULL; std::string driverName = driver ? driver->getName() : ""; if (driverName != "") { @@ -64,7 +64,7 @@ void AudioDriverChoice::step() { color.a = 1.0; } else { - text += "(No driver)"; + text += "(" + string::translate("AudioDisplay.noDriver") + ")"; color.a = 0.5; } } @@ -99,7 +99,7 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) { AudioDeviceValueItem* item = new AudioDeviceValueItem; item->port = port; item->deviceId = -1; - item->text = "(No device)"; + item->text = "(" + string::translate("AudioDisplay.noDevice") + ")"; item->rightText = CHECKMARK(item->deviceId == port->getDeviceId()); menu->addChild(item); } @@ -132,14 +132,14 @@ static void appendAudioDeviceMenu(ui::Menu* menu, audio::Port* port) { void AudioDeviceChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); - menu->addChild(createMenuLabel("Audio device")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice"))); appendAudioDeviceMenu(menu, port); } void AudioDeviceChoice::step() { text = ""; if (box.size.x >= 200.0) - text += "Device: "; + text += string::translate("AudioDisplay.device"); std::string detail = ""; if (port && port->getDevice()) detail = getDetailTemplate(port->getDevice()->getName(), port->getNumInputs(), port->inputOffset, port->getNumOutputs(), port->outputOffset); @@ -149,10 +149,7 @@ void AudioDeviceChoice::step() { color.a = 1.0; } else { - if (box.size.x >= 80.0) - text += "(No device)"; - else - text += "No device"; + text += string::translate("AudioDisplay.noDevice"); color.a = 0.5; } } @@ -184,7 +181,7 @@ static void appendAudioSampleRateMenu(ui::Menu* menu, audio::Port* port) { sampleRates.insert(port->getSampleRate()); if (sampleRates.empty()) { - menu->addChild(createMenuLabel("(Locked by device)")); + menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")")); } for (float sampleRate : sampleRates) { if (sampleRate <= 0) @@ -200,14 +197,14 @@ static void appendAudioSampleRateMenu(ui::Menu* menu, audio::Port* port) { void AudioSampleRateChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); - menu->addChild(createMenuLabel("Sample rate")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate"))); appendAudioSampleRateMenu(menu, port); } void AudioSampleRateChoice::step() { text = ""; if (box.size.x >= 100.0) - text += "Rate: "; + text += string::translate("AudioDisplay.sampleRateColon"); float sampleRate = port ? port->getSampleRate() : 0; if (sampleRate > 0) { text += string::f("%g", sampleRate / 1000.f); @@ -247,7 +244,7 @@ static void appendAudioBlockSizeMenu(ui::Menu* menu, audio::Port* port) { blockSizes.insert(port->getBlockSize()); if (blockSizes.empty()) { - menu->addChild(createMenuLabel("(Locked by device)")); + menu->addChild(createMenuLabel("(" + string::translate("AudioDisplay.lockedByDevice") + ")")); } for (int blockSize : blockSizes) { if (blockSize <= 0) @@ -264,14 +261,14 @@ static void appendAudioBlockSizeMenu(ui::Menu* menu, audio::Port* port) { void AudioBlockSizeChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); - menu->addChild(createMenuLabel("Block size")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize"))); appendAudioBlockSizeMenu(menu, port); } void AudioBlockSizeChoice::step() { text = ""; if (box.size.x >= 100.0) - text += "Block size: "; + text += string::translate("AudioDisplay.blockSizeColon"); int blockSize = port ? port->getBlockSize() : 0; if (blockSize > 0) { text += string::f("%d", blockSize); @@ -358,36 +355,36 @@ void AudioButton::onAction(const ActionEvent& e) { void appendAudioMenu(ui::Menu* menu, audio::Port* port) { - menu->addChild(createMenuLabel("Audio driver")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDriver"))); appendAudioDriverMenu(menu, port); menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel("Audio device")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.audioDevice"))); appendAudioDeviceMenu(menu, port); menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel("Sample rate")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.sampleRate"))); appendAudioSampleRateMenu(menu, port); menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel("Block size")); + menu->addChild(createMenuLabel(string::translate("AudioDisplay.blockSize"))); appendAudioBlockSizeMenu(menu, port); // Uncomment this to use sub-menus instead of one big menu. - // AudioDriverItem* driverItem = createMenuItem("Audio driver", RIGHT_ARROW); + // AudioDriverItem* driverItem = createMenuItem(string::translate("AudioDisplay.audioDriver"), RIGHT_ARROW); // driverItem->port = port; // menu->addChild(driverItem); - // AudioDeviceItem* deviceItem = createMenuItem("Audio device", RIGHT_ARROW); + // AudioDeviceItem* deviceItem = createMenuItem(string::translate("AudioDisplay.audioDevice"), RIGHT_ARROW); // deviceItem->port = port; // menu->addChild(deviceItem); - // AudioSampleRateItem* sampleRateItem = createMenuItem("Sample rate", RIGHT_ARROW); + // AudioSampleRateItem* sampleRateItem = createMenuItem(string::translate("AudioDisplay.sampleRate"), RIGHT_ARROW); // sampleRateItem->port = port; // menu->addChild(sampleRateItem); - // AudioBlockSizeItem* blockSizeItem = createMenuItem("Block size", RIGHT_ARROW); + // AudioBlockSizeItem* blockSizeItem = createMenuItem(string::translate("AudioDisplay.blockSize"), RIGHT_ARROW); // blockSizeItem->port = port; // menu->addChild(blockSizeItem); } diff --git a/src/app/Browser.cpp b/src/app/Browser.cpp index c6384d41..6ba71c22 100644 --- a/src/app/Browser.cpp +++ b/src/app/Browser.cpp @@ -989,7 +989,7 @@ inline void TagButton::onAction(const ActionEvent& e) { noneItem->browser = browser; menu->addChild(noneItem); - menu->addChild(createMenuLabel(widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("Browser.tagsSelectMultiple"))); + menu->addChild(createMenuLabel(widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("key.click") + string::translate("Browser.tagsSelectMultiple"))); menu->addChild(new ui::MenuSeparator); for (int tagId = 0; tagId < (int) tag::tagAliases.size(); tagId++) { diff --git a/src/app/Knob.cpp b/src/app/Knob.cpp index 0b73dec4..9a74d5b2 100644 --- a/src/app/Knob.cpp +++ b/src/app/Knob.cpp @@ -107,7 +107,7 @@ void Knob::onDragEnd(const DragEndEvent& e) { if (!std::isnan(internal->oldValue) && internal->oldValue != newValue) { // Push ParamChange history action history::ParamChange* h = new history::ParamChange; - h->name = "move knob"; + h->name = string::translate("Knob.history.move"); h->moduleId = module->id; h->paramId = paramId; h->oldValue = internal->oldValue; @@ -302,7 +302,7 @@ void Knob::onLeave(const LeaveEvent& e) { if (!std::isnan(internal->oldValue) && internal->oldValue != newValue) { // Push ParamChange history action history::ParamChange* h = new history::ParamChange; - h->name = "move knob"; + h->name = string::translate("Knob.history.move"); h->moduleId = module->id; h->paramId = paramId; h->oldValue = internal->oldValue; diff --git a/src/app/MidiDisplay.cpp b/src/app/MidiDisplay.cpp index 22fedd5d..e8556a32 100644 --- a/src/app/MidiDisplay.cpp +++ b/src/app/MidiDisplay.cpp @@ -31,14 +31,14 @@ static void appendMidiDriverMenu(ui::Menu* menu, midi::Port* port) { void MidiDriverChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); - menu->addChild(createMenuLabel("MIDI driver")); + menu->addChild(createMenuLabel(string::translate("MidiDisplay.driver"))); appendMidiDriverMenu(menu, port); } void MidiDriverChoice::step() { text = (port && port->driver) ? port->getDriver()->getName() : ""; if (text.empty()) { - text = "(No driver)"; + text = "(" + string::translate("MidiDisplay.noDriver") + ")"; color.a = 0.5f; } else { @@ -72,7 +72,7 @@ static void appendMidiDeviceMenu(ui::Menu* menu, midi::Port* port) { MidiDeviceValueItem* item = new MidiDeviceValueItem; item->port = port; item->deviceId = -1; - item->text = "(No device)"; + item->text = "(" + string::translate("MidiDisplay.noDevice") + ")"; item->rightText = CHECKMARK(item->deviceId == port->getDeviceId()); menu->addChild(item); } @@ -89,14 +89,14 @@ static void appendMidiDeviceMenu(ui::Menu* menu, midi::Port* port) { void MidiDeviceChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); - menu->addChild(createMenuLabel("MIDI device")); + menu->addChild(createMenuLabel(string::translate("MidiDisplay.device"))); appendMidiDeviceMenu(menu, port); } void MidiDeviceChoice::step() { text = (port && port->device) ? port->getDevice()->getName() : ""; if (text.empty()) { - text = "(No device)"; + text = "(" + string::translate("MidiDisplay.noDevice") + ")"; color.a = 0.5f; } else { @@ -138,12 +138,12 @@ static void appendMidiChannelMenu(ui::Menu* menu, midi::Port* port) { void MidiChannelChoice::onAction(const ActionEvent& e) { ui::Menu* menu = createMenu(); - menu->addChild(createMenuLabel("MIDI channel")); + menu->addChild(createMenuLabel(string::translate("MidiDisplay.channel"))); appendMidiChannelMenu(menu, port); } void MidiChannelChoice::step() { - text = port ? port->getChannelName(port->getChannel()) : "Channel 1"; + text = port ? port->getChannelName(port->getChannel()) : string::translate("MidiDisplay.channel1"); } struct MidiChannelItem : ui::MenuItem { @@ -203,28 +203,28 @@ void MidiButton::onAction(const ActionEvent& e) { void appendMidiMenu(ui::Menu* menu, midi::Port* port) { - menu->addChild(createMenuLabel("MIDI driver")); + menu->addChild(createMenuLabel(string::translate("MidiDisplay.driver"))); appendMidiDriverMenu(menu, port); menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel("MIDI device")); + menu->addChild(createMenuLabel(string::translate("MidiDisplay.device"))); appendMidiDeviceMenu(menu, port); menu->addChild(new ui::MenuSeparator); - // menu->addChild(createMenuLabel("MIDI channel")); + // menu->addChild(createMenuLabel(string::translate("MidiDisplay.channel"))); // appendMidiChannelMenu(menu, port); // Uncomment this to use sub-menus instead of one big menu. - // MidiDriverItem* driverItem = createMenuItem("MIDI driver", RIGHT_ARROW); + // MidiDriverItem* driverItem = createMenuItem(string::translate("MidiDisplay.driver"), RIGHT_ARROW); // driverItem->port = port; // menu->addChild(driverItem); - // MidiDeviceItem* deviceItem = createMenuItem("MIDI device", RIGHT_ARROW); + // MidiDeviceItem* deviceItem = createMenuItem(string::translate("MidiDisplay.device"), RIGHT_ARROW); // deviceItem->port = port; // menu->addChild(deviceItem); - MidiChannelItem* channelItem = createMenuItem("MIDI channel", RIGHT_ARROW); + MidiChannelItem* channelItem = createMenuItem(string::translate("MidiDisplay.channel"), RIGHT_ARROW); channelItem->port = port; menu->addChild(channelItem); } diff --git a/src/app/ModuleLightWidget.cpp b/src/app/ModuleLightWidget.cpp index 5a35a767..ce150a74 100644 --- a/src/app/ModuleLightWidget.cpp +++ b/src/app/ModuleLightWidget.cpp @@ -22,8 +22,8 @@ struct LightTooltip : ui::Tooltip { if (!lightInfo) return; // Label - text = lightInfo->getName(); - text += " light"; + std::string name = lightInfo->getName(); + text = string::f(string::translate("ModuleLightWidget.light"), name); // Description std::string description = lightInfo->getDescription(); if (description != "") { diff --git a/src/app/ParamWidget.cpp b/src/app/ParamWidget.cpp index a255b9d1..4605e0ef 100644 --- a/src/app/ParamWidget.cpp +++ b/src/app/ParamWidget.cpp @@ -274,14 +274,14 @@ void ParamWidget::createContextMenu() { // Initialize if (pq && pq->resetEnabled && pq->isBounded()) { - menu->addChild(createMenuItem(string::translate("ParamWidget.initialize"), switchQuantity ? "" : string::translate("ParamWidget.doubleClick"), [=]() { + menu->addChild(createMenuItem(string::translate("ParamWidget.initialize"), switchQuantity ? "" : string::translate("key.doubleClick"), [=]() { this->resetAction(); })); } // Fine if (!switchQuantity) { - menu->addChild(createMenuItem(string::translate("ParamWidget.fine"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("ParamWidget.fineDrag"), NULL, true)); + menu->addChild(createMenuItem(string::translate("ParamWidget.fine"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("key.drag"), NULL, true)); } // Unmap diff --git a/src/app/PortWidget.cpp b/src/app/PortWidget.cpp index ebb18983..aaa02299 100644 --- a/src/app/PortWidget.cpp +++ b/src/app/PortWidget.cpp @@ -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(string::translate("PortWidget.deleteTopCable"), widget::getKeyCommandName(0, RACK_MOD_SHIFT) + string::translate("PortWidget.click"), + menu->addChild(createMenuItem(string::translate("PortWidget.deleteTopCable"), widget::getKeyCommandName(0, RACK_MOD_SHIFT) + string::translate("key.click"), [=]() { if (!weakThis) return; @@ -296,7 +296,7 @@ void PortWidget::createContextMenu() { )); { - PortCloneCableItem* item = createMenuItem(string::translate("PortWidget.cloneTopCable"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("PortWidget.drag")); + PortCloneCableItem* item = createMenuItem(string::translate("PortWidget.cloneTopCable"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("key.drag")); item->disabled = !topCw; item->pw = this; item->cw = topCw; @@ -304,7 +304,7 @@ void PortWidget::createContextMenu() { } { - PortCreateCableItem* item = createMenuItem(string::translate("PortWidget.createCableTop"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("PortWidget.drag")); + PortCreateCableItem* item = createMenuItem(string::translate("PortWidget.createCableTop"), widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("key.drag")); item->pw = this; menu->addChild(item); } @@ -326,7 +326,7 @@ void PortWidget::createContextMenu() { if (!cws.empty()) { menu->addChild(new ui::MenuSeparator); - menu->addChild(createMenuLabel(string::translate("PortWidget.clickDrag") + string::translate("PortWidget.grabCable"))); + menu->addChild(createMenuLabel(string::translate("key.clickDrag") + string::translate("PortWidget.grabCable"))); // Cable items for (auto it = cws.rbegin(); it != cws.rend(); it++) { diff --git a/src/app/Switch.cpp b/src/app/Switch.cpp index 4b37b379..de523b1d 100644 --- a/src/app/Switch.cpp +++ b/src/app/Switch.cpp @@ -103,7 +103,7 @@ void Switch::onDragStart(const DragStartEvent& e) { if (oldValue != newValue) { // Push ParamChange history action history::ParamChange* h = new history::ParamChange; - h->name = "move switch"; + h->name = string::translate("Switch.history.move"); h->moduleId = module->id; h->paramId = paramId; h->oldValue = oldValue; diff --git a/src/app/TipWindow.cpp b/src/app/TipWindow.cpp index 8e2c1ce0..64d50ea0 100644 --- a/src/app/TipWindow.cpp +++ b/src/app/TipWindow.cpp @@ -31,23 +31,36 @@ struct TipInfo { }; -// Remember to use “smart quotes.” -static const std::vector tipInfos = { - {"To add a module to your patch, right-click an empty rack space or press Enter. Then click and drag a module from the Module Browser into the desired rack space.\n\nTo select multiple modules, click and drag on empty rack space.", "", ""}, - {"To move around your patch, use the scroll bars, drag while holding the middle mouse button, " RACK_MOD_ALT_NAME "+click and drag, or hold the arrow keys. Arrow key movement speed can be adjusted by holding " RACK_MOD_CTRL_NAME ", " RACK_MOD_SHIFT_NAME ", or " RACK_MOD_CTRL_NAME "+" RACK_MOD_SHIFT_NAME ".\n\nTo zoom in and out, drag the Zoom slider in the View menu, hold " RACK_MOD_CTRL_NAME " and scroll, or press " RACK_MOD_CTRL_NAME "+= and " RACK_MOD_CTRL_NAME "+minus.", "", ""}, - {"You can use Rack in fullscreen mode by selecting “View > Fullscreen“ or pressing F11.\n\nIn fullscreen mode, the menu bar and scroll bars are hidden. This is ideal for screen recording with VCV Recorder.", "Get VCV Recorder", "https://vcvrack.com/Recorder"}, - {"You can browse thousands of modules on the VCV Library website.\n\nRegister for a VCV account, log into Rack using the Library menu, and browse the VCV Library to add or purchase modules. Keep all plugins up to date by clicking “Library > Update all”.", "VCV Library", "https://library.vcvrack.com/"}, - {"Some developers of free plugins accept donations. Right-click your favorite module's panel and select “Info > Donate”.\n\nYou can also donate via the module's VCV Library page.", "VCV Library", "https://library.vcvrack.com/"}, - {"Want to use VCV Rack in your DAW? VCV Rack Pro is available for VST2, VST3, Audio Unit, and CLAP hosts.\n\nSupported DAWs include Ableton Live, Cubase, FL Studio, Reason, Bitwig, Reaper, Mixbus, Studio One, Cakewalk, Logic Pro, and GarageBand.", "Learn more", "https://vcvrack.com/Rack"}, - {"You can learn more about VCV Rack by browsing the official manual.", "VCV Rack manual", "https://vcvrack.com/manual/"}, - {"Follow VCV Rack on Twitter for new module announcements, development news, and featured artists/music.", "Twitter @vcvrack", "https://twitter.com/vcvrack"}, - {"Patch cables in Rack can carry up to 16 signals. You can use this ability to build polyphonic patches using modules having the “Polyphonic” tag. Cables carrying more than 1 signal appear thicker than normal cables. To try out polyphony, add the VCV MIDI-to-CV module to your patch, right-click its panel, and select your desired number of polyphonic channels.", "Learn more about polyphony in VCV Rack", "https://vcvrack.com/manual/Polyphony"}, - {"Know C++ programming and want to create your own modules for Rack? Developing Rack modules is a great way to learn digital signal processing and quickly test your ideas with an easy-to-learn platform.\n\nDownload the Rack SDK and follow the development tutorial to get started.", "Plugin Development Tutorial", "https://vcvrack.com/manual/PluginDevelopmentTutorial"}, - {"Wondering how to use a particular module? Right-click its panel and choose “Info > User manual”.\n\nYou can also open the module's Info menu to view the module's tags, website, VCV Library page, and changelog, if available.", "", ""}, - {"Did you know that the VCV Library is integrated with ModularGrid? If a module is available as a hardware Eurorack module, right-click its panel and choose “Info > ModularGrid”, or click the “ModularGrid” link on its VCV Library page.\n\nOn ModularGrid.net, search for the VCV logo on certain module's entry pages.", "Example: Grayscale Permutation on ModularGrid", "https://www.modulargrid.net/e/grayscale-permutation-18hp"}, - {"When any context menu is open, you can " RACK_MOD_CTRL_NAME "+click a menu item to keep the menu open. This can be useful when browsing module presets or settings.", "", ""}, - // {"", "", ""}, -}; +static std::vector getTipInfos() { + // Remember to use “smart quotes.” + return { + {string::translate("TipWindow.addModule"), "", ""}, + {string::f(string::translate("TipWindow.moveRack"), + widget::getKeyCommandName(0, RACK_MOD_ALT) + string::translate("key.click"), + string::translate("key.ctrl"), + string::translate("key.shift"), + string::translate("key.ctrl"), + string::translate("key.shift"), + string::translate("key.ctrl"), + widget::getKeyCommandName(GLFW_KEY_EQUAL, RACK_MOD_CTRL), + widget::getKeyCommandName(GLFW_KEY_MINUS, RACK_MOD_CTRL)), + "", ""}, + {string::translate("TipWindow.fullscreen"), string::translate("TipWindow.fullscreenButton"), "https://vcvrack.com/Recorder"}, + {string::translate("TipWindow.library"), string::translate("TipWindow.libraryButton"), "https://library.vcvrack.com/"}, + {string::translate("TipWindow.donation"), string::translate("TipWindow.libraryButton"), "https://library.vcvrack.com/"}, + {string::translate("TipWindow.daw"), string::translate("TipWindow.learnMore"), "https://vcvrack.com/Rack"}, + {string::translate("TipWindow.manual"), string::translate("TipWindow.manualButton"), "https://vcvrack.com/manual/"}, + {string::translate("TipWindow.twitter"), string::translate("TipWindow.twitterHandle"), "https://twitter.com/vcvrack"}, + {string::translate("TipWindow.polyphony"), string::translate("TipWindow.polyphonyButton"), "https://vcvrack.com/manual/Polyphony"}, + {string::translate("TipWindow.develop"), string::translate("TipWindow.developButton"), "https://vcvrack.com/manual/PluginDevelopmentTutorial"}, + {string::translate("TipWindow.moduleManual"), "", ""}, + {string::translate("TipWindow.modularGrid"), string::translate("TipWindow.modularGridButton"), "https://www.modulargrid.net/e/grayscale-permutation-18hp"}, + {string::f(string::translate("TipWindow.menuKeepOpen"), + widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("key.click")), + "", ""}, + // {"", "", ""}, + }; +} struct TipWindow : widget::OpaqueWidget { @@ -74,7 +87,7 @@ struct TipWindow : widget::OpaqueWidget { // header->box.size.x = box.size.x - 2*margin; header->box.size.y = 20; header->fontSize = 20; - header->text = "Welcome to " + APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION; + header->text = string::f(string::translate("TipWindow.welcome"), APP_NAME + " " + APP_EDITION_NAME + " " + APP_VERSION); layout->addChild(header); label = new ui::Label; @@ -108,7 +121,7 @@ struct TipWindow : widget::OpaqueWidget { ui::OptionButton* showButton = new ui::OptionButton; showButton->box.size.x = 200; - showButton->text = "Show tips at startup"; + showButton->text = string::translate("TipWindow.startup"); showButton->quantity = &showQuantity; buttonLayout->addChild(showButton); @@ -120,7 +133,7 @@ struct TipWindow : widget::OpaqueWidget { }; PreviousButton* prevButton = new PreviousButton; prevButton->box.size.x = buttonWidth; - prevButton->text = "◀ Previous"; + prevButton->text = "◀ " + string::translate("TipWindow.previous"); prevButton->tipWindow = this; buttonLayout->addChild(prevButton); @@ -132,7 +145,7 @@ struct TipWindow : widget::OpaqueWidget { }; NextButton* nextButton = new NextButton; nextButton->box.size.x = buttonWidth; - nextButton->text = "▶ Next"; + nextButton->text = "▶ " + string::translate("TipWindow.next"); nextButton->tipWindow = this; buttonLayout->addChild(nextButton); @@ -144,7 +157,7 @@ struct TipWindow : widget::OpaqueWidget { }; CloseButton* closeButton = new CloseButton; closeButton->box.size.x = buttonWidth; - closeButton->text = "✖ Close"; + closeButton->text = "✖ " + string::translate("TipWindow.close"); closeButton->tipWindow = this; buttonLayout->addChild(closeButton); @@ -155,6 +168,8 @@ struct TipWindow : widget::OpaqueWidget { } void advanceTip(int delta = 1) { + std::vector tipInfos = getTipInfos(); + // Increment tip index settings::tipIndex = math::eucMod(settings::tipIndex + delta, (int) tipInfos.size()); diff --git a/src/engine/PortInfo.cpp b/src/engine/PortInfo.cpp index 3acdc5e8..5f4f72a3 100644 --- a/src/engine/PortInfo.cpp +++ b/src/engine/PortInfo.cpp @@ -15,9 +15,7 @@ std::string PortInfo::getName() { std::string PortInfo::getFullName() { std::string name = getName(); - name += " "; - name += (type == Port::INPUT) ? "input" : "output"; - return name; + return string::f((type == Port::INPUT) ? string::translate("PortInfo.input") : string::translate("PortInfo.output"), name); } diff --git a/src/library.cpp b/src/library.cpp index 716f35d5..d8849460 100644 --- a/src/library.cpp +++ b/src/library.cpp @@ -114,7 +114,7 @@ void logIn(std::string email, std::string password) { return; DEFER({updateMutex.unlock();}); - loginStatus = "Logging in..."; + loginStatus = string::translate("library.loggingIn"); json_t* reqJ = json_object(); json_object_set_new(reqJ, "email", json_string(email.c_str())); json_object_set_new(reqJ, "password", json_string(password.c_str())); @@ -123,7 +123,7 @@ void logIn(std::string email, std::string password) { json_decref(reqJ); if (!resJ) { - loginStatus = "No response from server"; + loginStatus = string::translate("library.noResponse"); return; } DEFER({json_decref(resJ);}); @@ -137,7 +137,7 @@ void logIn(std::string email, std::string password) { json_t* tokenJ = json_object_get(resJ, "token"); if (!tokenJ) { - loginStatus = "No token in response"; + loginStatus = string::translate("library.noToken"); return; } @@ -172,14 +172,14 @@ void checkUpdates() { if (isSyncing) return; - updateStatus = "Querying for updates..."; + updateStatus = string::translate("library.queryingUpdates"); // Check user token std::string userUrl = API_URL + "/user"; json_t* userResJ = network::requestJson(network::METHOD_GET, userUrl, NULL, getTokenCookies()); if (!userResJ) { WARN("Request for user account failed"); - updateStatus = "Could not query user account"; + updateStatus = string::translate("library.queryAccountFailed"); return; } DEFER({json_decref(userResJ);}); @@ -202,7 +202,7 @@ void checkUpdates() { json_decref(manifestsReq); if (!manifestsResJ) { WARN("Request for library manifests failed"); - updateStatus = "Could not query plugin manifests"; + updateStatus = string::translate("library.queryManifestsFailed"); return; } DEFER({json_decref(manifestsResJ);}); @@ -212,7 +212,7 @@ void checkUpdates() { json_t* modulesResJ = network::requestJson(network::METHOD_GET, modulesUrl, NULL, getTokenCookies()); if (!modulesResJ) { WARN("Request for user's modules failed"); - updateStatus = "Could not query user's modules"; + updateStatus = string::translate("library.queryModulesFailed"); return; } DEFER({json_decref(modulesResJ);}); diff --git a/src/midi.cpp b/src/midi.cpp index 5ad6257c..63bd3aa4 100644 --- a/src/midi.cpp +++ b/src/midi.cpp @@ -150,9 +150,9 @@ void Port::setChannel(int channel) { std::string Port::getChannelName(int channel) { if (channel < 0) - return "All channels"; + return string::translate("midi.allChannels"); else - return string::f("Channel %d", channel + 1); + return string::f(string::translate("midi.channelNum"), channel + 1); } json_t* Port::toJson() { diff --git a/src/patch.cpp b/src/patch.cpp index ec468e6a..3bbe90e7 100644 --- a/src/patch.cpp +++ b/src/patch.cpp @@ -145,7 +145,7 @@ void Manager::saveDialog() { save(path); } catch (Exception& e) { - std::string message = string::f("Could not save patch: %s", e.what()); + std::string message = string::f(string::translate("patch.saveFailed"), e.what()); osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str()); return; } @@ -195,7 +195,7 @@ void Manager::saveAsDialog(bool setPath) { save(path); } catch (Exception& e) { - std::string message = string::f("Could not save patch: %s", e.what()); + std::string message = string::f(string::translate("patch.saveFailed"), e.what()); osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); return; } @@ -206,14 +206,14 @@ void Manager::saveAsDialog(bool setPath) { void Manager::saveTemplateDialog() { // Even if /template.vcv doesn't exist, this message is still valid because it overrides the /template.vcv patch. - if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, "Overwrite template patch?")) + if (!osdialog_message(OSDIALOG_INFO, OSDIALOG_OK_CANCEL, string::translate("patch.overwriteTemplate").c_str())) return; try { save(templatePath); } catch (Exception& e) { - std::string message = string::f("Could not save template patch: %s", e.what()); + std::string message = string::f(string::translate("patch.saveTemplateFailed"), e.what()); osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str()); return; } @@ -316,7 +316,7 @@ void Manager::loadTemplate() { load(factoryTemplatePath); } catch (Exception& e) { - std::string message = string::f("Could not load system template patch, clearing rack: %s", e.what()); + std::string message = string::f(string::translate("patch.loadTemplateFailed"), e.what()); osdialog_message(OSDIALOG_INFO, OSDIALOG_OK, message.c_str()); clear(); @@ -331,7 +331,7 @@ void Manager::loadTemplate() { void Manager::loadTemplateDialog() { - if (!promptClear("The current patch is unsaved. Clear it and start a new patch?")) { + if (!promptClear(string::translate("patch.loadTemplateConfirm"))) { return; } loadTemplate(); @@ -373,7 +373,7 @@ void Manager::loadAction(std::string path) { load(path); } catch (Exception& e) { - std::string message = string::f("Could not load patch: %s", e.what()); + std::string message = string::f(string::translate("patch.loadFailed"), e.what()); osdialog_message(OSDIALOG_WARNING, OSDIALOG_OK, message.c_str()); return; } @@ -385,7 +385,7 @@ void Manager::loadAction(std::string path) { void Manager::loadDialog() { - if (!promptClear("The current patch is unsaved. Clear it and open a new patch?")) + if (!promptClear(string::translate("patch.loadConfirm"))) return; std::string dir; @@ -415,7 +415,7 @@ void Manager::loadDialog() { void Manager::loadPathDialog(std::string path) { - if (!promptClear("The current patch is unsaved. Clear it and open the new patch?")) + if (!promptClear(string::translate("patch.loadConfirm"))) return; loadAction(path); @@ -425,7 +425,7 @@ void Manager::loadPathDialog(std::string path) { void Manager::revertDialog() { if (path == "") return; - if (!promptClear("Revert patch to the last saved state?")) + if (!promptClear(string::translate("patch.revertConfirm"))) return; loadAction(path); @@ -577,11 +577,7 @@ bool Manager::checkUnavailableModulesJson(json_t* rootJ) { if (!pluginModuleSlugs.empty()) { // Ask user to open browser - std::string msg = "This patch includes modules that are not installed:"; - msg += "\n\n"; - msg += string::join(pluginModuleSlugs, "\n"); - msg += "\n\n"; - msg += "Show missing modules on the VCV Library?"; + std::string msg = string::f(string::translate("patch.unavailableModules"), string::join(pluginModuleSlugs, "\n")); if (osdialog_message(OSDIALOG_WARNING, OSDIALOG_YES_NO, msg.c_str())) { std::string url = "https://library.vcvrack.com/?modules="; url += string::join(pluginModuleSlugs, ","); diff --git a/src/plugin/Model.cpp b/src/plugin/Model.cpp index 1245aadd..bae520b3 100644 --- a/src/plugin/Model.cpp +++ b/src/plugin/Model.cpp @@ -198,7 +198,9 @@ void Model::appendContextMenu(ui::Menu* menu, bool inBrowser) { } // Favorite - std::string favoriteRightText = inBrowser ? (RACK_MOD_CTRL_NAME "+click") : ""; + std::string favoriteRightText; + if (inBrowser) + favoriteRightText = widget::getKeyCommandName(0, RACK_MOD_CTRL) + string::translate("key.click"); if (isFavorite()) favoriteRightText += " " CHECKMARK_STRING; menu->addChild(createMenuItem(string::translate("Model.favorite"), favoriteRightText, diff --git a/src/ui/TextField.cpp b/src/ui/TextField.cpp index b9777a95..348ec774 100644 --- a/src/ui/TextField.cpp +++ b/src/ui/TextField.cpp @@ -360,25 +360,25 @@ void TextField::createContextMenu() { ui::Menu* menu = createMenu(); TextFieldCutItem* cutItem = new TextFieldCutItem; - cutItem->text = "Cut"; + cutItem->text = string::translate("TextField.cut"); cutItem->rightText = widget::getKeyCommandName(GLFW_KEY_X, RACK_MOD_CTRL); cutItem->textField = this; menu->addChild(cutItem); TextFieldCopyItem* copyItem = new TextFieldCopyItem; - copyItem->text = "Copy"; + copyItem->text = string::translate("TextField.copy"); copyItem->rightText = widget::getKeyCommandName(GLFW_KEY_C, RACK_MOD_CTRL); copyItem->textField = this; menu->addChild(copyItem); TextFieldPasteItem* pasteItem = new TextFieldPasteItem; - pasteItem->text = "Paste"; + pasteItem->text = string::translate("TextField.paste"); pasteItem->rightText = widget::getKeyCommandName(GLFW_KEY_V, RACK_MOD_CTRL); pasteItem->textField = this; menu->addChild(pasteItem); TextFieldSelectAllItem* selectAllItem = new TextFieldSelectAllItem; - selectAllItem->text = "Select all"; + selectAllItem->text = string::translate("TextField.selectAll"); selectAllItem->rightText = widget::getKeyCommandName(GLFW_KEY_A, RACK_MOD_CTRL); selectAllItem->textField = this; menu->addChild(selectAllItem); diff --git a/src/widget/event.cpp b/src/widget/event.cpp index ca7f0674..f0fc9f33 100644 --- a/src/widget/event.cpp +++ b/src/widget/event.cpp @@ -17,6 +17,7 @@ std::string getKeyName(int key) { // glfwGetKeyName overrides switch (key) { case GLFW_KEY_SPACE: return string::translate("key.space"); + case GLFW_KEY_MINUS: return string::translate("key.minus"); } // Printable characters diff --git a/translations/en.json b/translations/en.json index 1837392d..99234f5e 100644 --- a/translations/en.json +++ b/translations/en.json @@ -2,6 +2,7 @@ "language": "English", "translators": "", "key.space": "Space", + "key.minus": "Minus", "key.escape": "Escape", "key.enter": "Enter", "key.tab": "Tab", @@ -19,6 +20,12 @@ "key.ctrl": "Ctrl", "key.shift": "Shift", "key.alt": "Alt", + "key.click": "Click", + "key.rightClick": "Right-click", + "key.middleClick": "Middle-click", + "key.doubleClick": "Double-click", + "key.drag": "Drag", + "key.clickDrag": "Click+Drag", "tag.Arpeggiator": "Arpeggiator", "tag.Attenuator": "Attenuator", "tag.Blank": "Blank", @@ -259,13 +266,11 @@ "Browser.allBrands": "All brands", "Browser.brand": "Brand", "Browser.allTags": "All tags", - "Browser.tagsSelectMultiple": "click to select multiple", + "Browser.tagsSelectMultiple": " 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 ", @@ -275,13 +280,85 @@ "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" + "PortWidget.history.moveCable": "move cable", + "library.loggingIn": "Logging in...", + "library.noResponse": "No response from server", + "library.noToken": "No token in response", + "library.queryingUpdates": "Querying for updates...", + "library.queryAccountFailed": "Could not query user account", + "library.queryManifestsFailed": "Could not query plugin manifests", + "library.queryModulesFailed": "Could not query user's modules", + "patch.saveFailed": "Could not save patch: %s", + "patch.overwriteTemplate": "Overwrite template patch?", + "patch.saveTemplateFailed": "Could not save template patch: %s", + "patch.loadTemplateFailed": "Could not load system template patch, clearing rack: %s", + "patch.loadTemplateConfirm": "The current patch is unsaved. Clear it and start a new patch?", + "patch.loadFailed": "Could not load patch: %s", + "patch.loadConfirm": "The current patch is unsaved. Clear it and open a new patch?", + "patch.revertConfirm": "Revert patch to the last saved state?", + "patch.unavailableModules": "This patch includes modules that are not installed:\n\n%s\n\nShow missing modules on the VCV Library?", + "Switch.history.move": "move switch", + "Knob.history.move": "move knob", + "TextField.cut": "Cut", + "TextField.copy": "Copy", + "TextField.paste": "Paste", + "TextField.selectAll": "Select all", + "standalone.multipleInstances": "VCV Rack is already running. Multiple Rack instances are not supported.", + "standalone.resetSettings": "Reset settings to default?", + "standalone.resDir": "VCV Rack's resource directory \"%s\" does not exist. Make sure Rack is correctly installed and launched.", + "standalone.micPermission": "VCV Rack cannot access audio input because Microphone permission is blocked.\n\nGive permission to Rack by opening Apple's System Settings and enabling Privacy & Security > Microphone > %s.", + "standalone.crashed": "VCV Rack crashed during the last session, possibly due to a buggy module in your patch. Clear your patch and start over?", + "AudioDisplay.audioDriver": "Audio driver", + "AudioDisplay.driver": "Driver: ", + "AudioDisplay.noDriver": "No driver", + "AudioDisplay.noDevice": "No device", + "AudioDisplay.audioDevice": "Audio device", + "AudioDisplay.device": "Device: ", + "AudioDisplay.lockedByDevice": "Locked by device", + "AudioDisplay.sampleRate": "Sample rate", + "AudioDisplay.sampleRateColon": "Rate: ", + "AudioDisplay.blockSize": "Block size", + "AudioDisplay.blockSizeColon": "Block size: ", + "MidiDisplay.driver": "MIDI driver", + "MidiDisplay.noDriver": "No driver", + "MidiDisplay.device": "MIDI device", + "MidiDisplay.noDevice": "No device", + "MidiDisplay.channel": "MIDI channel", + "MidiDisplay.channel1": "Channel 1", + "midi.allChannels": "All channels", + "midi.channelNum": "Channel %d", + "TipWindow.addModule": "To add a module to your patch, right-click an empty rack space or press Enter. Then click and drag a module from the Module Browser into the desired rack space.\\n\\nTo select multiple modules, click and drag on empty rack space.", + "TipWindow.moveRack": "To move around your patch, use the scroll bars, drag while holding the middle mouse button, %s and drag, or hold the arrow keys. Arrow key movement speed can be adjusted by holding %s, %s, or %s+%s.\\n\\nTo zoom in and out, drag the Zoom slider in the View menu, hold %s and scroll, or press %s and %s.", + "TipWindow.fullscreen": "You can use Rack in fullscreen mode by selecting \u201cView > Fullscreen\u201c or pressing F11.\\n\\nIn fullscreen mode, the menu bar and scroll bars are hidden. This is ideal for screen recording with VCV Recorder.", + "TipWindow.fullscreenButton": "Get VCV Recorder", + "TipWindow.library": "You can browse thousands of modules on the VCV Library website.\\n\\nRegister for a VCV account, log into Rack using the Library menu, and browse the VCV Library to add or purchase modules. Keep all plugins up to date by clicking \u201cLibrary > Update all\u201d.", + "TipWindow.libraryButton": "VCV Library", + "TipWindow.donation": "Some developers of free plugins accept donations. Right-click your favorite module's panel and select \u201cInfo > Donate\u201d.\\n\\nYou can also donate via the module's VCV Library page.", + "TipWindow.daw": "Want to use VCV Rack in your DAW? VCV Rack Pro is available for VST2, VST3, Audio Unit, and CLAP hosts.\\n\\nSupported DAWs include Ableton Live, Cubase, FL Studio, Reason, Bitwig, Reaper, Mixbus, Studio One, Cakewalk, Logic Pro, and GarageBand.", + "TipWindow.learnMore": "Learn more", + "TipWindow.manual": "You can learn more about VCV Rack by browsing the official manual.", + "TipWindow.manualButton": "VCV Rack manual", + "TipWindow.twitter": "Follow VCV Rack on Twitter for new module announcements, development news, and featured artists/music.", + "TipWindow.twitterHandle": "Twitter @vcvrack", + "TipWindow.polyphony": "Patch cables in Rack can carry up to 16 signals. You can use this ability to build polyphonic patches using modules having the \u201cPolyphonic\u201d tag. Cables carrying more than 1 signal appear thicker than normal cables. To try out polyphony, add the VCV MIDI-to-CV module to your patch, right-click its panel, and select your desired number of polyphonic channels.", + "TipWindow.polyphonyButton": "Learn more about polyphony in VCV Rack", + "TipWindow.develop": "Know C++ programming and want to create your own modules for Rack? Developing Rack modules is a great way to learn digital signal processing and quickly test your ideas with an easy-to-learn platform.\\n\\nDownload the Rack SDK and follow the development tutorial to get started.", + "TipWindow.developButton": "Plugin Development Tutorial", + "TipWindow.moduleManual": "Wondering how to use a particular module? Right-click its panel and choose \u201cInfo > User manual\u201d.\\n\\nYou can also open the module's Info menu to view the module's tags, website, VCV Library page, and changelog, if available.", + "TipWindow.modularGrid": "Did you know that the VCV Library is integrated with ModularGrid? If a module is available as a hardware Eurorack module, right-click its panel and choose \u201cInfo > ModularGrid\u201d, or click the \u201cModularGrid\u201d link on its VCV Library page.\\n\\nOn ModularGrid.net, search for the VCV logo on certain module's entry pages.", + "TipWindow.modularGridButton": "Example: Grayscale Permutation on ModularGrid", + "TipWindow.menuKeepOpen": "When any context menu is open, you can %s a menu item to keep the menu open. This can be useful when browsing module presets or settings.", + "TipWindow.welcome": "Welcome to %s", + "TipWindow.startup": "Show tips at startup", + "TipWindow.previous": "Previous", + "TipWindow.next": "Next", + "TipWindow.close": "Close", + "PortInfo.input": "%s input", + "PortInfo.output": "%s output", + "ModuleLightWidget.light": "%s light" } \ No newline at end of file