From d469ea99cb05aa41351d70b0b3b86a9a0a61620c Mon Sep 17 00:00:00 2001 From: reionwong Date: Mon, 19 Oct 2026 23:22:36 +0800 Subject: [PATCH] feat(i18n): translate the interface and the desktop file --- CMakeLists.txt | 4 +- cutefish-terminal.desktop | 1 - po/cutefish-terminal.pot | 211 ++++++++++++++++++++++++++++++++++++++ src/findbar.cpp | 4 +- src/main.cpp | 2 + src/settingsdialog.cpp | 37 ++++--- src/terminalpage.cpp | 33 +++--- src/terminalwindow.cpp | 28 ++--- 8 files changed, 275 insertions(+), 45 deletions(-) create mode 100644 po/cutefish-terminal.pot diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b23bf8..e140e3a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -28,7 +28,9 @@ target_include_directories(${PROJECT_NAME} PRIVATE src) target_link_libraries(${PROJECT_NAME} PRIVATE terminal-view) install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}) -install(FILES cutefish-terminal.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) + +cutefish_add_translations(cutefish-terminal SOURCES src DESKTOP_FILES cutefish-terminal.desktop) +install(FILES ${CMAKE_CURRENT_BINARY_DIR}/cutefish-terminal.desktop DESTINATION ${CMAKE_INSTALL_DATADIR}/applications) if(BUILD_TESTING) add_subdirectory(tests) diff --git a/cutefish-terminal.desktop b/cutefish-terminal.desktop index 4979792..1c493eb 100644 --- a/cutefish-terminal.desktop +++ b/cutefish-terminal.desktop @@ -1,7 +1,6 @@ [Desktop Entry] Version=1.0 Name=Terminal -Name[zh_CN]=终端 Exec=/usr/bin/cutefish-terminal Icon=utilities-terminal Terminal=false diff --git a/po/cutefish-terminal.pot b/po/cutefish-terminal.pot new file mode 100644 index 0000000..1641db8 --- /dev/null +++ b/po/cutefish-terminal.pot @@ -0,0 +1,211 @@ +#, fuzzy +msgid "" +msgstr "" +"Project-Id-Version: cutefish-terminal\n" +"Report-Msgid-Bugs-To: https://github.com/cutefishos\n" +"POT-Creation-Date: 2026-10-19 22:34+0800\n" +"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" +"Last-Translator: \n" +"Language-Team: \n" +"Language: \n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n" + +#: src/findbar.cpp +msgid "Find" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Theme" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Font" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Font Size" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Blinking Cursor" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Opacity" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Window Blur" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Cursor Shape" +msgstr "" + +#. TRANSLATORS: the shapes of the text cursor. +#: src/settingsdialog.cpp +msgid "Block" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Underline" +msgstr "" + +#: src/settingsdialog.cpp +msgid "I-Beam" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Scrollback" +msgstr "" + +#: src/settingsdialog.cpp +msgid "1,000 lines" +msgstr "" + +#: src/settingsdialog.cpp +msgid "10,000 lines" +msgstr "" + +#: src/settingsdialog.cpp +msgid "100,000 lines" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Unlimited" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Flash on Bell" +msgstr "" + +#: src/settingsdialog.cpp +msgid "Warn Before Pasting Multiple Lines" +msgstr "" + +#. TRANSLATORS: a font size in points. +#: src/settingsdialog.cpp src/terminalwindow.cpp +#, python-brace-format +msgid "{size} pt" +msgstr "" + +#. TRANSLATORS: a percentage, such as "80%". +#: src/settingsdialog.cpp +#, python-brace-format +msgid "{percent}%" +msgstr "" + +#: src/settingsdialog.cpp src/terminalpage.cpp +msgid "Settings" +msgstr "" + +#: src/terminalpage.cpp +msgid "Open Link" +msgstr "" + +#: src/terminalpage.cpp +msgid "Copy Link" +msgstr "" + +#: src/terminalpage.cpp +#, python-brace-format +msgid "Open “{name}”" +msgstr "" + +#: src/terminalpage.cpp +msgid "Show in File Manager" +msgstr "" + +#: src/terminalpage.cpp +msgid "Copy" +msgstr "" + +#: src/terminalpage.cpp src/terminalwindow.cpp +msgid "Paste" +msgstr "" + +#: src/terminalpage.cpp +msgid "Select All" +msgstr "" + +#: src/terminalpage.cpp +msgid "Find…" +msgstr "" + +#: src/terminalpage.cpp +msgid "Clear Scrollback" +msgstr "" + +#: src/terminalpage.cpp +msgid "Reset Terminal" +msgstr "" + +#: src/terminalpage.cpp +msgid "New Tab" +msgstr "" + +#: src/terminalpage.cpp +msgid "New Window" +msgstr "" + +#: src/terminalpage.cpp +msgid "Open File Manager" +msgstr "" + +#: src/terminalpage.cpp +msgid "Exit full screen" +msgstr "" + +#: src/terminalpage.cpp +msgid "Full screen" +msgstr "" + +#: src/terminalwindow.cpp +msgid "Rename Tab…" +msgstr "" + +#: src/terminalwindow.cpp +msgid "Close Tab" +msgstr "" + +#: src/terminalwindow.cpp +msgid "Close Other Tabs" +msgstr "" + +#: src/terminalwindow.cpp +msgid "Process is running, are you sure you want to quit?" +msgstr "" + +#: src/terminalwindow.cpp +msgid "Cancel" +msgstr "" + +#: src/terminalwindow.cpp +msgid "OK" +msgstr "" + +#: src/terminalwindow.cpp +msgid "Open this link?" +msgstr "" + +#: src/terminalwindow.cpp +msgid "Open" +msgstr "" + +#: src/terminalwindow.cpp +#, python-brace-format +msgid "Paste {n} line?" +msgid_plural "Paste {n} lines?" +msgstr[0] "" +msgstr[1] "" + +#: src/terminalwindow.cpp +msgid "Paste this line and press Enter?" +msgstr "" + +#: cutefish-terminal.desktop +msgid "Terminal" +msgstr "" diff --git a/src/findbar.cpp b/src/findbar.cpp index d3e0f62..26ab6e8 100644 --- a/src/findbar.cpp +++ b/src/findbar.cpp @@ -5,6 +5,7 @@ #include +#include "base/i18n.h" #include "base/strings.h" #include "ui/header_button.h" #include "ui/keys.h" @@ -16,6 +17,7 @@ #include "TerminalView.h" using namespace cutefish::ui; +using cutefish::i18n::tr; // The field's keys: Enter searches, Shift+Enter searches the other way, Escape closes. class FindField : public TextField @@ -58,7 +60,7 @@ FindBar::FindBar(Konsole::TerminalView* terminal) setVisible(false); m_field->setFrameVisible(false); - m_field->setPlaceholder("Find"); + m_field->setPlaceholder(tr("Find")); m_field->setTextMargins(0, 0); m_previous->setGlyph("chevron-up", 16); m_next->setGlyph("chevron-down", 16); diff --git a/src/main.cpp b/src/main.cpp index 169a0e0..896af13 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -10,6 +10,7 @@ #include #include +#include "base/i18n.h" #include "ui/application.h" #include "terminalsettings.h" @@ -96,6 +97,7 @@ int main(int argc, char** argv) ui::Application app(argc, argv); app.setApplicationId("cutefish-terminal"); + i18n::setDomain("cutefish-terminal"); // Character widths follow the locale. std::setlocale(LC_ALL, ""); diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index 5c20061..4ad63c4 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -8,6 +8,7 @@ #include #include "base/event_loop.h" +#include "base/i18n.h" #include "ui/combo_box.h" #include "ui/dialog_window.h" #include "ui/label.h" @@ -25,6 +26,8 @@ #include "terminalwindow.h" using namespace cutefish::ui; +using cutefish::i18n::format; +using cutefish::i18n::tr; // Bundled schemes, by file name, and their labels. static const std::vector kThemeNames = { @@ -269,11 +272,11 @@ public: TerminalSettings& settings = window->settings(); Card* appearance = addCard(); - theme = appearance->addRow("Theme")->addControl(kThemeLabels); + theme = appearance->addRow(tr("Theme"))->addControl(kThemeLabels); setWidth(theme, 200); theme->onActivated = [&settings](int index) { settings.setColorScheme(kThemeNames[size_t(index)]); }; - SettingRow* fontRow = appearance->addRow("Font"); + SettingRow* fontRow = appearance->addRow(tr("Font")); font = fontRow->addControl(); setWidth(font, 200); font->onActivated = [this, &settings](int index) { @@ -281,7 +284,7 @@ public: settings.setFontName(m_families[size_t(index)]); }; - SettingRow* sizeRow = appearance->addRow("Font Size"); + SettingRow* sizeRow = appearance->addRow(tr("Font Size")); fontSize = sizeRow->addControl(); sizeRow->setControlWidth(fontSize, 160); fontSize->setRange(5, 30); @@ -289,11 +292,11 @@ public: fontSize->onMoved = [&settings](double value) { settings.setFontPointSize(int(std::lround(value))); }; fontSizeValue = addValueLabel(sizeRow); - blinking = appearance->addRow("Blinking Cursor", true)->addControl(); + blinking = appearance->addRow(tr("Blinking Cursor"), true)->addControl(); blinking->onToggled = [&settings](bool checked) { settings.setBlinkingCursor(checked); }; Card* window2 = addCard(); - SettingRow* opacityRow = window2->addRow("Opacity"); + SettingRow* opacityRow = window2->addRow(tr("Opacity")); opacity = opacityRow->addControl(); opacityRow->setControlWidth(opacity, 160); opacity->setRange(0.1, 1.0); @@ -301,24 +304,25 @@ public: opacity->onMoved = [&settings](double value) { settings.setOpacity(value); }; opacityValue = addValueLabel(opacityRow); - blur = window2->addRow("Window Blur", true)->addControl(); + blur = window2->addRow(tr("Window Blur"), true)->addControl(); blur->onToggled = [&settings](bool checked) { settings.setBlur(checked); }; Card* behaviour = addCard(); // In the order of the cursorShape values. - cursorShape = behaviour->addRow("Cursor Shape")->addControl(std::vector{ - "Block", "Underline", "I-Beam"}); + cursorShape = behaviour->addRow(tr("Cursor Shape"))->addControl(std::vector{ + // TRANSLATORS: the shapes of the text cursor. + tr("Block"), tr("Underline"), tr("I-Beam")}); setWidth(cursorShape, 160); cursorShape->onActivated = [&settings](int index) { settings.setCursorShape(index); }; - scrollback = behaviour->addRow("Scrollback")->addControl(std::vector{ - "1,000 lines", "10,000 lines", "100,000 lines", "Unlimited"}); + scrollback = behaviour->addRow(tr("Scrollback"))->addControl(std::vector{ + tr("1,000 lines"), tr("10,000 lines"), tr("100,000 lines"), tr("Unlimited")}); setWidth(scrollback, 160); scrollback->onActivated = [&settings](int index) { settings.setScrollbackLines(kScrollbackLines[size_t(index)]); }; - visualBell = behaviour->addRow("Flash on Bell")->addControl(); + visualBell = behaviour->addRow(tr("Flash on Bell"))->addControl(); visualBell->onToggled = [&settings](bool checked) { settings.setVisualBell(checked); }; - confirmPaste = behaviour->addRow("Warn Before Pasting Multiple Lines", true)->addControl(); + confirmPaste = behaviour->addRow(tr("Warn Before Pasting Multiple Lines"), true)->addControl(); confirmPaste->onToggled = [&settings](bool checked) { settings.setConfirmMultilinePaste(checked); }; } @@ -341,10 +345,13 @@ public: font->setCurrentIndex(family == m_families.end() ? 0 : int(family - m_families.begin())); fontSize->setValue(settings.fontPointSize()); - fontSizeValue->setText(std::to_string(settings.fontPointSize()) + " pt"); + // TRANSLATORS: a font size in points. + fontSizeValue->setText(format(tr("{size} pt"), {{"size", std::to_string(settings.fontPointSize())}})); blinking->setChecked(settings.blinkingCursor()); opacity->setValue(settings.opacity()); - opacityValue->setText(std::to_string(int(std::lround(settings.opacity() * 100))) + "%"); + // TRANSLATORS: a percentage, such as "80%". + opacityValue->setText(format(tr("{percent}%"), + {{"percent", std::to_string(int(std::lround(settings.opacity() * 100)))}})); blur->setChecked(settings.blur()); cursorShape->setCurrentIndex(settings.cursorShape()); const auto lines = std::find(kScrollbackLines.begin(), kScrollbackLines.end(), settings.scrollbackLines()); @@ -434,7 +441,7 @@ void SettingsDialog::update() void SettingsDialog::show() { if (!m_dialog) { - m_dialog = std::make_unique(&m_window->window(), "Settings", kDialogWidth); + m_dialog = std::make_unique(&m_window->window(), tr("Settings"), kDialogWidth); m_content = m_dialog->setContent(m_window); m_dialog->onClosed = [this] { m_content = nullptr; diff --git a/src/terminalpage.cpp b/src/terminalpage.cpp index 9643454..0a75ab3 100644 --- a/src/terminalpage.cpp +++ b/src/terminalpage.cpp @@ -6,6 +6,7 @@ #include #include +#include "base/i18n.h" #include "base/strings.h" #include "base/url.h" #include "ui/clipboard.h" @@ -26,6 +27,8 @@ #include "terminalwindow.h" using namespace cutefish::ui; +using cutefish::i18n::format; +using cutefish::i18n::tr; namespace strings = cutefish::base::strings; // The terminal's margins inside the page; the top is the header's line. @@ -400,34 +403,34 @@ void TerminalPage::openMenu(double x, double y) m_menu->clear(); if (!m_menuLink.empty()) { - m_menu->addItem("Open Link", [this] { m_window->openLink(m_menuLink); }); - m_menu->addItem("Copy Link", [this] { Clipboard::setText(m_menuLink); }); + m_menu->addItem(tr("Open Link"), [this] { m_window->openLink(m_menuLink); }); + m_menu->addItem(tr("Copy Link"), [this] { Clipboard::setText(m_menuLink); }); m_menu->addSeparator(); } if (!m_menuPath.empty()) { const std::string name = m_menuPath.substr(m_menuPath.rfind('/') + 1); - m_menu->addItem("Open \xe2\x80\x9c" + elided(name.empty() ? m_menuPath : name, 30) + "\xe2\x80\x9d", + m_menu->addItem(format(tr("Open “{name}”"), {{"name", elided(name.empty() ? m_menuPath : name, 30)}}), [this] { ProcessHelper::openUrl(m_menuPath); }); - m_menu->addItem("Show in File Manager", [this] { ProcessHelper::showInFileManager(m_menuPath); }); + m_menu->addItem(tr("Show in File Manager"), [this] { ProcessHelper::showInFileManager(m_menuPath); }); m_menu->addSeparator(); } if (m_terminal->hasSelection()) - m_menu->addItem("Copy", [this] { m_terminal->copyClipboard(); }); + m_menu->addItem(tr("Copy"), [this] { m_terminal->copyClipboard(); }); if (m_menuCanPaste) - m_menu->addItem("Paste", [this] { m_terminal->pasteClipboard(); }); - m_menu->addItem("Select All", [this] { m_terminal->selectAll(); }); - m_menu->addItem("Find\xe2\x80\xa6", [this] { openFind(); }); + m_menu->addItem(tr("Paste"), [this] { m_terminal->pasteClipboard(); }); + m_menu->addItem(tr("Select All"), [this] { m_terminal->selectAll(); }); + m_menu->addItem(tr("Find…"), [this] { openFind(); }); if (m_terminal->scrollbarMaximum() > 0) - m_menu->addItem("Clear Scrollback", [this] { m_session->clearScrollback(); }); - m_menu->addItem("Reset Terminal", [this] { m_session->resetTerminal(); }); + m_menu->addItem(tr("Clear Scrollback"), [this] { m_session->clearScrollback(); }); + m_menu->addItem(tr("Reset Terminal"), [this] { m_session->resetTerminal(); }); m_menu->addSeparator(); - m_menu->addItem("New Tab", [this] { m_window->openNewTab(); }); - m_menu->addItem("New Window", [this] { m_window->newWindow(); }); - m_menu->addItem("Open File Manager", [this] { ProcessHelper::openFileManager(m_session->currentDir()); }); + m_menu->addItem(tr("New Tab"), [this] { m_window->openNewTab(); }); + m_menu->addItem(tr("New Window"), [this] { m_window->newWindow(); }); + m_menu->addItem(tr("Open File Manager"), [this] { ProcessHelper::openFileManager(m_session->currentDir()); }); m_menu->addSeparator(); - m_menu->addItem(m_window->isFullScreen() ? "Exit full screen" : "Full screen", + m_menu->addItem(m_window->isFullScreen() ? tr("Exit full screen") : tr("Full screen"), [this] { m_window->toggleFullScreen(); }); - m_menu->addItem("Settings", [this] { m_window->showSettings(); }); + m_menu->addItem(tr("Settings"), [this] { m_window->showSettings(); }); if (Window* window = this->window()) m_menu->popup(window, position); diff --git a/src/terminalwindow.cpp b/src/terminalwindow.cpp index 7e7075b..573f90e 100644 --- a/src/terminalwindow.cpp +++ b/src/terminalwindow.cpp @@ -7,6 +7,7 @@ #include #include "base/event_loop.h" +#include "base/i18n.h" #include "base/strings.h" #include "ui/animation.h" #include "ui/dialog_window.h" @@ -32,6 +33,9 @@ #include "windows.h" using namespace cutefish::ui; +using cutefish::i18n::format; +using cutefish::i18n::tr; +using cutefish::i18n::trn; namespace strings = cutefish::base::strings; static constexpr double kHeaderHeight = 40; @@ -205,7 +209,7 @@ public: // Shows the size briefly while zooming. void showZoom(int pointSize) { - m_zoomLabel->setText(std::to_string(pointSize) + " pt"); + m_zoomLabel->setText(format(tr("{size} pt"), {{"size", std::to_string(pointSize)}})); if (m_zoomFade) m_zoomFade->stop(); m_zoomOpacity = 1; @@ -602,11 +606,11 @@ void TerminalWindow::openTabMenu(int index, PointF position) if (!m_tabMenu) m_tabMenu = std::make_unique(); m_tabMenu->clear(); - m_tabMenu->addItem("Rename Tab\xe2\x80\xa6", [this] { m_headerContent->tabBar->startRename(m_tabMenuIndex); }); + m_tabMenu->addItem(tr("Rename Tab…"), [this] { m_headerContent->tabBar->startRename(m_tabMenuIndex); }); m_tabMenu->addSeparator(); - m_tabMenu->addItem("Close Tab", [this] { closeProtection(m_tabMenuIndex); }); + m_tabMenu->addItem(tr("Close Tab"), [this] { closeProtection(m_tabMenuIndex); }); if (tabCount() > 1) - m_tabMenu->addItem("Close Other Tabs", [this] { closeOtherTabs(m_tabMenuIndex); }); + m_tabMenu->addItem(tr("Close Other Tabs"), [this] { closeOtherTabs(m_tabMenuIndex); }); m_tabMenu->popup(m_window.get(), position); } @@ -634,9 +638,9 @@ void TerminalWindow::confirmClose(const std::vector& pages, std:: } if (!m_exitPrompt) { - m_exitPrompt = std::make_unique(m_window.get(), "Process is running, are you sure you want to quit?"); - m_exitPrompt->setCancelText("Cancel"); - m_exitPrompt->setConfirmText("OK"); + m_exitPrompt = std::make_unique(m_window.get(), tr("Process is running, are you sure you want to quit?")); + m_exitPrompt->setCancelText(tr("Cancel")); + m_exitPrompt->setConfirmText(tr("OK")); m_exitPrompt->setDestructive(true); m_exitPrompt->onAccepted = [this] { if (auto pending = std::exchange(m_pendingAction, nullptr)) @@ -782,8 +786,8 @@ void TerminalWindow::openLink(const std::string& url) } if (!m_linkConfirm) { - m_linkConfirm = std::make_unique(m_window.get(), "Open this link?"); - m_linkConfirm->setConfirmText("Open"); + m_linkConfirm = std::make_unique(m_window.get(), tr("Open this link?")); + m_linkConfirm->setConfirmText(tr("Open")); m_linkConfirm->onAccepted = [this] { ProcessHelper::openUrl(m_pendingLink); }; } m_pendingLink = url; @@ -810,7 +814,7 @@ void TerminalWindow::confirmPaste(const std::string& text, std::function if (!m_pasteConfirm) { m_pasteConfirm = std::make_unique(m_window.get(), std::string()); - m_pasteConfirm->setConfirmText("Paste"); + m_pasteConfirm->setConfirmText(tr("Paste")); m_pasteConfirm->onAccepted = [this] { if (auto pending = std::exchange(m_pendingPaste, nullptr)) pending(); @@ -819,8 +823,8 @@ void TerminalWindow::confirmPaste(const std::string& text, std::function } const size_t count = lines.size(); - m_pasteConfirm->setTitle(count > 1 ? "Paste " + std::to_string(count) + " lines?" - : "Paste this line and press Enter?"); + m_pasteConfirm->setTitle(count > 1 ? trn("Paste {n} line?", "Paste {n} lines?", count) + : std::string(tr("Paste this line and press Enter?"))); m_pasteConfirm->setText(strings::join(shown, "\n")); m_pendingPaste = std::move(action); m_pasteConfirm->show();