feat(i18n): translate the interface and the desktop file

main
reionwong 1 week ago
parent 8d613502f1
commit 9f678a8d70

@ -75,8 +75,9 @@ target_link_libraries(${PROJECT_NAME} PRIVATE settings-ui)
install(TARGETS ${PROJECT_NAME} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
install(DIRECTORY src/images/ DESTINATION ${SETTINGS_DATA_DIR}/images)
cutefish_add_translations(cutefish-settings SOURCES src DESKTOP_FILES cutefish-settings.desktop)
install(FILES
cutefish-settings.desktop
${CMAKE_CURRENT_BINARY_DIR}/cutefish-settings.desktop
DESTINATION ${CMAKE_INSTALL_DATADIR}/applications
)

@ -1,6 +1,5 @@
[Desktop Entry]
Name=Settings
Name[zh_CN]=设置
Exec=/usr/bin/cutefish-settings
Icon=preferences-system
Terminal=false

File diff suppressed because it is too large Load Diff

@ -5,6 +5,7 @@
#include <string>
#include "base/dbus.h"
#include "base/i18n.h"
#include "ui/application.h"
#include "window/options.h"
#include "window/settingswindow.h"
@ -68,6 +69,7 @@ int main(int argc, char** argv)
ui::Application application(argc, argv);
application.setApplicationId("cutefish-settings");
i18n::setDomain("cutefish-settings");
// A running instance takes the request, and this process ends.
base::dbus::Connection* bus = application.sessionBus();

@ -12,6 +12,12 @@
#include <sys/sysinfo.h>
#include <sys/utsname.h>
#include "base/i18n.h"
using cutefish::i18n::format;
using cutefish::i18n::tr;
using cutefish::i18n::trNoop;
namespace settings::about {
static std::string unquoted(std::string value)
@ -165,15 +171,17 @@ std::string processor()
}
}
if (!found)
return "Unknown";
return tr("Unknown");
if (count > 0)
model += " x " + std::to_string(count);
// TRANSLATORS: a processor model and its number of cores, such as "Intel Core i5 x 8".
model = format(tr("{model} x {count}"), {{"model", model}, {"count", std::to_string(count)}});
return model;
}
std::string formatByteSize(double size)
{
static const char* const units[] = {"B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"};
static const char* const units[] = {trNoop("B"), trNoop("KB"), trNoop("MB"), trNoop("GB"), trNoop("TB"),
trNoop("PB"), trNoop("EB"), trNoop("ZB"), trNoop("YB")};
int unit = 0;
while (std::abs(size) >= 1024 && unit < 8) {
size /= 1024;
@ -181,7 +189,8 @@ std::string formatByteSize(double size)
}
char number[64];
snprintf(number, sizeof(number), "%.0f", size);
return std::string(number) + " " + units[unit];
// TRANSLATORS: a size, such as "16 GB".
return format(tr("{value} {unit}"), {{"value", number}, {"unit", tr(units[unit])}});
}
std::string memorySize()
@ -192,14 +201,20 @@ std::string memorySize()
return formatByteSize(double(info.totalram) * info.mem_unit);
}
static std::string storageText(const std::string& used, const std::string& total)
{
// TRANSLATORS: the space used on the system disk and its size, such as "40 GB / 256 GB".
return format(tr("{used} / {total}"), {{"used", used}, {"total", total}});
}
std::string internalStorage()
{
struct statvfs root;
if (statvfs("/", &root) != 0)
return formatByteSize(0) + " / " + formatByteSize(0);
return storageText(formatByteSize(0), formatByteSize(0));
const double total = double(root.f_blocks) * root.f_frsize;
const double available = double(root.f_bavail) * root.f_frsize;
return formatByteSize(total - available) + " / " + formatByteSize(total);
return storageText(formatByteSize(total - available), formatByteSize(total));
}
} // namespace settings::about

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "pages/about/aboutinfo.h"
#include "ui/label.h"
#include "ui/cell_layout.h"
@ -13,6 +14,8 @@
#include "window/pagelist.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -22,7 +25,7 @@ class AboutPage : public Page
{
public:
explicit AboutPage(SettingsWindow& window)
: Page(window, "About")
: Page(window, tr("About"))
{
// A nested layout never grows past its children's width, so it is
// centred itself rather than filled.
@ -39,19 +42,20 @@ public:
name->setWeight(700);
name->setColor(Color::rgb(0x3385FF));
name->setLayoutHints({.horizontalAlignment = Alignment::Center, .verticalAlignment = Alignment::Start});
m_runningOn = brand->addChild<Label>("Running on " + about::prettyProductName());
m_runningOn = brand->addChild<Label>(
format(tr("Running on {system}"), {{"system", about::prettyProductName()}}));
m_runningOn->setLayoutHints({.horizontalAlignment = Alignment::Center, .verticalAlignment = Alignment::Start});
Card* card = content()->addChild<Card>(Card::Style::List);
const std::string model = about::computerModel();
if (!model.empty())
card->addChild<InfoRow>("Model", model);
card->addChild<InfoRow>("System version", about::prettyProductName());
card->addChild<InfoRow>("System type", about::architecture());
card->addChild<InfoRow>("Kernel version", about::kernelVersion());
card->addChild<InfoRow>("Processor", about::processor());
card->addChild<InfoRow>("RAM", about::memorySize());
card->addChild<InfoRow>("Internal storage", about::internalStorage());
card->addChild<InfoRow>(tr("Model"), model);
card->addChild<InfoRow>(tr("System version"), about::prettyProductName());
card->addChild<InfoRow>(tr("System type"), about::architecture());
card->addChild<InfoRow>(tr("Kernel version"), about::kernelVersion());
card->addChild<InfoRow>(tr("Processor"), about::processor());
card->addChild<InfoRow>(tr("RAM"), about::memorySize());
card->addChild<InfoRow>(tr("Internal storage"), about::internalStorage());
}
protected:

@ -7,6 +7,7 @@
#include "cutefish/accounts/accounts_manager.h"
#include "base/event_loop.h"
#include "base/i18n.h"
#include "base/paths.h"
#include "pages/accounts/password.h"
#include "pages/accounts/passwordchanger.h"
@ -25,6 +26,8 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -34,11 +37,11 @@ class AccountsPage : public Page
{
public:
explicit AccountsPage(SettingsWindow& window)
: Page(window, "Users")
: Page(window, tr("Users"))
{
m_users = content()->addChild<Card>(Card::Style::List);
m_users->setLayoutHints({.fillWidth = true});
StandardButton* add = content()->addChild<StandardButton>("Add user");
StandardButton* add = content()->addChild<StandardButton>(tr("Add user"));
add->onClicked = [this] {
m_addUser = std::make_unique<AddUserDialog>(settingsWindow(), m_manager);
m_addUser->onClosed = [this] { dispose(m_addUser); };
@ -93,8 +96,9 @@ private:
m_chooser->setModal(true);
m_chooser->setFilters(
{FileChooser::Filter::fromNameFilter(
"Image files (*.jpg *.jpeg *.png *.webp *.bmp *.tif *.tiff *.avif)"),
FileChooser::Filter::fromNameFilter("All files (*)")});
format(tr("Image files ({patterns})"),
{{"patterns", "*.jpg *.jpeg *.png *.webp *.bmp *.tif *.tiff *.avif"}})),
FileChooser::Filter::fromNameFilter(tr("All files (*)"))});
m_chooser->setCurrentFolder(base::paths::userDirectory(base::paths::UserDirectory::Pictures));
m_chooser->onAccepted = [this, account](const std::vector<std::string>& paths, const std::string&) {
if (paths.empty())
@ -139,12 +143,12 @@ private:
if (uid == int64_t(getuid()))
return;
m_confirm = std::make_unique<ConfirmDialog>(
&settingsWindow().window(), "Delete this user",
"Delete user “" + account->userName()
+ "”? Their home directory and its contents will be permanently deleted. This "
"cannot be undone.");
m_confirm->setCancelText("Cancel");
m_confirm->setConfirmText("Delete");
&settingsWindow().window(), tr("Delete this user"),
format(tr("Delete user “{name}”? Their home directory and its contents will be permanently deleted. "
"This cannot be undone."),
{{"name", account->userName()}}));
m_confirm->setCancelText(tr("Cancel"));
m_confirm->setConfirmText(tr("Delete"));
m_confirm->setDestructive(true);
m_confirm->onAccepted = [this, uid] { m_manager.deleteUser(uid, true); };
m_confirm->show();

@ -3,6 +3,7 @@
#include "pages/accounts/userdetailsdialog.h"
#include "base/i18n.h"
#include "ui/cell_layout.h"
#include "ui/dialog_window.h"
#include "ui/keys.h"
@ -14,6 +15,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -30,8 +32,8 @@ UserDetailsDialog::UserDetailsDialog(SettingsWindow& window, std::shared_ptr<acc
content->setSpacing(metrics::dialogSpacing);
Card* card = content->addChild<Card>(Card::Style::List);
m_type = card->addChild<InfoRow>("Account type");
m_password = card->addChild<ListRow>("Change password…");
m_type = card->addChild<InfoRow>(tr("Account type"));
m_password = card->addChild<ListRow>(tr("Change password…"));
m_password->setName("user:changePassword");
m_password->onClicked = [this] {
close();
@ -42,7 +44,7 @@ UserDetailsDialog::UserDetailsDialog(SettingsWindow& window, std::shared_ptr<acc
RowLayout* buttons = content->addChild<RowLayout>();
buttons->setSpacing(metrics::dialogButtonSpacing);
buttons->setLayoutHints({.horizontalAlignment = Alignment::End});
m_delete = buttons->addChild<DialogButton>("Delete this user", true);
m_delete = buttons->addChild<DialogButton>(tr("Delete this user"), true);
m_delete->setName("user:delete");
m_delete->setDestructive(true);
m_delete->onClicked = [this] {
@ -78,7 +80,8 @@ void UserDetailsDialog::close()
void UserDetailsDialog::update()
{
m_dialog->setTitle(m_account->userName());
m_type->setValue(m_account->accountType() == accounts::UserAccount::Standard ? "Standard" : "Administrator");
m_type->setValue(m_account->accountType() == accounts::UserAccount::Standard ? tr("Standard")
: tr("Administrator"));
m_delete->setEnabled(m_account->userId() != m_loggedInUid);
}

@ -3,6 +3,7 @@
#include "pages/accounts/userdialogs.h"
#include "base/i18n.h"
#include "cutefish/accounts/accounts_manager.h"
#include "pages/accounts/password.h"
#include "ui/combo_box.h"
@ -15,6 +16,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -56,7 +58,7 @@ protected:
ChangePasswordDialog::ChangePasswordDialog(SettingsWindow& parent, const std::string& userName,
std::unique_ptr<PasswordChanger> changer)
: FormDialog(parent, "Change password", kWidth)
: FormDialog(parent, tr("Change password"), kWidth)
, m_changer(std::move(changer))
{
Label* name = content()->addChild<SecondaryLabel>(userName);
@ -65,12 +67,12 @@ ChangePasswordDialog::ChangePasswordDialog(SettingsWindow& parent, const std::st
m_form = addForm(content());
if (m_changer) {
m_current = addField(m_form, "Current password", true);
m_password = addField(m_form, "New password", true);
m_current = addField(m_form, tr("Current password"), true);
m_password = addField(m_form, tr("New password"), true);
} else {
m_password = addField(m_form, "Password", true);
m_password = addField(m_form, tr("Password"), true);
}
m_verify = addField(m_form, "Verify password", true);
m_verify = addField(m_form, tr("Verify password"), true);
if (m_changer) {
m_error = content()->addChild<Label>();
@ -85,19 +87,19 @@ ChangePasswordDialog::ChangePasswordDialog(SettingsWindow& parent, const std::st
case PasswordChanger::Result::WrongPassword:
m_current->setText({});
m_current->setFocus();
showError("The current password is incorrect");
showError(tr("The current password is incorrect"));
break;
case PasswordChanger::Result::Rejected:
m_password->setText({});
m_verify->setText({});
m_password->setFocus();
showError(message.empty() ? "The new password was not accepted" : message);
showError(message.empty() ? tr("The new password was not accepted") : message);
break;
case PasswordChanger::Result::Failed:
showError(message.empty() ? "The password could not be changed" : message);
showError(message.empty() ? tr("The password could not be changed") : message);
break;
case PasswordChanger::Result::TimedOut:
showError("The password change did not finish in time");
showError(tr("The password change did not finish in time"));
break;
}
update();
@ -105,9 +107,9 @@ ChangePasswordDialog::ChangePasswordDialog(SettingsWindow& parent, const std::st
}
RowLayout* buttons = addButtonRow();
m_cancel = buttons->addChild<DialogButton>("Cancel");
m_cancel = buttons->addChild<DialogButton>(tr("Cancel"));
m_cancel->onClicked = [this] { requestClose(); };
m_change = buttons->addChild<DialogButton>("Change password", true);
m_change = buttons->addChild<DialogButton>(tr("Change password"), true);
m_change->onClicked = [this] { change(); };
for (TextField* field : {m_current, m_password, m_verify}) {
@ -141,7 +143,7 @@ void ChangePasswordDialog::change()
showError({});
if (!m_changer->start(m_current->text(), m_password->text()))
showError("The password could not be changed");
showError(tr("The password could not be changed"));
update();
}
@ -152,15 +154,15 @@ void ChangePasswordDialog::showError(const std::string& error)
}
AddUserDialog::AddUserDialog(SettingsWindow& parent, accounts::AccountsManager& manager)
: FormDialog(parent, "Add new user", kWidth)
: FormDialog(parent, tr("Add new user"), kWidth)
, m_manager(manager)
{
m_form = addForm(content());
m_name = addField(m_form, "User name", false);
m_password = addField(m_form, "Password", true);
m_verify = addField(m_form, "Verify password", true);
m_form->addChild<Label>("Account type");
m_type = m_form->addChild<ComboBox>(std::vector<std::string>{"Standard", "Administrator"});
m_name = addField(m_form, tr("User name"), false);
m_password = addField(m_form, tr("Password"), true);
m_verify = addField(m_form, tr("Verify password"), true);
m_form->addChild<Label>(tr("Account type"));
m_type = m_form->addChild<ComboBox>(std::vector<std::string>{tr("Standard"), tr("Administrator")});
m_type->setCurrentIndex(0);
m_type->setLayoutHints({.fillWidth = true});
@ -170,9 +172,9 @@ AddUserDialog::AddUserDialog(SettingsWindow& parent, accounts::AccountsManager&
m_error->setVisible(false);
RowLayout* buttons = addButtonRow();
m_cancel = buttons->addChild<DialogButton>("Cancel");
m_cancel = buttons->addChild<DialogButton>(tr("Cancel"));
m_cancel->onClicked = [this] { requestClose(); };
m_add = buttons->addChild<DialogButton>("Add", true);
m_add = buttons->addChild<DialogButton>(tr("Add"), true);
m_add->onClicked = [this] { add(); };
for (TextField* field : {m_name, m_password, m_verify})

@ -10,6 +10,7 @@
#include <unicode/unistr.h>
#include <unicode/utf16.h>
#include "base/i18n.h"
#include "base/transform.h"
#include "ui/animation.h"
#include "ui/cell_layout.h"
@ -24,6 +25,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -208,7 +210,7 @@ UserRow::UserRow(std::shared_ptr<accounts::UserAccount> account, int64_t loggedI
m_userName = addContent<Label>();
m_realName = addContent<Label>();
addContent<View>()->setLayoutHints({.fillWidth = true});
m_loggedIn = addContent<Label>("Logged in");
m_loggedIn = addContent<Label>(tr("Logged in"));
m_info = addContent<IconButton>("info", 20);
m_info->onClicked = [this] {
if (onInfo)

@ -4,6 +4,7 @@
#include <array>
#include <functional>
#include "base/i18n.h"
#include "cutefish/appearance/appearance.h"
#include "ui/icon.h"
#include "ui/layout.h"
@ -23,6 +24,7 @@
using namespace cutefish::ui;
using cutefish::appearance::Appearance;
using cutefish::i18n::tr;
namespace settings {
@ -114,22 +116,22 @@ class AppearancePage : public Page
{
public:
explicit AppearancePage(SettingsWindow& window)
: Page(window, "Appearance")
: Page(window, tr("Appearance"))
{
Section* modes = content()->addChild<Section>("Appearance");
Section* modes = content()->addChild<Section>(tr("Appearance"));
Card* modeCard = modes->addChild<Card>();
RowLayout* tiles = modeCard->addChild<RowLayout>();
tiles->setSpacing(metrics::tileSpacing);
// The 100x68 artwork at its own size inside the 4px inset.
const SizeF preview{108, 76};
m_light = tiles->addChild<ImageSelectionItem>(dataPath("images/light_mode.svg"), "Light", preview);
m_light = tiles->addChild<ImageSelectionItem>(dataPath("images/light_mode.svg"), tr("Light"), preview);
m_light->onClicked = [this] { chooseDarkMode(false); };
m_dark = tiles->addChild<ImageSelectionItem>(dataPath("images/dark_mode.svg"), "Dark", preview);
m_dark = tiles->addChild<ImageSelectionItem>(dataPath("images/dark_mode.svg"), tr("Dark"), preview);
m_dark->onClicked = [this] { chooseDarkMode(true); };
m_auto = tiles->addChild<ImageSelectionItem>(dataPath("images/auto_mode.svg"), "Auto", preview);
m_auto = tiles->addChild<ImageSelectionItem>(dataPath("images/auto_mode.svg"), tr("Auto"), preview);
m_auto->onClicked = [this] { m_appearance.setDarkModeSchedule(Appearance::SunsetToSunrise); };
Section* accents = content()->addChild<Section>("Accent color");
Section* accents = content()->addChild<Section>(tr("Accent color"));
Card* accentCard = accents->addChild<Card>();
Row* swatches = accentCard->addChild<Row>();
// The ring sits smallSpacing inside its cell; it, like a preview's
@ -145,9 +147,9 @@ public:
Card* links = content()->addChild<Card>(Card::Style::List);
const std::array<std::tuple<const char*, const char*, Color, const char*>, 3> pages = {
std::tuple{"Fonts", "fonts.svg", Color::rgb(0xFE8433), "fonts"},
{"Icon Theme", "icontheme.svg", Color::rgb(0x2EC347), "icontheme"},
{"Cursor Style", "cursor.svg", Color::rgb(0x3385FF), "cursortheme"}};
std::tuple{tr("Fonts"), "fonts.svg", Color::rgb(0xFE8433), "fonts"},
{tr("Icon Theme"), "icontheme.svg", Color::rgb(0x2EC347), "icontheme"},
{tr("Cursor Style"), "cursor.svg", Color::rgb(0x3385FF), "cursortheme"}};
for (const auto& [title, icon, tint, id] : pages) {
NavigationItem* item = links->addChild<NavigationItem>(title);
item->setLeading<RowIcon>(icon, tint);

@ -9,6 +9,7 @@
#include <ctime>
#include "base/file.h"
#include "base/i18n.h"
#include "base/paths.h"
#include "base/settings.h"
#include "cutefish/upower/battery_status.h"
@ -18,6 +19,8 @@
using namespace cutefish;
using namespace cutefish::base;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -111,9 +114,9 @@ std::string Battery::lastChargedTime() const
const int64_t days = timeformat::daysBetween(secs, now());
if (days == 0)
return "Today at " + timeformat::shortTime(secs);
return format(tr("Today at {time}"), {{"time", timeformat::shortTime(secs)}});
if (days == 1)
return "Yesterday at " + timeformat::shortTime(secs);
return format(tr("Yesterday at {time}"), {{"time", timeformat::shortTime(secs)}});
return timeformat::shortDateTime(secs);
}

@ -8,6 +8,7 @@
#include <vector>
#include "base/event_loop.h"
#include "base/i18n.h"
#include "base/timer.h"
#include "pages/battery/battery.h"
#include "pages/battery/batteryhistory.h"
@ -29,6 +30,8 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -45,9 +48,9 @@ public:
{
setSpacing(metrics::m);
setLayoutHints({.fillWidth = true});
m_title = addChild<SectionLabel>("History");
m_title = addChild<SectionLabel>(tr("History"));
m_title->setName("battery:historytitle");
m_picker = addChild<SegmentedControl>(std::vector<std::string>{"24 hours", "7 days"});
m_picker = addChild<SegmentedControl>(std::vector<std::string>{tr("24 hours"), tr("7 days")});
// Compact, to sit in the title row.
m_picker->setContentHeight(28);
}
@ -75,7 +78,7 @@ class BatteryPage : public Page
{
public:
explicit BatteryPage(SettingsWindow& window)
: Page(window, "Battery")
: Page(window, tr("Battery"))
{
// History.
m_historySection = content()->addChild<ColumnLayout>();
@ -101,16 +104,16 @@ public:
m_graph->setDuration(m_plottedDuration);
// Health.
m_health = content()->addChild<Section>("Health");
m_health = content()->addChild<Section>(tr("Health"));
m_health->setName("battery:health");
Card* health = m_health->addChild<Card>(Card::Style::List);
m_condition = health->addChild<InfoRow>("Condition");
m_capacity = health->addChild<InfoRow>("Maximum capacity");
m_condition = health->addChild<InfoRow>(tr("Condition"));
m_capacity = health->addChild<InfoRow>(tr("Maximum capacity"));
m_lastCharged = health->addChild<InfoRow>();
m_lastCharged->setName("battery:lastcharged");
// Charging.
m_charging = content()->addChild<Section>("Charging");
m_charging = content()->addChild<Section>(tr("Charging"));
m_charging->setName("battery:charging");
Card* charging = m_charging->addChild<Card>(Card::Style::List);
m_chargeLimit = charging->addChild<SwitchRow>();
@ -120,7 +123,7 @@ public:
m_chargeLimitNote->setName("battery:chargelimitnote");
Card* percentage = content()->addChild<Card>(Card::Style::List);
SwitchRow* showPercent = percentage->addChild<SwitchRow>("Show percentage in status bar");
SwitchRow* showPercent = percentage->addChild<SwitchRow>(tr("Show percentage in status bar"));
showPercent->setChecked(m_battery.showPercent());
showPercent->onToggled = [this](bool checked) { m_battery.setPercentEnabled(checked); };
@ -183,10 +186,11 @@ private:
{
const int capacity = m_battery.capacity();
m_health->setVisible(capacity != 0);
m_condition->setValue(capacity >= 90 ? "Excellent" : capacity >= 80 ? "Normal" : "Poor");
m_capacity->setValue(std::to_string(capacity) + "%");
m_condition->setValue(capacity >= 90 ? tr("Excellent") : capacity >= 80 ? tr("Normal") : tr("Poor"));
// TRANSLATORS: a percentage, such as "92%".
m_capacity->setValue(format(tr("{percent}%"), {{"percent", std::to_string(capacity)}}));
const int lastCharged = m_battery.lastChargedPercent();
m_lastCharged->setText("Last charged to " + std::to_string(lastCharged) + "%");
m_lastCharged->setText(format(tr("Last charged to {percent}%"), {{"percent", std::to_string(lastCharged)}}));
m_lastCharged->setValue(m_battery.lastChargedTime());
m_lastCharged->setVisible(lastCharged != 0);
}
@ -194,7 +198,8 @@ private:
void updateChargeLimit()
{
m_charging->setVisible(m_battery.chargeLimitSupported());
m_chargeLimit->setText("Limit charging to " + std::to_string(m_battery.chargeLimitEnd()) + "%");
m_chargeLimit->setText(
format(tr("Limit charging to {percent}%"), {{"percent", std::to_string(m_battery.chargeLimitEnd())}}));
m_chargeLimit->setChecked(m_battery.chargeLimitEnabled());
updateChargeLimitNote();
}
@ -203,11 +208,12 @@ private:
{
const bool held = m_battery.chargeLimitHeld();
m_chargeLimitNote->setText(
held ? "While plugged in, charging stops at " + std::to_string(m_battery.chargeLimitEnd())
+ "% and resumes below " + std::to_string(m_battery.chargeLimitStart())
+ "%, so the battery wears more slowly."
: "This computer's battery controller did not keep the limit, so the battery still "
"charges fully.");
held ? format(tr("While plugged in, charging stops at {end}% and resumes below {start}%, so the "
"battery wears more slowly."),
{{"end", std::to_string(m_battery.chargeLimitEnd())},
{"start", std::to_string(m_battery.chargeLimitStart())}})
: tr("This computer's battery controller did not keep the limit, so the battery still "
"charges fully."));
m_chargeLimitNote->setColor(held ? std::nullopt : std::optional<Color>(accent::orange));
}

@ -9,6 +9,7 @@
#include <cmath>
#include <cstdio>
#include "base/i18n.h"
#include "base/locale.h"
#include "pages/battery/timeformat.h"
#include "ui/animation.h"
@ -23,6 +24,8 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -45,15 +48,15 @@ static std::string stateText(uint32_t state)
{
switch (state) {
case 1:
return "Charging";
return tr("Charging");
case 4:
return "Fully charged";
return tr("Fully charged");
case 5:
return "Not charging";
return tr("Not charging");
case 2:
case 3:
case 6:
return "On battery";
return tr("On battery");
}
return {};
}
@ -86,7 +89,7 @@ public:
explicit Legend(HistoryGraph& graph)
: m_graph(graph)
{
for (const char* text : {"On battery", "Plugged in"})
for (const char* text : {tr("On battery"), tr("Plugged in")})
m_labels.push_back(addChild<Label>(text));
}
@ -579,9 +582,14 @@ void HistoryGraph::layout()
const int64_t secs = int64_t(std::floor(start + m_probe->x / m_plot.width * m_duration));
std::string time = timeformat::shortTime(secs);
if (m_duration > 86400)
time = timeformat::shortDayName(secs) + " " + time;
// TRANSLATORS: a weekday and a time in the graph's readout, such as "Mon 9:41 AM".
time = format(tr("{day} {time}"), {{"day", timeformat::shortDayName(secs)}, {"time", time}});
const std::string state = stateText(m_probe->state);
m_readout->setTexts(fixed(value, m_decimals) + m_units, state.empty() ? time : state + " · " + time);
std::string detail = time;
if (!state.empty())
// TRANSLATORS: the battery state and the time in the graph's readout, such as "Charging · 9:41 AM".
detail = format(tr("{state} · {time}"), {{"state", state}, {"time", time}});
m_readout->setTexts(fixed(value, m_decimals) + m_units, detail);
const double readoutWidth = m_readout->contentWidth() + metrics::s * 2;
const double x = std::max(0.0, std::min(width - readoutWidth, m_plot.x + m_probe->x - readoutWidth / 2));
m_readout->setGeometry({x, 0, readoutWidth, m_headerHeight});

@ -6,6 +6,7 @@
#include <map>
#include <string>
#include "base/i18n.h"
#include "ui/dialog_window.h"
#include "ui/cell_layout.h"
#include "ui/switch.h"
@ -16,6 +17,8 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
using cutefish::i18n::trNoop;
namespace settings {
@ -26,27 +29,27 @@ static constexpr size_t kNameLength = 248;
// The kind of device behind each icon name BlueZ reports.
static std::string deviceType(const std::string& icon)
{
static const std::map<std::string, std::string> types = {
{"audio-card", "Audio device"},
{"audio-headphones", "Headphones"},
{"audio-headset", "Headset"},
{"camera-photo", "Camera"},
{"camera-video", "Video camera"},
{"computer", "Computer"},
{"input-gaming", "Game controller"},
{"input-keyboard", "Keyboard"},
{"input-mouse", "Mouse"},
{"input-tablet", "Drawing tablet"},
{"modem", "Modem"},
{"multimedia-player", "Media player"},
{"network-wireless", "Network device"},
{"phone", "Phone"},
{"printer", "Printer"},
{"scanner", "Scanner"},
{"video-display", "Display"},
static const std::map<std::string, const char*> types = {
{"audio-card", trNoop("Audio device")},
{"audio-headphones", trNoop("Headphones")},
{"audio-headset", trNoop("Headset")},
{"camera-photo", trNoop("Camera")},
{"camera-video", trNoop("Video camera")},
{"computer", trNoop("Computer")},
{"input-gaming", trNoop("Game controller")},
{"input-keyboard", trNoop("Keyboard")},
{"input-mouse", trNoop("Mouse")},
{"input-tablet", trNoop("Drawing tablet")},
{"modem", trNoop("Modem")},
{"multimedia-player", trNoop("Media player")},
{"network-wireless", trNoop("Network device")},
{"phone", trNoop("Phone")},
{"printer", trNoop("Printer")},
{"scanner", trNoop("Scanner")},
{"video-display", trNoop("Display")},
};
const auto found = types.find(icon);
return found == types.end() ? std::string() : found->second;
return found == types.end() ? std::string() : tr(found->second);
}
// The dialog's content: the page's padding on top, as the window keeps it below.
@ -73,7 +76,7 @@ AdapterDetailsDialog::AdapterDetailsDialog(Window* parent, std::shared_ptr<bluez
ColumnLayout* content = makeContent(*m_dialog);
Card* identity = content->addChild<Card>(Card::Style::List);
ListRow* nameRow = identity->addChild<ListRow>("Name");
ListRow* nameRow = identity->addChild<ListRow>(tr("Name"));
nameRow->setInteractive(false);
nameRow->setMinimumRowHeight(metrics::controlRowHeight);
m_name = nameRow->addContent<TextField>();
@ -91,17 +94,17 @@ AdapterDetailsDialog::AdapterDetailsDialog(Window* parent, std::shared_ptr<bluez
}
};
m_name->onAccepted = [this] { rename(); };
m_address = identity->addChild<InfoRow>("Address");
m_address = identity->addChild<InfoRow>(tr("Address"));
Card* switches = content->addChild<Card>(Card::Style::List);
m_powered = switches->addChild<SwitchRow>("Powered");
m_powered = switches->addChild<SwitchRow>(tr("Powered"));
m_powered->onToggled = [this](bool checked) { m_adapter->setPowered(checked); };
m_discoverable = switches->addChild<SwitchRow>("Visible to other devices");
m_discoverable = switches->addChild<SwitchRow>(tr("Visible to other devices"));
m_discoverable->onToggled = [this](bool checked) { m_adapter->setDiscoverable(checked); };
m_pairable = switches->addChild<SwitchRow>("Allow incoming pairing");
m_pairable = switches->addChild<SwitchRow>(tr("Allow incoming pairing"));
m_pairable->onToggled = [this](bool checked) { m_adapter->setPairable(checked); };
DialogButton* close = makeButtons(content)->addChild<DialogButton>("Close");
DialogButton* close = makeButtons(content)->addChild<DialogButton>(tr("Close"));
close->onClicked = [this] {
rename();
m_dialog->close();
@ -144,21 +147,21 @@ DeviceDetailsDialog::DeviceDetailsDialog(Window* parent, std::shared_ptr<bluez::
ColumnLayout* content = makeContent(*m_dialog);
Card* details = content->addChild<Card>(Card::Style::List);
m_type = details->addChild<InfoRow>("Device type");
m_model = details->addChild<InfoRow>("Model number");
m_version = details->addChild<InfoRow>("Version");
m_address = details->addChild<InfoRow>("Address");
m_type = details->addChild<InfoRow>(tr("Device type"));
m_model = details->addChild<InfoRow>(tr("Model number"));
m_version = details->addChild<InfoRow>(tr("Version"));
m_address = details->addChild<InfoRow>(tr("Address"));
Card* switches = content->addChild<Card>(Card::Style::List);
m_trusted = switches->addChild<SwitchRow>("Trusted");
m_trusted = switches->addChild<SwitchRow>(tr("Trusted"));
m_trusted->onToggled = [this](bool checked) { m_device->setTrusted(checked); };
m_blocked = switches->addChild<SwitchRow>("Blocked");
m_blocked = switches->addChild<SwitchRow>(tr("Blocked"));
m_blocked->onToggled = [this](bool checked) { m_device->setBlocked(checked); };
RowLayout* buttons = makeButtons(content);
DialogButton* close = buttons->addChild<DialogButton>("Close");
DialogButton* close = buttons->addChild<DialogButton>(tr("Close"));
close->onClicked = [this] { m_dialog->close(); };
m_forget = buttons->addChild<DialogButton>("Forget this device", true);
m_forget = buttons->addChild<DialogButton>(tr("Forget this device"), true);
m_forget->setDestructive(true);
m_forget->onClicked = [this] {
m_device->forget();

@ -6,6 +6,7 @@
#include <string>
#include <vector>
#include "base/i18n.h"
#include "base/signal.h"
#include "cutefish/bluez/devices_model.h"
#include "cutefish/bluez/manager.h"
@ -29,6 +30,8 @@
using namespace cutefish;
using namespace cutefish::bluez;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -105,12 +108,14 @@ public:
setName(m_device->path());
m_icon->setIconName(m_device->icon().empty() ? "bluetooth" : m_device->icon());
m_name->setText(m_device->name());
std::string status = m_device->blocked() ? "Blocked"
: linked() ? "Connected"
: m_device->paired() ? "Not connected"
std::string status = m_device->blocked() ? tr("Blocked")
: linked() ? tr("Connected")
: m_device->paired() ? tr("Not connected")
: "";
if (linked() && m_device->batteryPercentage() >= 0)
status += " · " + std::to_string(m_device->batteryPercentage()) + "%";
// TRANSLATORS: a device's connection and its battery level, such as "Connected · 80%".
status = format(tr("{status} · {percent}%"),
{{"status", status}, {"percent", std::to_string(m_device->batteryPercentage())}});
m_status->setText(status);
m_status->setVisible(!status.empty());
setMinimumRowHeight(status.empty() ? metrics::rowHeight : metrics::twoLineRowHeight);
@ -119,7 +124,7 @@ public:
// The busy indicator stands in for the action, except that pairing can be cancelled.
m_action->setVisible(!m_device->blocked() && (pairing() || (linked() && !m_device->busy()))
&& (isHovered() || m_revealed));
m_action->setText(pairing() ? "Cancel pairing" : "Disconnect");
m_action->setText(pairing() ? tr("Cancel pairing") : tr("Disconnect"));
m_info->setVisible(m_device->paired());
}
@ -251,7 +256,7 @@ public:
void update()
{
setText(m_adapter->name());
m_state->setText(m_adapter->discoverable() ? "Discoverable" : "Not discoverable");
m_state->setText(m_adapter->discoverable() ? tr("Discoverable") : tr("Not discoverable"));
}
protected:
@ -271,14 +276,14 @@ class BluetoothPage : public Page
{
public:
explicit BluetoothPage(SettingsWindow& window)
: Page(window, "Bluetooth")
: Page(window, tr("Bluetooth"))
, m_manager(Manager::shared())
{
ColumnLayout* top = content()->addChild<ColumnLayout>();
top->setSpacing(metrics::sectionLabelSpacing);
top->setLayoutHints({.fillWidth = true});
m_firstCard = top->addChild<Card>(Card::Style::List);
m_switch = m_firstCard->addChild<SwitchRow>("Bluetooth");
m_switch = m_firstCard->addChild<SwitchRow>(tr("Bluetooth"));
m_switch->onToggled = [this](bool checked) { m_manager->setEnabled(checked); };
m_message = top->addChild<SectionLabel>(std::string(), true);
m_message->setName("bluetooth:message");
@ -287,11 +292,11 @@ public:
m_devices->setSpacing(metrics::sectionSpacing);
m_devices->setLayoutHints({.fillWidth = true});
const auto details = [this](std::shared_ptr<BluetoothObject> device) { showDeviceDetails(std::move(device)); };
m_paired = m_devices->addChild<DeviceSection>("My devices", m_manager, DevicesModel::PairedDevices, false,
m_paired = m_devices->addChild<DeviceSection>(tr("My devices"), m_manager, DevicesModel::PairedDevices, false,
details);
m_paired->setName("bluetooth:paired");
m_other = m_devices->addChild<DeviceSection>("Other devices", m_manager, DevicesModel::UnpairedDevices, true,
details);
m_other = m_devices->addChild<DeviceSection>(tr("Other devices"), m_manager, DevicesModel::UnpairedDevices,
true, details);
m_other->setName("bluetooth:other");
m_subscriptions.push_back(m_manager->changed.connect([this] { update(); }));
@ -342,16 +347,16 @@ private:
row->setVisible(enabled);
}
const std::string message = !m_manager->errorText().empty() ? m_manager->errorText()
: !m_manager->operational() ? "Bluetooth service is unavailable"
: m_manager->hardwareBlocked() ? "Bluetooth is blocked by a hardware switch"
: m_manager->adapters().empty() ? "No Bluetooth adapters found"
: !m_manager->operational() ? tr("Bluetooth service is unavailable")
: m_manager->hardwareBlocked() ? tr("Bluetooth is blocked by a hardware switch")
: m_manager->adapters().empty() ? tr("No Bluetooth adapters found")
: "";
m_message->setText(message);
m_message->setVisible(!message.empty());
m_devices->setVisible(enabled);
const bool scanning = m_manager->scanning();
m_other->setBusy(scanning);
m_other->setPlaceholder(scanning ? "" : "No devices found. Make sure your device is discoverable.");
m_other->setPlaceholder(scanning ? "" : tr("No devices found. Make sure your device is discoverable."));
}
void showAdapterDetails(std::shared_ptr<BluetoothObject> adapter)

@ -4,6 +4,7 @@
#include <algorithm>
#include <cmath>
#include "base/i18n.h"
#include "cutefish/appearance/appearance.h"
#include "pages/cursortheme/cursorthemes.h"
#include "ui/image_view.h"
@ -22,6 +23,7 @@
using namespace cutefish::ui;
using cutefish::appearance::Appearance;
using cutefish::i18n::tr;
namespace settings {
@ -46,10 +48,10 @@ class CursorThemePage : public Page
{
public:
explicit CursorThemePage(SettingsWindow& window)
: Page(window, "Cursor Style")
: Page(window, tr("Cursor Style"))
, m_themes(CursorTheme::installed())
{
Section* themes = content()->addChild<Section>("Cursor theme");
Section* themes = content()->addChild<Section>(tr("Cursor theme"));
Card* card = themes->addChild<Card>();
TileGrid* grid = card->addChild<TileGrid>(SizeF{104 + metrics::tileSpacing, 104}, metrics::m);
grid->setVisible(!m_themes.themes.empty());
@ -64,7 +66,7 @@ public:
m_items.push_back(item);
}
Section* size = content()->addChild<Section>("Cursor size");
Section* size = content()->addChild<Section>(tr("Cursor size"));
Card* sizeCard = size->addChild<Card>();
RowLayout* row = sizeCard->addChild<RowLayout>();
row->setSpacing(metrics::rowSpacing);

@ -2,6 +2,7 @@
// SPDX-License-Identifier: BSD-3-Clause
#include "base/event_loop.h"
#include "base/i18n.h"
#include "cutefish/timedate/time_date.h"
#include "pages/datetime/time.h"
#include "pages/datetime/timezonedialog.h"
@ -16,6 +17,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -25,7 +27,7 @@ class DateTimePage : public Page
{
public:
explicit DateTimePage(SettingsWindow& window)
: Page(window, "Date & Time")
: Page(window, tr("Date & Time"))
, m_time(m_timeDate)
, m_map(m_timeDate)
{
@ -34,13 +36,13 @@ public:
section->setLayoutHints({.fillWidth = true});
Card* card = section->addChild<Card>(Card::Style::List);
m_autoSync = card->addChild<SwitchRow>("Auto sync");
m_autoSync = card->addChild<SwitchRow>(tr("Auto sync"));
// Greyed out, with the note below, when no NTP service is installed.
m_autoSync->setEnabled(m_time.canNtp());
m_autoSync->onToggled = [this](bool checked) { m_time.setUseNtp(checked); };
m_twentyFour = card->addChild<SwitchRow>("24-hour time");
m_twentyFour = card->addChild<SwitchRow>(tr("24-hour time"));
m_twentyFour->onToggled = [this](bool checked) { m_time.setTwentyFour(checked); };
m_timeZone = card->addChild<NavigationItem>("Time zone");
m_timeZone = card->addChild<NavigationItem>(tr("Time zone"));
m_timeZone->onClicked = [this] {
if (m_dialog)
return;
@ -53,7 +55,7 @@ public:
};
Footnote* note = section->addChild<Footnote>(
"Unable to use auto sync. Please ensure your NTP service is installed.");
tr("Unable to use auto sync. Please ensure your NTP service is installed."));
note->setVisible(!m_time.canNtp());
m_subscriptions.push_back(m_time.useNtpChanged.connect([this] { update(); }));

@ -8,6 +8,7 @@
#include <cmath>
#include "base/event_loop.h"
#include "base/i18n.h"
#include "ui/animation.h"
#include "ui/color_provider.h"
#include "ui/dialog_window.h"
@ -31,6 +32,8 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -128,7 +131,7 @@ public:
SearchBox()
{
field = addChild<SearchBoxField>();
field->setPlaceholder("Search city or country");
field->setPlaceholder(tr("Search city or country"));
field->setFrameVisible(false);
field->setTextMargins(metrics::m + 14 + metrics::s, metrics::m);
m_glyph = addChild<Icon>("search", 14);
@ -262,7 +265,7 @@ public:
m_scroll = addChild<ScrollView>();
m_scroll->setElastic(false);
m_rows = m_scroll->setContent<Column>();
m_empty = addChild<StyledLabel>("No matching time zones");
m_empty = addChild<StyledLabel>(tr("No matching time zones"));
m_empty->style = [](StyledLabel& label) { label.setColor(Theme::current().disabledText); };
}
@ -757,7 +760,7 @@ class Band : public View
public:
Band()
{
title = addChild<Label>("Time Zone");
title = addChild<Label>(tr("Time Zone"));
title->setFontSize(14);
title->setWeight(600);
search = addChild<SearchBox>();
@ -824,9 +827,10 @@ public:
m_detail->style = [](StyledLabel& label) { label.setColor(Theme::current().disabledText); };
RowLayout* buttons = bar->addChild<RowLayout>();
buttons->setSpacing(metrics::dialogButtonSpacing);
m_cancel = buttons->addChild<DialogButton>("Cancel");
m_cancel = buttons->addChild<DialogButton>(tr("Cancel"));
m_cancel->onClicked = [this] { m_dialog.close(); };
m_set = buttons->addChild<DialogButton>("Set", true);
// TRANSLATORS: the button that makes the chosen time zone the system's.
m_set = buttons->addChild<DialogButton>(tr("Set"), true);
m_set->onClicked = [this] { m_dialog.apply(); };
results = addChild<ResultsPopup>();
@ -843,9 +847,11 @@ public:
const TimeZoneMap::Info& info = m_dialog.selectedInfo();
m_time->setText(info.time);
const bool named = !info.name.empty();
m_place->setText(!named ? "Click the map or search to choose a time zone"
: !info.region.empty() && info.region != info.name ? info.name + ", " + info.region
: info.name);
m_place->setText(!named ? tr("Click the map or search to choose a time zone")
: !info.region.empty() && info.region != info.name
// TRANSLATORS: a time zone's city and its country or region, such as "Paris, France".
? format(tr("{city}, {region}"), {{"city", info.name}, {"region", info.region}})
: info.name);
m_place->setWeight(named ? 600 : 400);
m_place->style = [named](StyledLabel& label) {
const Theme& theme = Theme::current();
@ -854,9 +860,10 @@ public:
if (!named)
m_detail->setText({});
else if (!m_dialog.isChanged())
m_detail->setText("Current time zone");
m_detail->setText(tr("Current time zone"));
else
m_detail->setText(joined({info.day, info.difference, "Currently " + m_dialog.map().currentTimeZone()},
m_detail->setText(joined({info.day, info.difference,
format(tr("Currently {zone}"), {{"zone", m_dialog.map().currentTimeZone()}})},
" · "));
m_set->setEnabled(m_dialog.isChanged());
map()->update();
@ -916,7 +923,7 @@ TimeZoneDialog::TimeZoneDialog(SettingsWindow& parent, TimeZoneMap& map)
{
auto root = std::make_unique<Root>(*this);
m_root = root.get();
m_window->setTitle("Time Zone");
m_window->setTitle(tr("Time Zone"));
m_window->setBackgroundColor(withAlpha(ColorId::SecondBackground, 0.5));
m_window->setContentView(std::move(root));
m_window->setTransientParent(&parent.window(), false);

@ -15,11 +15,15 @@
#include <unicode/unistr.h>
#include "base/file.h"
#include "base/i18n.h"
#include "base/locale.h"
#include "cutefish/timedate/time_date.h"
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
using cutefish::i18n::trn;
namespace settings {
@ -283,24 +287,25 @@ TimeZoneMap::Info TimeZoneMap::info(const std::string& id) const
const int hours = std::abs(gap) / 3600;
const int minutes = std::abs(gap) % 3600 / 60;
if (gap == 0)
difference = "Same time as now";
else if (minutes == 0 && hours == 1)
difference = gap > 0 ? "1 hour ahead" : "1 hour behind";
difference = tr("Same time as now");
else if (minutes == 0)
difference = std::to_string(hours) + (gap > 0 ? " hours ahead" : " hours behind");
difference = gap > 0 ? trn("{n} hour ahead", "{n} hours ahead", hours)
: trn("{n} hour behind", "{n} hours behind", hours);
else if (hours == 0)
difference = std::to_string(minutes) + (gap > 0 ? " min ahead" : " min behind");
difference = gap > 0 ? trn("{n} min ahead", "{n} min ahead", minutes)
: trn("{n} min behind", "{n} min behind", minutes);
else
difference = std::to_string(hours) + " h " + std::to_string(minutes) + (gap > 0 ? " min ahead" : " min behind");
difference = format(gap > 0 ? tr("{hours} h {minutes} min ahead") : tr("{hours} h {minutes} min behind"),
{{"hours", std::to_string(hours)}, {"minutes", std::to_string(minutes)}});
const auto day = [](int64_t seconds) { return int64_t(std::floor(double(seconds) / 86400.0)); };
const int64_t utc = time.time_since_epoch().count();
std::string dayText;
const int64_t days = day(utc + offset) - day(utc + here);
if (days > 0)
dayText = "Tomorrow";
dayText = tr("Tomorrow");
else if (days < 0)
dayText = "Yesterday";
dayText = tr("Yesterday");
return {found->timeZone, localeTimeZoneName(found->timeZone), territoryName(found->country), offset,
shortTime(found->timeZone, utc), dayText, difference};

@ -3,6 +3,7 @@
#include <array>
#include "base/i18n.h"
#include "pages/defaultapps/defaultapplications.h"
#include "widgets/card.h"
#include "widgets/listrow.h"
@ -11,6 +12,7 @@
#include "window/pagelist.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -20,11 +22,11 @@ class DefaultAppsPage : public Page
{
public:
explicit DefaultAppsPage(SettingsWindow& window)
: Page(window, "Default Applications")
: Page(window, tr("Default Applications"))
{
Card* card = content()->addChild<Card>(Card::Style::List);
const std::array<const char*, DefaultApplications::KindCount> titles = {"Web browser", "Email client",
"Terminal", "Video player"};
const std::array<const char*, DefaultApplications::KindCount> titles = {tr("Web browser"), tr("Email client"),
tr("Terminal"), tr("Video player")};
for (int kind = 0; kind < DefaultApplications::KindCount; ++kind) {
ListRow* row = card->addChild<ListRow>(titles[size_t(kind)]);
row->setInteractive(false);
@ -50,7 +52,7 @@ private:
box->setItems(std::move(items));
const int index = m_applications.defaultIndex(DefaultApplications::Kind(kind));
box->setCurrentIndex(index >= 0 ? index : (list.empty() ? -1 : 0));
box->setDisplayText(list.empty() ? std::optional<std::string>("None") : std::nullopt);
box->setDisplayText(list.empty() ? std::optional<std::string>(tr("None")) : std::nullopt);
row->setEnabled(!list.empty());
}
}

@ -9,6 +9,7 @@
#include <string>
#include <vector>
#include "base/i18n.h"
#include "base/locale.h"
#include "base/signal.h"
#include "base/timer.h"
@ -37,6 +38,10 @@ using namespace cutefish;
using namespace cutefish::display;
using namespace cutefish::ui;
using namespace std::chrono_literals;
using cutefish::i18n::format;
using cutefish::i18n::tr;
using cutefish::i18n::trc;
using cutefish::i18n::trn;
namespace settings {
@ -49,7 +54,14 @@ static std::string timeOfDay(int minutes)
static std::string percent(double scale)
{
return std::to_string(int(std::lround(scale * 100))) + "%";
// TRANSLATORS: a percentage, such as "92%".
return format(tr("{percent}%"), {{"percent", std::to_string(int(std::lround(scale * 100)))}});
}
// A menu item marked as the best choice for the screen.
static std::string recommended(const std::string& item)
{
return format(tr("{item} (Recommended)"), {{"item", item}});
}
namespace {
@ -155,7 +167,7 @@ class DisplayPage : public Page
{
public:
explicit DisplayPage(SettingsWindow& window)
: Page(window, "Display")
: Page(window, tr("Display"))
{
m_screens = content()->addChild<ColumnLayout>();
m_screens->setSpacing(metrics::sectionLabelSpacing);
@ -212,7 +224,7 @@ private:
void buildModes()
{
Card* card = m_screenPage->addChild<Card>(Card::Style::List);
m_brightnessRow = card->addChild<HalfRow>("Brightness");
m_brightnessRow = card->addChild<HalfRow>(tr("Brightness"));
m_brightnessRow->setName("display:brightness");
m_brightnessRow->setInteractive(false);
m_brightnessRow->setMinimumRowHeight(metrics::controlRowHeight);
@ -224,19 +236,19 @@ private:
slider->onMoved = [this](double) { m_brightnessTimer.start(); };
slider->setValueToolTip([](double value) { return std::to_string(std::lround(value)); });
m_resolutionRow = card->addChild<ListRow>("Resolution");
m_resolutionRow = card->addChild<ListRow>(tr("Resolution"));
m_resolutionRow->setInteractive(false);
m_resolution = m_resolutionRow->addContent<RowComboBox>();
m_resolution->setName("display:resolution");
m_resolution->onActivated = [this](int index) { outputs().setResolutionIndex(currentRow(), index); };
ListRow* refreshRow = card->addChild<ListRow>("Refresh rate");
ListRow* refreshRow = card->addChild<ListRow>(tr("Refresh rate"));
refreshRow->setInteractive(false);
m_refreshRate = refreshRow->addContent<RowComboBox>();
m_refreshRate->setName("display:refresh");
m_refreshRate->onActivated = [this](int index) { outputs().setRefreshRateIndex(currentRow(), index); };
ListRow* scaleRow = card->addChild<ListRow>("Scale");
ListRow* scaleRow = card->addChild<ListRow>(tr("Scale"));
scaleRow->setInteractive(false);
m_scale = scaleRow->addContent<RowComboBox>();
m_scale->setName("display:scale");
@ -246,11 +258,11 @@ private:
outputs().setScale(currentRow(), scales[index]);
};
ListRow* rotationRow = card->addChild<ListRow>("Rotation");
ListRow* rotationRow = card->addChild<ListRow>(tr("Rotation"));
rotationRow->setInteractive(false);
// Paired with the labels as the old rotation pictures were.
m_rotation = rotationRow->addContent<RowComboBox>(
std::vector<std::string>{"Normal", "90°", "180°", "270°"});
std::vector<std::string>{trc("rotation", "Normal"), "90°", "180°", "270°"});
m_rotation->setName("display:rotation");
m_rotation->onActivated = [this](int index) {
static const int rotations[] = {OutputModel::None, OutputModel::Left, OutputModel::Inverted,
@ -265,24 +277,24 @@ private:
{
m_arrangement = m_screenPage->addChild<Card>(Card::Style::List);
m_arrangement->setName("display:arrangement");
m_enabled = m_arrangement->addChild<SwitchRow>("Enabled");
m_enabled = m_arrangement->addChild<SwitchRow>(tr("Enabled"));
m_enabled->onToggled = [this](bool checked) {
if (!outputs().setEnabled(currentRow(), checked))
updateScreens();
};
// A screen stops being primary only when another one takes over, so
// the switch turns on and never off.
m_primary = m_arrangement->addChild<SwitchRow>("Primary");
m_primary = m_arrangement->addChild<SwitchRow>(tr("Primary"));
m_primary->onToggled = [this](bool) {
if (!outputs().setPrimary(currentRow()))
updateScreens();
};
m_modeRow = m_arrangement->addChild<ListRow>("Mode");
m_modeRow = m_arrangement->addChild<ListRow>(tr("Mode"));
m_modeRow->setInteractive(false);
m_mode = m_modeRow->addContent<RowComboBox>();
m_mode->onActivated = [this](int index) { outputs().setReplicationSourceIndex(currentRow(), index); };
HalfRow* positionRow = m_arrangement->addChild<HalfRow>("Position");
HalfRow* positionRow = m_arrangement->addChild<HalfRow>(tr("Position"));
positionRow->setInteractive(false);
positionRow->setMinimumRowHeight(metrics::controlRowHeight);
RowLayout* fields = positionRow->addContent<RowLayout>();
@ -305,11 +317,11 @@ private:
{
m_nightCard = content()->addChild<Card>(Card::Style::List);
m_nightCard->setName("display:nightlight");
m_nightSwitch = m_nightCard->addChild<SwitchRow>("Night Light");
m_nightSwitch = m_nightCard->addChild<SwitchRow>(tr("Night Light"));
m_nightSwitch->onToggled = [this](bool checked) { m_nightLight.setEnabled(checked); };
m_temperature = m_nightCard->addChild<SliderRow>("Color temperature");
m_temperature->setCaptions("Less warm", "More warm");
m_temperature = m_nightCard->addChild<SliderRow>(tr("Color temperature"));
m_temperature->setCaptions(tr("Less warm"), tr("More warm"));
m_temperature->setRange(0, 1);
m_temperature->onMoved = [this](double value) {
m_pendingTemperature = value;
@ -325,27 +337,27 @@ private:
m_nightLight.setPreview(pressed);
};
ListRow* scheduleRow = m_nightCard->addChild<ListRow>("Schedule");
ListRow* scheduleRow = m_nightCard->addChild<ListRow>(tr("Schedule"));
scheduleRow->setInteractive(false);
m_schedule = scheduleRow->addContent<RowComboBox>(
std::vector<std::string>{"Never", "Sunset to sunrise", "Custom"});
std::vector<std::string>{tr("Never"), tr("Sunset to sunrise"), tr("Custom")});
m_schedule->setName("display:schedule");
m_schedule->onActivated = [this](int index) { m_nightLight.setSchedule(index); };
m_sunsetRow = m_nightCard->addChild<ListRow>("Sunset");
m_sunsetRow = m_nightCard->addChild<ListRow>(tr("Sunset"));
m_sunsetRow->setName("display:sunset");
m_sunsetRow->setInteractive(false);
m_sunset = m_sunsetRow->addContent<SecondaryLabel>();
m_sunriseRow = m_nightCard->addChild<ListRow>("Sunrise");
m_sunriseRow = m_nightCard->addChild<ListRow>(tr("Sunrise"));
m_sunriseRow->setInteractive(false);
m_sunrise = m_sunriseRow->addContent<SecondaryLabel>();
m_fromRow = m_nightCard->addChild<ListRow>("From");
m_fromRow = m_nightCard->addChild<ListRow>(tr("From"));
m_fromRow->setName("display:from");
m_fromRow->setInteractive(false);
m_from = m_fromRow->addContent<TimeComboBox>();
m_from->onPicked = [this](int minutes) { m_nightLight.setCustomStart(minutes); };
m_toRow = m_nightCard->addChild<ListRow>("To");
m_toRow = m_nightCard->addChild<ListRow>(tr("To"));
m_toRow->setInteractive(false);
m_to = m_toRow->addContent<TimeComboBox>();
m_to->onPicked = [this](int minutes) { m_nightLight.setCustomEnd(minutes); };
@ -357,7 +369,7 @@ private:
const int count = model.count();
std::vector<std::string> names;
for (int row = 0; row < count; ++row)
names.push_back(model.name(row).empty() ? "Screen" : model.name(row));
names.push_back(model.name(row).empty() ? tr("Screen") : model.name(row));
if (names != m_tabNames) {
m_tabNames = names;
const int current = m_screenTabs->currentIndex();
@ -368,7 +380,7 @@ private:
m_screenName->setVisible(count == 1);
m_screenTabs->setVisible(count > 1);
const int row = currentRow();
m_screenName->setText(count > 0 && !model.name(row).empty() ? model.name(row) : "Screen");
m_screenName->setText(count > 0 && !model.name(row).empty() ? model.name(row) : tr("Screen"));
m_screenPage->setEnabled(m_screen.available() && !m_screen.busy() && !m_screen.pendingConfirmation());
if (count > 0) {
@ -376,7 +388,7 @@ private:
std::vector<std::string> resolutionItems;
for (int i = 0; i < int(resolutions.size()); ++i) {
// "Recommended" is only a hint while choosing.
resolutionItems.push_back(i == model.recommendedResolutionIndex(row) ? resolutions[i] + " (Recommended)"
resolutionItems.push_back(i == model.recommendedResolutionIndex(row) ? recommended(resolutions[i])
: resolutions[i]);
}
setItems(m_resolution, resolutionItems);
@ -393,8 +405,8 @@ private:
std::vector<std::string> scaleItems;
int nearest = 0;
for (int i = 0; i < int(scales.size()); ++i) {
const bool recommended = std::abs(scales[i] - model.recommendedScale(row)) < 0.001;
scaleItems.push_back(recommended ? percent(scales[i]) + " (Recommended)" : percent(scales[i]));
const bool recommendedScale = std::abs(scales[i] - model.recommendedScale(row)) < 0.001;
scaleItems.push_back(recommendedScale ? recommended(percent(scales[i])) : percent(scales[i]));
// The nearest step, for a scale set some other way.
if (std::abs(scales[i] - model.scale(row)) < std::abs(scales[nearest] - model.scale(row)))
nearest = i;
@ -427,7 +439,7 @@ private:
m_errorCard->setVisible(m_screen.loaded() && (!m_screen.available() || !m_screen.error().empty()));
m_error->setText(m_screen.available() ? m_screen.error()
: "Display configuration is unavailable in this session.");
: tr("Display configuration is unavailable in this session."));
updateConfirmation();
}
@ -457,16 +469,16 @@ private:
return;
}
const int seconds = m_screen.confirmationSeconds();
const std::string text = "The previous settings come back in " + std::to_string(seconds)
+ (seconds == 1 ? " second." : " seconds.");
const std::string text = trn("The previous settings come back in {n} second.",
"The previous settings come back in {n} seconds.", seconds);
if (m_confirmation) {
m_confirmation->setText(text);
return;
}
m_confirmation = std::make_unique<ConfirmDialog>(&settingsWindow().window(),
"Keep these display settings?", text);
m_confirmation->setCancelText("Revert");
m_confirmation->setConfirmText("Keep");
tr("Keep these display settings?"), text);
m_confirmation->setCancelText(tr("Revert"));
m_confirmation->setConfirmText(tr("Keep"));
m_confirmation->onAccepted = [this] { m_screen.keep(); };
m_confirmation->onRejected = [this] { m_screen.revert(); };
m_confirmation->show();

@ -4,6 +4,7 @@
#include <cmath>
#include <vector>
#include "base/i18n.h"
#include "pages/dock/docksettings.h"
#include "ui/layout.h"
#include "ui/cell_layout.h"
@ -20,6 +21,7 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -29,13 +31,13 @@ class DockPage : public Page
{
public:
explicit DockPage(SettingsWindow& window)
: Page(window, "Dock")
: Page(window, tr("Dock"))
{
Section* position = content()->addChild<Section>("Position on screen");
Section* position = content()->addChild<Section>(tr("Position on screen"));
Card* positions = position->addChild<Card>();
RowLayout* tiles = positions->addChild<RowLayout>();
tiles->setSpacing(metrics::tileSpacing);
const char* const names[] = {"Left", "Bottom", "Right"};
const char* const names[] = {tr("Left"), tr("Bottom"), tr("Right")};
const char* const pictures[] = {"images/dock_left.svg", "images/dock_bottom.svg", "images/dock_right.svg"};
for (int i = 0; i < 3; ++i) {
ImageSelectionItem* tile = tiles->addChild<ImageSelectionItem>(dataPath(pictures[i]), names[i],
@ -49,9 +51,9 @@ public:
updateDirection();
Card* sliders = content()->addChild<Card>(Card::Style::List);
SliderRow* size = sliders->addChild<SliderRow>("Size");
SliderRow* size = sliders->addChild<SliderRow>(tr("Size"));
size->setName("dock:size");
size->setCaptions("Small", "Huge");
size->setCaptions(tr("Small"), tr("Huge"));
// The value is the icon size itself; the dock derives its paddings
// from it (54 is the design baseline).
size->setRange(32, 128);
@ -63,9 +65,9 @@ public:
m_settings.setIconSize(int(std::round(value)));
size->setValue(m_settings.iconSize());
};
SliderRow* glass = sliders->addChild<SliderRow>("Glass effect");
SliderRow* glass = sliders->addChild<SliderRow>(tr("Glass effect"));
glass->setName("dock:glass");
glass->setCaptions("More clear", "More tinted");
glass->setCaptions(tr("More clear"), tr("More tinted"));
glass->setRange(0, 1);
glass->setValue(m_settings.glassTint());
glass->onMoved = [this, glass](double value) {
@ -73,19 +75,19 @@ public:
glass->setValue(m_settings.glassTint());
};
Section* mode = content()->addChild<Section>("Display mode");
Section* mode = content()->addChild<Section>(tr("Display mode"));
auto* visibility = mode->addChild<SegmentedControl>(
std::vector<std::string>{"Always show", "Always hide", "Smart hide"});
std::vector<std::string>{tr("Always show"), tr("Always hide"), tr("Smart hide")});
visibility->setLayoutHints({.fillWidth = true});
visibility->setCurrentIndex(m_settings.visibility());
visibility->onCurrentChanged = [this](int index) { m_settings.setVisibility(index); };
Card* corners = content()->addChild<Card>(Card::Style::List);
NavigationItem* hotCorners = corners->addChild<NavigationItem>("Hot Corners");
NavigationItem* hotCorners = corners->addChild<NavigationItem>(tr("Hot Corners"));
hotCorners->onClicked = [&window] { window.pushPage("hotcorners"); };
Card* minimized = content()->addChild<Card>(Card::Style::List);
SwitchRow* area = minimized->addChild<SwitchRow>("Minimized windows area");
SwitchRow* area = minimized->addChild<SwitchRow>(tr("Minimized windows area"));
area->setChecked(m_settings.minimizedArea());
area->onToggled = [this](bool checked) { m_settings.setMinimizedArea(checked); };
}

@ -5,6 +5,7 @@
#include <array>
#include <cstdio>
#include "base/i18n.h"
#include "base/locale.h"
#include "base/paths.h"
#include "base/settings.h"
@ -19,6 +20,8 @@
using namespace cutefish::ui;
using cutefish::appearance::Appearance;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -41,15 +44,16 @@ std::string languageTag()
std::string pointSizeText(double size)
{
char text[32];
snprintf(text, sizeof(text), "%gpt", size);
return text;
snprintf(text, sizeof(text), "%g", size);
// TRANSLATORS: a font size in points, such as "9pt".
return format(tr("{size}pt"), {{"size", text}});
}
class FontsPage : public Page
{
public:
explicit FontsPage(SettingsWindow& window)
: Page(window, "Fonts")
: Page(window, tr("Fonts"))
{
const cutefish::base::Settings services(cutefish::base::paths::cutefishConfig("services"));
m_antiAliasing = services.boolValue("Appearance/FontAntialias", true);
@ -58,21 +62,22 @@ public:
m_hinting = hint == kHintStyles.end() ? 0 : int(hint - kHintStyles.begin());
Card* card = content()->addChild<Card>(Card::Style::List);
m_general = addRow(card, "General font")->addContent<RowComboBox>();
m_general = addRow(card, tr("General font"))->addContent<RowComboBox>();
m_general->onActivated = [this](int) { m_appearance.setFontFamily(m_general->currentText()); };
m_fixed = addRow(card, "Fixed font")->addContent<RowComboBox>();
m_fixed = addRow(card, tr("Fixed font"))->addContent<RowComboBox>();
m_general->setFontPreview(true);
m_fixed->setFontPreview(true);
m_fixed->onActivated = [this](int) { m_appearance.setFixedFontFamily(m_fixed->currentText()); };
std::vector<std::string> sizes;
for (const int size : kSizes)
sizes.push_back(size == kDefaultSize ? pointSizeText(size) + " (Default)" : pointSizeText(size));
m_size = addRow(card, "Font size")->addContent<RowComboBox>(sizes);
sizes.push_back(size == kDefaultSize ? format(tr("{size} (Default)"), {{"size", pointSizeText(size)}})
: pointSizeText(size));
m_size = addRow(card, tr("Font size"))->addContent<RowComboBox>(sizes);
m_size->onActivated = [this](int index) { m_appearance.setFontPointSize(kSizes[size_t(index)]); };
RowComboBox* hinting = addRow(card, "Hinting")->addContent<RowComboBox>(
std::vector<std::string>{"None", "Slight", "Medium", "Full"});
RowComboBox* hinting = addRow(card, tr("Hinting"))->addContent<RowComboBox>(
std::vector<std::string>{tr("None"), tr("Slight"), tr("Medium"), tr("Full")});
hinting->setCurrentIndex(m_hinting);
hinting->onActivated = [this](int index) {
if (index == m_hinting)
@ -81,7 +86,7 @@ public:
save();
};
SwitchRow* antiAliasing = card->addChild<SwitchRow>("Anti-Aliasing");
SwitchRow* antiAliasing = card->addChild<SwitchRow>(tr("Anti-Aliasing"));
antiAliasing->setSeparatorVisible(false);
antiAliasing->setChecked(m_antiAliasing);
antiAliasing->onToggled = [this](bool checked) {

@ -6,7 +6,10 @@
#include <cstdint>
#include <cstdio>
#include "base/i18n.h"
using namespace cutefish::base;
using cutefish::i18n::trc;
namespace settings {
@ -112,13 +115,13 @@ std::string HotCorners::modifierText(int modifiers) const
text += part;
};
if (modifiers & kControlDown)
add(m_commandKeys ? "Control" : "Ctrl");
add(m_commandKeys ? trc("key", "Control") : trc("key", "Ctrl"));
if (modifiers & kAltDown)
add(m_commandKeys ? "Option" : "Alt");
add(m_commandKeys ? trc("key", "Option") : trc("key", "Alt"));
if (modifiers & kShiftDown)
add("Shift");
add(trc("key", "Shift"));
if (modifiers & kMetaDown)
add(m_commandKeys ? "Command" : "Meta");
add(m_commandKeys ? trc("key", "Command") : trc("key", "Meta"));
return text;
}

@ -4,6 +4,7 @@
#include <algorithm>
#include <array>
#include "base/i18n.h"
#include "pages/hotcorners/hotcorners.h"
#include "ui/label.h"
#include "ui/theme.h"
@ -15,6 +16,9 @@
#include "window/pagelist.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
using cutefish::i18n::trNoop;
namespace settings {
@ -23,29 +27,32 @@ namespace {
// The compositor's action names, "" for none, and what the menu calls them.
const std::array<const char*, 5> kActions = {"", "toggle-overview", "toggle-show-desktop", "toggle-app-list",
"lock-screen"};
const std::vector<std::string> kActionNames = {"None", "Show All Windows", "Show Desktop", "Launcher",
"Lock Screen"};
const std::array<const char*, 5> kActionNames = {trNoop("None"), trNoop("Show All Windows"), trNoop("Show Desktop"),
trNoop("Launcher"), trNoop("Lock Screen")};
class HotCornersPage : public Page
{
public:
explicit HotCornersPage(SettingsWindow& window)
: Page(window, "Hot Corners")
: Page(window, tr("Hot Corners"))
{
m_unavailable = content()->addChild<Card>();
m_unavailableLabel = m_unavailable->addChild<Label>(
"Hot corners are managed by the Cutefish compositor, which is not running.");
tr("Hot corners are managed by the Cutefish compositor, which is not running."));
m_unavailableLabel->setWrap(true);
m_unavailableLabel->setLayoutHints({.fillWidth = true});
m_list = content()->addChild<Card>(Card::Style::List);
const std::array<std::pair<const char*, const char*>, 4> corners = {
std::pair{"Top left", "top-left"}, {"Top right", "top-right"}, {"Bottom left", "bottom-left"},
{"Bottom right", "bottom-right"}};
std::pair{tr("Top left"), "top-left"}, {tr("Top right"), "top-right"},
{tr("Bottom left"), "bottom-left"}, {tr("Bottom right"), "bottom-right"}};
std::vector<std::string> actionNames;
for (const char* name : kActionNames)
actionNames.push_back(tr(name));
for (const auto& [text, corner] : corners) {
ListRow* row = m_list->addChild<ListRow>(text);
row->setInteractive(false);
RowComboBox* box = row->addContent<RowComboBox>(kActionNames);
RowComboBox* box = row->addContent<RowComboBox>(actionNames);
const std::string name = corner;
// Keys held while choosing are the ones the corner will ask for.
box->onActivated = [this, name](int index) {
@ -54,8 +61,8 @@ public:
m_boxes.push_back({name, box});
}
m_note = content()->addChild<Footnote>(
"Move the pointer into a corner of the screen to run its action. Hold modifier keys while "
"choosing an action to make the corner work only while they are held.");
tr("Move the pointer into a corner of the screen to run its action. Hold modifier keys while "
"choosing an action to make the corner work only while they are held."));
m_subscription = m_corners.changed.connect([this] { update(); });
update();
}
@ -81,7 +88,9 @@ private:
box->setCurrentIndex(index);
const int modifiers = m_corners.modifiers(corner);
if (modifiers != 0 && index > 0)
box->setDisplayText(m_corners.modifierText(modifiers) + " – " + kActionNames[size_t(index)]);
// TRANSLATORS: the keys a hot corner needs held and its action, such as "Ctrl + Alt – Lock Screen".
box->setDisplayText(format(tr("{keys} – {action}"), {{"keys", m_corners.modifierText(modifiers)},
{"action", tr(kActionNames[size_t(index)])}}));
else
box->setDisplayText(std::nullopt);
}

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "cutefish/appearance/appearance.h"
#include "pages/icontheme/iconthemes.h"
#include "ui/image_view.h"
@ -14,6 +15,7 @@
using namespace cutefish::ui;
using cutefish::appearance::Appearance;
using cutefish::i18n::tr;
namespace settings {
@ -52,10 +54,10 @@ class IconThemePage : public Page
{
public:
explicit IconThemePage(SettingsWindow& window)
: Page(window, "Icon Theme")
: Page(window, tr("Icon Theme"))
, m_themes(IconTheme::installed())
{
Section* section = content()->addChild<Section>("Icon theme");
Section* section = content()->addChild<Section>(tr("Icon theme"));
Card* card = section->addChild<Card>();
TileGrid* grid = card->addChild<TileGrid>(SizeF{104 + metrics::tileSpacing, 104}, metrics::m);
grid->setVisible(!m_themes.empty());

@ -5,6 +5,7 @@
#include <cstdlib>
#include <vector>
#include "base/i18n.h"
#include "pages/input/checkrow.h"
#include "pages/keyboard/inputmethod.h"
#include "pages/keyboard/keyboard.h"
@ -20,6 +21,8 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
using cutefish::i18n::trc;
namespace settings {
@ -65,41 +68,41 @@ class KeyboardPage : public Page
{
public:
explicit KeyboardPage(SettingsWindow& window)
: Page(window, "Keyboard")
: Page(window, tr("Keyboard"))
{
Section* inputMethods = content()->addChild<Section>("Input method");
Section* inputMethods = content()->addChild<Section>(tr("Input method"));
m_noInputMethods = inputMethods->addChild<NoteCard>(
"Input methods can only be selected in a Cutefish Wayland session.");
tr("Input methods can only be selected in a Cutefish Wayland session."));
m_inputMethodCard = inputMethods->addChild<Card>(Card::Style::List);
m_inputMethodCard->setName("keyboard:inputMethods");
m_notRunning = inputMethods->addChild<Footnote>("The selected input method is not running.");
m_notRunning = inputMethods->addChild<Footnote>(tr("The selected input method is not running."));
m_restartNote = inputMethods->addChild<Footnote>(
"Applications started before the input method may need to be restarted.");
tr("Applications started before the input method may need to be restarted."));
m_restartNote->setOpacity(0.8);
m_shortcutKey = content()->addChild<Section>("Shortcut key");
m_shortcutKey = content()->addChild<Section>(tr("Shortcut key"));
Card* conventions = m_shortcutKey->addChild<Card>(Card::Style::List);
m_pc = conventions->addChild<CheckRow>("Ctrl");
m_pc = conventions->addChild<CheckRow>(trc("key", "Ctrl"));
m_pc->onClicked = [this] { m_keyboard.setShortcutConvention("pc"); };
m_command = conventions->addChild<CheckRow>("Command");
m_command = conventions->addChild<CheckRow>(trc("key", "Command"));
m_command->onClicked = [this] { m_keyboard.setShortcutConvention("command"); };
m_conventionNote = m_shortcutKey->addChild<Footnote>();
Card* navigation = content()->addChild<Card>(Card::Style::List);
m_layout = navigation->addChild<NavigationItem>("Layout");
m_layout = navigation->addChild<NavigationItem>(tr("Layout"));
m_layout->onClicked = [&window] { window.pushPage("keyboardlayout"); };
NavigationItem* shortcuts = navigation->addChild<NavigationItem>("Keyboard Shortcuts");
NavigationItem* shortcuts = navigation->addChild<NavigationItem>(tr("Keyboard Shortcuts"));
shortcuts->onClicked = [&window] { window.pushPage("shortcuts"); };
m_repeatCard = content()->addChild<Card>(Card::Style::List);
m_repeatCard->setName("keyboard:repeat");
m_repeat = m_repeatCard->addChild<SwitchRow>("Key repeat");
m_repeat = m_repeatCard->addChild<SwitchRow>(tr("Key repeat"));
m_repeat->onToggled = [this](bool checked) { m_keyboard.setRepeatEnabled(checked); };
m_delay = m_repeatCard->addChild<SliderRow>("Delay until repeat");
m_delay = m_repeatCard->addChild<SliderRow>(tr("Delay until repeat"));
m_delay->setRange(0, double(kRepeatDelays.size() - 1));
m_delay->setStepSize(1);
m_delay->onMoved = [this](double value) { m_keyboard.setRepeatDelay(kRepeatDelays[size_t(std::round(value))]); };
m_rate = m_repeatCard->addChild<SliderRow>("Repeat rate");
m_rate = m_repeatCard->addChild<SliderRow>(tr("Repeat rate"));
m_rate->setRange(0, double(kRepeatIntervals.size() - 1));
m_rate->setStepSize(1);
m_rate->onMoved = [this](double value) {
@ -122,7 +125,7 @@ private:
m_notRunning->setVisible(available && !current.empty() && !m_inputMethod.isRunning());
m_restartNote->setVisible(available);
std::vector<InputMethod::Method> methods{{"", "None", ""}};
std::vector<InputMethod::Method> methods{{"", tr("None"), ""}};
methods.insert(methods.end(), m_inputMethod.methods().begin(), m_inputMethod.methods().end());
std::vector<std::string> ids;
for (const InputMethod::Method& method : methods)
@ -152,11 +155,11 @@ private:
m_command->setChecked(convention == "command");
m_conventionNote->setText(
convention == "command"
? "The key next to the space bar works as Command and the one beside it as Option, on any "
"keyboard: Command+C copies, Command+Tab switches windows, Command+Q quits. The shortcuts "
"can be changed in Keyboard Shortcuts."
: "Ctrl is the shortcut key and Alt switches windows. The shortcuts can be changed in "
"Keyboard Shortcuts.");
? tr("The key next to the space bar works as Command and the one beside it as Option, on "
"any keyboard: Command+C copies, Command+Tab switches windows, Command+Q quits. The "
"shortcuts can be changed in Keyboard Shortcuts.")
: tr("Ctrl is the shortcut key and Alt switches windows. The shortcuts can be changed in "
"Keyboard Shortcuts."));
m_layout->setVisible(available);
m_layout->setValue(m_keyboard.layoutName());
m_repeatCard->setVisible(available);

@ -9,6 +9,7 @@
#include <unicode/ucasemap.h>
#include "base/i18n.h"
#include "pages/input/checkrow.h"
#include "pages/keyboard/keyboard.h"
#include "ui/label.h"
@ -23,6 +24,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -93,7 +95,7 @@ public:
m_list = addChild<LayoutListView>();
m_list->setElastic(false);
m_list->setRowHeight(metrics::rowHeight);
m_empty = addChild<Label>("No layouts found");
m_empty = addChild<Label>(tr("No layouts found"));
m_empty->setName("keyboardlayout:empty");
m_empty->setVisible(false);
}
@ -121,7 +123,7 @@ class LayoutPage : public Page
{
public:
explicit LayoutPage(SettingsWindow& window)
: Page(window, "Layout")
: Page(window, tr("Layout"))
{
// The list scrolls, not the page.
setScrollDirections(false, false);
@ -129,7 +131,7 @@ public:
m_search = content()->addChild<SearchField>();
m_search->setName("keyboardlayout:search");
m_search->setPlaceholder("Search layouts");
m_search->setPlaceholder(tr("Search layouts"));
m_search->setLayoutHints({.fillWidth = true});
m_search->onTextChanged = [this] {
filter();

@ -6,6 +6,7 @@
#include <chrono>
#include <cmath>
#include "base/i18n.h"
#include "pages/language/language.h"
#include "ui/dialog_window.h"
#include "ui/icon.h"
@ -21,6 +22,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -119,7 +121,7 @@ protected:
} // namespace
AddLanguageDialog::AddLanguageDialog(SettingsWindow& parent, Language& language)
: FormDialog(parent, "Add Language", 460)
: FormDialog(parent, tr("Add Language"), 460)
, m_language(language)
{
setFixedHeight(560);
@ -127,7 +129,7 @@ AddLanguageDialog::AddLanguageDialog(SettingsWindow& parent, Language& language)
setBackgroundColor(ColorId::Background);
m_search = content()->addChild<SearchField>();
m_search->setPlaceholder("Search languages");
m_search->setPlaceholder(tr("Search languages"));
m_search->setLayoutHints({.fillWidth = true});
m_search->onTextChanged = [this] {
select({});
@ -156,9 +158,9 @@ AddLanguageDialog::AddLanguageDialog(SettingsWindow& parent, Language& language)
m_error->setLayoutHints({.fillWidth = true});
RowLayout* buttons = addButtonRow();
m_cancel = buttons->addChild<DialogButton>("Cancel");
m_cancel = buttons->addChild<DialogButton>(tr("Cancel"));
m_cancel->onClicked = [this] { requestClose(); };
m_add = buttons->addChild<DialogButton>("Add", true);
m_add = buttons->addChild<DialogButton>(tr("Add"), true);
m_add->onClicked = [this] {
m_adding = true;
updateStates();
@ -208,7 +210,7 @@ void AddLanguageDialog::updateRows()
row->onClicked = [this, code] { select(code); };
}
m_scroll->setScrollPosition(position);
m_empty->setText(m_language.isLoading() ? "Loading languages…" : "No languages found");
m_empty->setText(m_language.isLoading() ? tr("Loading languages…") : tr("No languages found"));
m_empty->parent()->setVisible(languages.empty());
updateStates();
}

@ -13,11 +13,13 @@
#include <unicode/unistr.h>
#include "base/file.h"
#include "base/i18n.h"
#include "base/locale.h"
#include "base/paths.h"
#include "base/settings.h"
using namespace cutefish::base;
using cutefish::i18n::tr;
namespace settings {
@ -279,7 +281,7 @@ void Language::saveLanguages(const std::vector<std::string>& codes)
m_saving = true;
savingChanged.emit();
if (!m_service.isValid()) {
m_errorMessage = "Could not save preferred languages. Please try again.";
m_errorMessage = tr("Could not save preferred languages. Please try again.");
errorMessageChanged.emit();
m_saving = false;
savingChanged.emit();
@ -287,7 +289,7 @@ void Language::saveLanguages(const std::vector<std::string>& codes)
}
m_saveCall = m_service.callAsync("setPreferredLanguages", [this, codes](const dbus::Reply& reply) {
if (reply.isError()) {
m_errorMessage = "Could not save preferred languages. Please try again.";
m_errorMessage = tr("Could not save preferred languages. Please try again.");
errorMessageChanged.emit();
} else {
setPreferredCodes(codes);

@ -2,6 +2,7 @@
// SPDX-License-Identifier: BSD-3-Clause
#include "base/event_loop.h"
#include "base/i18n.h"
#include "pages/language/addlanguagedialog.h"
#include "pages/language/language.h"
#include "pages/language/preferredlanguagelist.h"
@ -16,6 +17,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -25,16 +27,17 @@ class LanguagePage : public Page
{
public:
explicit LanguagePage(SettingsWindow& window)
: Page(window, "Language")
: Page(window, tr("Language"))
{
Section* section = content()->addChild<Section>("Preferred languages");
Section* section = content()->addChild<Section>(tr("Preferred languages"));
Card* card = section->addChild<Card>(Card::Style::Padded);
card->setPadding({metrics::cardHorizontalPadding, metrics::xs, metrics::cardHorizontalPadding, metrics::xs});
card->addChild<PreferredLanguageList>(m_language);
section->addChild<Footnote>(
"Drag a language to the top to make it the system language. Right-click a language to remove it.");
tr("Drag a language to the top to make it the system language. Right-click a language to remove "
"it."));
m_add = content()->addChild<StandardButton>("Add language…");
m_add = content()->addChild<StandardButton>(tr("Add language…"));
m_add->onClicked = [this] {
m_dialog = std::make_unique<AddLanguageDialog>(settingsWindow(), m_language);
m_dialog->onClosed = [this] {

@ -7,6 +7,7 @@
#include <chrono>
#include <cmath>
#include "base/i18n.h"
#include "pages/language/language.h"
#include "ui/color_provider.h"
#include "ui/keys.h"
@ -23,6 +24,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -36,7 +38,7 @@ class SystemLanguageBadge : public View
public:
SystemLanguageBadge()
{
m_label = addChild<Label>("System language");
m_label = addChild<Label>(tr("System language"));
m_label->setWeight(500);
}
@ -423,7 +425,8 @@ void PreferredLanguageList::scrollAtEdge()
void PreferredLanguageList::showActions(int index, PointF windowPosition)
{
m_actions = std::make_unique<Menu>();
const size_t remove = m_actions->addItem("Remove language", [this, index] { m_language.removeLanguage(index); });
const size_t remove =
m_actions->addItem(tr("Remove language"), [this, index] { m_language.removeLanguage(index); });
m_actions->setItemEnabled(remove, count() > 1);
m_actions->popup(window(), windowPosition);
}

@ -3,6 +3,7 @@
#include <cmath>
#include "base/i18n.h"
#include "pages/mouse/mouse.h"
#include "ui/slider.h"
#include "widgets/card.h"
@ -13,6 +14,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -38,18 +40,18 @@ class MousePage : public Page
{
public:
explicit MousePage(SettingsWindow& window)
: Page(window, "Mouse")
: Page(window, tr("Mouse"))
{
m_card = content()->addChild<Card>(Card::Style::List);
m_card->setName("mouse:card");
m_leftHanded = m_card->addChild<SwitchRow>("Left hand");
m_leftHanded = m_card->addChild<SwitchRow>(tr("Left hand"));
m_leftHanded->setName("mouse:leftHanded");
m_leftHanded->onToggled = [this](bool checked) { m_mouse.setLeftHanded(checked); };
m_naturalScroll = m_card->addChild<SwitchRow>("Natural scrolling");
m_naturalScroll = m_card->addChild<SwitchRow>(tr("Natural scrolling"));
m_naturalScroll->onToggled = [this](bool checked) { m_mouse.setNaturalScroll(checked); };
m_acceleration = m_card->addChild<SwitchRow>("Mouse acceleration");
m_acceleration = m_card->addChild<SwitchRow>(tr("Mouse acceleration"));
m_acceleration->onToggled = [this](bool checked) { m_mouse.setAcceleration(checked); };
m_speed = m_card->addChild<SliderRow>("Pointer speed");
m_speed = m_card->addChild<SliderRow>(tr("Pointer speed"));
m_speed->setName("mouse:speed");
m_speed->setRange(0, kSteps);
m_speed->setStepSize(1);

@ -4,6 +4,7 @@
#include "pages/network/common/detailsdialog.h"
#include "base/event_loop.h"
#include "base/i18n.h"
#include "cutefish/networkmanager/connections.h"
#include "cutefish/networkmanager/handler.h"
#include "pages/network/common/ipsettingsview.h"
@ -18,6 +19,8 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -65,33 +68,34 @@ DetailsDialog::DetailsDialog(SettingsWindow& window, NetworkList& list, Kind kin
m_details->setLayoutHints({.fillWidth = true});
m_savedCard = m_details->addChild<Card>(Card::Style::List);
m_autoConnect = m_savedCard->addChild<SwitchRow>(kind == Kind::Wireless ? "Auto-Join" : "Connect Automatically");
m_autoConnect =
m_savedCard->addChild<SwitchRow>(kind == Kind::Wireless ? tr("Auto-Join") : tr("Connect Automatically"));
m_autoConnect->onToggled = [this](bool checked) {
if (!m_list.isMock() && m_settings)
m_settings->setAutoConnect(checked);
};
NavigationItem* ipSettings = m_savedCard->addChild<NavigationItem>("IP Settings");
NavigationItem* ipSettings = m_savedCard->addChild<NavigationItem>(tr("IP Settings"));
ipSettings->setEnabled(!m_list.isMock());
ipSettings->onClicked = [this] { showIpSettings(true); };
m_addressCard = m_details->addChild<Card>(Card::Style::List);
if (kind == Kind::Wireless) {
m_security = m_addressCard->addChild<InfoRow>("Security");
m_signal = m_addressCard->addChild<InfoRow>("Signal");
m_security = m_addressCard->addChild<InfoRow>(tr("Security"));
m_signal = m_addressCard->addChild<InfoRow>(tr("Signal"));
}
m_ipv4 = m_addressCard->addChild<InfoRow>("IPv4 address");
m_ipv6 = m_addressCard->addChild<InfoRow>("IPv6 address");
m_mac = m_addressCard->addChild<InfoRow>("MAC address");
m_gateway = m_addressCard->addChild<InfoRow>("Gateway");
m_dns = m_addressCard->addChild<InfoRow>("DNS");
m_ipv4 = m_addressCard->addChild<InfoRow>(tr("IPv4 address"));
m_ipv6 = m_addressCard->addChild<InfoRow>(tr("IPv6 address"));
m_mac = m_addressCard->addChild<InfoRow>(tr("MAC address"));
m_gateway = m_addressCard->addChild<InfoRow>(tr("Gateway"));
m_dns = m_addressCard->addChild<InfoRow>(tr("DNS"));
RowLayout* buttons = m_details->addChild<RowLayout>();
buttons->setSpacing(metrics::dialogButtonSpacing);
buttons->setLayoutHints({.horizontalAlignment = Alignment::End});
DialogButton* closeButton = buttons->addChild<DialogButton>("Close");
DialogButton* closeButton = buttons->addChild<DialogButton>(tr("Close"));
closeButton->onClicked = [this] { close(); };
if (kind == Kind::Wireless) {
m_forget = buttons->addChild<DialogButton>("Forget this network", true);
m_forget = buttons->addChild<DialogButton>(tr("Forget this network"), true);
m_forget->setDestructive(true);
m_forget->onClicked = [this] {
if (!m_list.isMock())
@ -152,7 +156,8 @@ void DetailsDialog::update()
if (m_kind == Kind::Wireless) {
setSaved(!m_row.connectionPath.empty());
m_security->setValue(m_row.securityTypeString);
m_signal->setValue(std::to_string(m_row.signal) + "%");
// TRANSLATORS: a percentage, such as "92%".
m_signal->setValue(format(tr("{percent}%"), {{"percent", std::to_string(m_row.signal)}}));
} else {
// Only a connection that is up has addresses.
m_addressCard->setVisible(!m_row.ipV4Address.empty() || !m_row.ipV6Address.empty());
@ -199,7 +204,7 @@ void DetailsDialog::showIpSettings(bool shown)
cutefish::base::EventLoop::current()->post([gone] {});
}
m_dialog->setBackVisible(shown);
m_dialog->setTitle(shown ? std::string("IP Settings") : m_row.uniqueName);
m_dialog->setTitle(shown ? std::string(tr("IP Settings")) : m_row.uniqueName);
}
void DetailsDialog::close()

@ -6,6 +6,7 @@
#include <algorithm>
#include <cctype>
#include "base/i18n.h"
#include "cutefish/networkmanager/handler.h"
#include "pages/network/common/rows.h"
#include "ui/combo_box.h"
@ -19,6 +20,7 @@
#include "widgets/metrics.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -81,52 +83,52 @@ IpSettingsView::IpSettingsView(std::string connectionPath)
}));
Card* ipv4 = addCard(this);
m_ipv4Method = addRow(ipv4, "Configure IPv4").row->addChild<ComboBox>();
m_ipv4Method = addRow(ipv4, tr("Configure IPv4")).row->addChild<ComboBox>();
m_ipv4Method->setLayoutHints({.fillWidth = true});
m_ipv4Method->onActivated = [this](int) {
if (currentValue(m_ipv4Method, m_form.ipv4Methods) == "manual")
fillFromCurrent(true);
update();
};
FormRow row = addRow(ipv4, "IP Address");
FormRow row = addRow(ipv4, tr("IP Address"));
m_ipv4AddressRow = row.row;
m_ipv4Address = addField(row.row, "Required");
row = addRow(ipv4, "Subnet Mask");
m_ipv4Address = addField(row.row, tr("Required"));
row = addRow(ipv4, tr("Subnet Mask"));
m_ipv4NetmaskRow = row.row;
m_ipv4Netmask = addField(row.row, "255.255.255.0");
row = addRow(ipv4, "Router");
row = addRow(ipv4, tr("Router"));
m_ipv4GatewayRow = row.row;
m_ipv4Gateway = addField(row.row, "Optional");
m_ipv4Gateway = addField(row.row, tr("Optional"));
Card* ipv6 = addCard(this);
m_ipv6Method = addRow(ipv6, "Configure IPv6").row->addChild<ComboBox>();
m_ipv6Method = addRow(ipv6, tr("Configure IPv6")).row->addChild<ComboBox>();
m_ipv6Method->setLayoutHints({.fillWidth = true});
m_ipv6Method->onActivated = [this](int) {
if (currentValue(m_ipv6Method, m_form.ipv6Methods) == "manual")
fillFromCurrent(false);
update();
};
row = addRow(ipv6, "IP Address");
row = addRow(ipv6, tr("IP Address"));
m_ipv6AddressRow = row.row;
m_ipv6Address = addField(row.row, "Required");
row = addRow(ipv6, "Prefix Length");
m_ipv6Address = addField(row.row, tr("Required"));
row = addRow(ipv6, tr("Prefix Length"));
m_ipv6PrefixRow = row.row;
m_ipv6Prefix = addField(row.row, "64");
row = addRow(ipv6, "Router");
row = addRow(ipv6, tr("Router"));
m_ipv6GatewayRow = row.row;
m_ipv6Gateway = addField(row.row, "Optional");
m_ipv6Gateway = addField(row.row, tr("Optional"));
Card* dns = addCard(this);
m_automaticDnsRow = dns->addChild<RowLayout>();
m_automaticDnsRow->setSpacing(metrics::dialogSpacing);
m_automaticDnsRow->setLayoutHints({.fillWidth = true});
Label* automaticLabel = m_automaticDnsRow->addChild<Label>("Automatic DNS");
Label* automaticLabel = m_automaticDnsRow->addChild<Label>(tr("Automatic DNS"));
automaticLabel->setWrap(true);
automaticLabel->setLayoutHints({.fillWidth = true});
m_automaticDns = m_automaticDnsRow->addChild<Switch>();
setTrailingSwitchPadding(m_automaticDns);
m_automaticDns->onToggled = [this](bool) { update(); };
row = addRow(dns, "DNS Servers");
row = addRow(dns, tr("DNS Servers"));
m_dns = addField(row.row, {});
m_message = addChild<Label>();
@ -136,12 +138,12 @@ IpSettingsView::IpSettingsView(std::string connectionPath)
RowLayout* buttons = addChild<RowLayout>();
buttons->setSpacing(metrics::dialogButtonSpacing);
buttons->setLayoutHints({.horizontalAlignment = Alignment::End});
DialogButton* cancel = buttons->addChild<DialogButton>("Cancel");
DialogButton* cancel = buttons->addChild<DialogButton>(tr("Cancel"));
cancel->onClicked = [this] {
if (onFinished)
onFinished();
};
m_saveButton = buttons->addChild<DialogButton>("Save", true);
m_saveButton = buttons->addChild<DialogButton>(tr("Save"), true);
m_saveButton->onClicked = [this] { save(); };
load();
@ -241,7 +243,7 @@ void IpSettingsView::update()
for (RowLayout* row : {m_ipv6AddressRow, m_ipv6PrefixRow, m_ipv6GatewayRow})
row->setVisible(ipv6Manual);
m_automaticDnsRow->setVisible(automaticFamily);
m_dns->setPlaceholder(automaticFamily && m_automaticDns->isChecked() ? "Optional" : "1.1.1.1, 8.8.8.8");
m_dns->setPlaceholder(automaticFamily && m_automaticDns->isChecked() ? tr("Optional") : "1.1.1.1, 8.8.8.8");
const nm::ip::Check result = nm::ip::check(form);
const std::string problem = !m_error.empty() ? m_error : result.message;
@ -251,11 +253,11 @@ void IpSettingsView::update()
for (const char c : m_dns->text())
hasDns |= !std::isspace(static_cast<unsigned char>(c));
if (message.empty() && automaticFamily && m_automaticDns->isChecked() && hasDns)
message = "These servers are asked before the network's own.";
message = tr("These servers are asked before the network's own.");
m_message->setText(message);
m_message->setVisible(!message.empty());
m_saveButton->setText(m_saving ? "Saving…" : "Save");
m_saveButton->setText(m_saving ? tr("Saving…") : tr("Save"));
m_saveButton->setEnabled(!m_saving && result.complete);
requestLayout();
}

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "cutefish/networkmanager/connections.h"
#include "cutefish/networkmanager/handler.h"
#include "pages/network/common/detailsdialog.h"
@ -20,6 +21,7 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -76,11 +78,11 @@ class EthernetPage : public Page
{
public:
explicit EthernetPage(SettingsWindow& window)
: Page(window, "Ethernet")
: Page(window, tr("Ethernet"))
{
// The switch and the wired connections share one card.
m_card = content()->addChild<Card>(Card::Style::List);
m_switch = m_card->addChild<SwitchRow>("Ethernet");
m_switch = m_card->addChild<SwitchRow>(tr("Ethernet"));
m_switch->setName("ethernet:switch");
m_switch->onToggled = [this](bool checked) {
if (!m_list.isMock())

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "cutefish/networkmanager/connections.h"
#include "cutefish/networkmanager/handler.h"
#include "cutefish/networkmanager/hotspot.h"
@ -22,6 +23,8 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -68,13 +71,13 @@ class HotspotPage : public Page
{
public:
explicit HotspotPage(SettingsWindow& window)
: Page(window, "Hotspot")
: Page(window, tr("Hotspot"))
, m_configuration(nm::HotspotConfiguration::instance())
{
content()->setSpacing(metrics::sectionLabelSpacing);
Card* switchCard = content()->addChild<Card>(Card::Style::List);
m_switch = switchCard->addChild<SwitchRow>("Wi-Fi hotspot");
m_switch = switchCard->addChild<SwitchRow>(tr("Wi-Fi hotspot"));
m_switch->setName("hotspot:switch");
m_switch->onToggled = [this](bool checked) {
if (checked)
@ -92,7 +95,7 @@ public:
Card* fields = content()->addChild<Card>(Card::Style::List);
fields->setLayoutHints(
{.fillWidth = true, .margins = {0, metrics::sectionSpacing - metrics::sectionLabelSpacing, 0, 0}});
ListRow* nameRow = fields->addChild<ListRow>("Network name");
ListRow* nameRow = fields->addChild<ListRow>(tr("Network name"));
nameRow->setInteractive(false);
nameRow->setMinimumRowHeight(metrics::controlRowHeight);
m_name = nameRow->addContent<TextField>(m_configuration->name());
@ -104,7 +107,7 @@ public:
m_configuration->setName(m_name->text());
};
ListRow* passwordRow = fields->addChild<ListRow>("Password");
ListRow* passwordRow = fields->addChild<ListRow>(tr("Password"));
passwordRow->setInteractive(false);
passwordRow->setMinimumRowHeight(metrics::controlRowHeight);
m_password = passwordRow->addContent<PasswordField>();
@ -117,7 +120,7 @@ public:
if (nm::HotspotConfiguration::passwordValid(m_password->field->text()))
m_configuration->setPassword(m_password->field->text());
};
m_password->generate->setToolTip("Generate password");
m_password->generate->setToolTip(tr("Generate password"));
m_password->generate->onClicked = [this] {
m_password->field->setText(nm::HotspotConfiguration::generatePassword());
m_configuration->setPassword(m_password->field->text());
@ -185,19 +188,20 @@ private:
return;
}
m_confirm = std::make_unique<ConfirmDialog>(
&settingsWindow().window(), "Turn On Wi-Fi Hotspot?",
"Turning on the hotspot will disconnect from “" + m_handler.hotspotReplaces()
+ "”, and you will not have access to the internet it provides.");
m_confirm->setCancelText("Cancel");
m_confirm->setConfirmText("Turn on");
&settingsWindow().window(), tr("Turn On Wi-Fi Hotspot?"),
format(tr("Turning on the hotspot will disconnect from “{network}”, and you will not have access to the "
"internet it provides."),
{{"network", m_handler.hotspotReplaces()}}));
m_confirm->setCancelText(tr("Cancel"));
m_confirm->setConfirmText(tr("Turn on"));
m_confirm->onAccepted = [this] { start(); };
m_confirm->show();
}
std::string description() const
{
return "Share this computer's internet connection by creating a Wi-Fi network other devices can join. The "
"connection to share must come from a source other than Wi-Fi.";
return tr("Share this computer's internet connection by creating a Wi-Fi network other devices can join. "
"The connection to share must come from a source other than Wi-Fi.");
}
void update()
@ -214,12 +218,13 @@ private:
else if (!m_enabled.isReady())
status = description();
else if (!m_enabled.isWirelessEnabled())
status = "Turn on Wi-Fi to use the hotspot.";
status = tr("Turn on Wi-Fi to use the hotspot.");
else if (!m_handler.hotspotAllowed())
status = "System policy prohibits use as a hotspot.";
status = tr("System policy prohibits use as a hotspot.");
else if (active)
status = "Other devices can join “" + m_configuration->name()
+ "” with the password below. Turn off the hotspot to use Wi-Fi on this computer.";
status = format(tr("Other devices can join “{network}” with the password below. Turn off the hotspot to "
"use Wi-Fi on this computer."),
{{"network", m_configuration->name()}});
else
status = description();
m_status->setText(status);
@ -229,10 +234,10 @@ private:
std::string validation;
if (!nameValid)
validation = m_name->text().empty() ? "Network name cannot be empty." : "Network name is too long.";
validation = m_name->text().empty() ? tr("Network name cannot be empty.") : tr("Network name is too long.");
else if (!passwordValid)
validation = m_password->field->text().size() < 8 ? "The password must have at least 8 characters."
: "The password must have at most 63 characters.";
validation = m_password->field->text().size() < 8 ? tr("The password must have at least 8 characters.")
: tr("The password must have at most 63 characters.");
m_validation->setText(validation);
m_validation->setVisible(!validation.empty());
requestLayout();

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "cutefish/networkmanager/connections.h"
#include "cutefish/networkmanager/handler.h"
#include "ui/cell_layout.h"
@ -12,6 +13,7 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -23,20 +25,20 @@ class NetworkPage : public Page
{
public:
explicit NetworkPage(SettingsWindow& window)
: Page(window, "Network")
: Page(window, tr("Network"))
{
Card* card = content()->addChild<Card>(Card::Style::List);
NavigationItem* vpn = card->addChild<NavigationItem>("VPN");
NavigationItem* vpn = card->addChild<NavigationItem>(tr("VPN"));
vpn->setName("network:vpn");
vpn->setLeading<RowIcon>("network-vpn", Color::rgb(0x48484C));
vpn->onClicked = [this] { settingsWindow().pushPage("vpn"); };
NavigationItem* proxy = card->addChild<NavigationItem>("Proxy");
NavigationItem* proxy = card->addChild<NavigationItem>(tr("Proxy"));
proxy->setName("network:proxy");
proxy->setLeading<RowIcon>("proxy.svg", Color::rgb(0x0067FF));
proxy->onClicked = [this] { settingsWindow().pushPage("proxy"); };
m_hotspot = card->addChild<NavigationItem>("Hotspot");
m_hotspot = card->addChild<NavigationItem>(tr("Hotspot"));
m_hotspot->setName("network:hotspot");
m_hotspot->setLeading<RowIcon>("network-wireless-hotspot", Color::rgb(0x2EBC6B));
m_hotspot->onClicked = [this] { settingsWindow().pushPage("hotspot"); };

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "pages/network/common/textchecks.h"
#include "pages/network/proxy/proxysettings.h"
#include "ui/button.h"
@ -21,6 +22,7 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings::network {
@ -50,15 +52,15 @@ class ProxyPage : public Page
{
public:
explicit ProxyPage(SettingsWindow& window)
: Page(window, "Proxy")
: Page(window, tr("Proxy"))
, m_fileChooser(&window.window())
{
Card* modes = content()->addChild<Card>();
m_noProxy = addRadio(modes, "No proxy", 0);
m_noProxy = addRadio(modes, tr("No proxy"), 0);
m_noProxy->setName("proxy:none");
m_auto = addRadio(modes, "Use proxy auto configuration URL", 1);
m_auto = addRadio(modes, tr("Use proxy auto configuration URL"), 1);
m_auto->setVisible(false);
m_manual = addRadio(modes, "Use manually specified proxy configuration", 2);
m_manual = addRadio(modes, tr("Use manually specified proxy configuration"), 2);
m_manual->setName("proxy:manual");
m_proxyItem = content()->addChild<Card>();
@ -69,7 +71,7 @@ public:
m_script = m_scriptRow->addChild<TextField>(m_proxy.scriptProxy());
m_script->setLayoutHints({.fillWidth = true});
m_script->onTextEdited = [this] { markChanged(); };
Button* selectFile = m_scriptRow->addChild<Button>("Select file");
Button* selectFile = m_scriptRow->addChild<Button>(tr("Select file"));
selectFile->onClicked = [this] { m_fileChooser.open(); };
m_fileChooser.onAccepted = [this](const std::vector<std::string>& paths, const std::string&) {
if (paths.empty())
@ -81,12 +83,12 @@ public:
m_grid = m_proxyItem->addChild<GridLayout>(4);
m_grid->setColumnSpacing(metrics::rowSpacing);
m_grid->setRowSpacing(metrics::formRowSpacing);
addLabel("HTTP proxy");
addLabel(tr("HTTP proxy"));
m_http = addHostField(m_proxy.httpProxy());
addLabel("Port");
addLabel(tr("Port"));
m_httpPort = addPortField(m_proxy.httpProxyPort());
addSpacer();
m_forFtp = m_grid->addChild<CheckBox>("Also use this proxy for FTP");
m_forFtp = m_grid->addChild<CheckBox>(tr("Also use this proxy for FTP"));
m_forFtp->setLayoutHints({.fillWidth = true});
m_forFtp->setChecked(m_proxy.useSameProxy());
m_forFtp->onToggled = [this](bool) {
@ -95,20 +97,20 @@ public:
};
addSpacer();
addSpacer();
addLabel("FTP proxy");
addLabel(tr("FTP proxy"));
m_ftp = addHostField(m_proxy.ftpProxy());
addLabel("Port");
addLabel(tr("Port"));
m_ftpPort = addPortField(m_proxy.ftpProxyPort());
addLabel("SOCKS proxy");
addLabel(tr("SOCKS proxy"));
m_socks = addHostField(m_proxy.socksProxy());
addLabel("Port");
addLabel(tr("Port"));
m_socksPort = addPortField(m_proxy.socksProxyPort());
m_divider = m_proxyItem->addChild<Divider>();
m_saveRow = m_proxyItem->addChild<RowLayout>();
m_saveRow->setSpacing(metrics::rowSpacing);
m_saveRow->addChild<View>()->setLayoutHints({.fillWidth = true});
Button* save = m_saveRow->addChild<Button>("Save");
Button* save = m_saveRow->addChild<Button>(tr("Save"));
save->onClicked = [this] { saveFields(); };
m_subscription = m_proxy.changed.connect([this](unsigned fields) { reload(fields); });

@ -5,6 +5,7 @@
#include <algorithm>
#include "base/i18n.h"
#include "pages/network/common/formscroll.h"
#include "pages/network/common/networklist.h"
#include "pages/network/vpn/vpneditor.h"
@ -24,6 +25,8 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -93,17 +96,17 @@ void AddVpnDialog::show()
return;
m_adding = false;
m_error.clear();
m_dialog = std::make_unique<FormDialog>(m_window, "Add VPN", kWidth);
m_dialog = std::make_unique<FormDialog>(m_window, tr("Add VPN"), kWidth);
ColumnLayout* content = m_dialog->content();
RowLayout* kindRow = content->addChild<RowLayout>();
kindRow->setSpacing(metrics::dialogSpacing);
kindRow->setLayoutHints({.fillWidth = true});
m_kindLabel = kindRow->addChild<Label>("Type");
m_kindLabel = kindRow->addChild<Label>(tr("Type"));
m_kinds = m_handler.vpnKinds();
std::vector<std::string> labels;
for (const nm::vpn::Kind& kind : m_kinds)
labels.push_back(kind.installed ? kind.label : kind.label + " (Not Installed)");
labels.push_back(kind.installed ? kind.label : format(tr("{type} (Not Installed)"), {{"type", kind.label}}));
m_kindBox = kindRow->addChild<ComboBox>(labels);
m_kindBox->setLayoutHints({.fillWidth = true});
m_kindBox->setCurrentIndex(0);
@ -127,9 +130,9 @@ void AddVpnDialog::show()
m_errorLabel = content->addChild<MessageLabel>();
RowLayout* buttons = m_dialog->addButtonRow();
DialogButton* cancel = buttons->addChild<DialogButton>("Cancel");
DialogButton* cancel = buttons->addChild<DialogButton>(tr("Cancel"));
cancel->onClicked = [this] { m_dialog->close(); };
m_addButton = buttons->addChild<DialogButton>("Add", true);
m_addButton = buttons->addChild<DialogButton>(tr("Add"), true);
m_addButton->onClicked = [this] { add(); };
loadKind();
@ -196,19 +199,21 @@ VpnDetailsDialog::VpnDetailsDialog(SettingsWindow& window, NetworkList& list, nm
m_scroll = formCard->addChild<FormScroll>(std::move(editor), metrics::cardHorizontalPadding);
m_statusCard = m_content->addChild<Card>(Card::Style::List);
m_type = m_statusCard->addChild<InfoRow>("Type");
m_status = m_statusCard->addChild<InfoRow>("Status");
m_address = m_statusCard->addChild<InfoRow>("IPv4 address");
m_export = m_statusCard->addChild<ListRow>("Export to file…");
m_type = m_statusCard->addChild<InfoRow>(tr("Type"));
m_status = m_statusCard->addChild<InfoRow>(tr("Status"));
m_address = m_statusCard->addChild<InfoRow>(tr("IPv4 address"));
m_export = m_statusCard->addChild<ListRow>(tr("Export to file…"));
m_export->onClicked = [this] {
m_exportChooser = std::make_unique<FileChooser>(m_dialog->window());
m_exportChooser->setMode(FileChooser::Mode::SaveFile);
m_exportChooser->setTitle("Export VPN Configuration");
m_exportChooser->setTitle(tr("Export VPN Configuration"));
std::string patterns;
for (const std::string& pattern : m_editor->form().exportPatterns)
patterns += (patterns.empty() ? "" : " ") + pattern;
m_exportChooser->setFilters({FileChooser::Filter::fromNameFilter("VPN configurations (" + patterns + ")"),
FileChooser::Filter::fromNameFilter("All files (*)")});
m_exportChooser->setFilters(
{FileChooser::Filter::fromNameFilter(format(tr("VPN configurations ({patterns})"),
{{"patterns", patterns}})),
FileChooser::Filter::fromNameFilter(tr("All files (*)"))});
m_exportChooser->onAccepted = [this](const std::vector<std::string>& paths, const std::string&) {
if (paths.empty())
return;
@ -224,16 +229,16 @@ VpnDetailsDialog::VpnDetailsDialog(SettingsWindow& window, NetworkList& list, nm
RowLayout* buttons = m_content->addChild<RowLayout>();
buttons->setSpacing(metrics::dialogButtonSpacing);
buttons->setLayoutHints({.horizontalAlignment = Alignment::End});
DialogButton* remove = buttons->addChild<DialogButton>("Remove VPN");
DialogButton* remove = buttons->addChild<DialogButton>(tr("Remove VPN"));
remove->setDestructive(true);
remove->onClicked = [this] {
m_handler.removeConnection(m_connectionPath);
close();
};
buttons->addChild<View>()->setLayoutHints({.fillWidth = true});
DialogButton* cancel = buttons->addChild<DialogButton>("Cancel");
DialogButton* cancel = buttons->addChild<DialogButton>(tr("Cancel"));
cancel->onClicked = [this] { close(); };
m_saveButton = buttons->addChild<DialogButton>("Save", true);
m_saveButton = buttons->addChild<DialogButton>(tr("Save"), true);
m_saveButton->onClicked = [this] { save(); };
// A long form scrolls instead of outgrowing the main window.
@ -320,14 +325,14 @@ void VpnDetailsDialog::update()
m_dialog->setTitle(row->name);
m_type->setValue(row->vpnType);
m_status->setValue(row->connectionState == nm::ConnectionStatus::Activated ? "Connected"
: row->connectionState == nm::ConnectionStatus::Activating ? "Connecting…"
: "Not connected");
m_status->setValue(row->connectionState == nm::ConnectionStatus::Activated ? tr("Connected")
: row->connectionState == nm::ConnectionStatus::Activating ? tr("Connecting…")
: tr("Not connected"));
m_address->setValue(row->connectionState == nm::ConnectionStatus::Activated ? row->ipV4Address : std::string());
m_export->setVisible(m_editor->form().exportable);
static_cast<MessageLabel*>(m_message)
->setMessage(!m_error.empty() ? m_error : row->statusMessage, !m_error.empty());
m_saveButton->setText(m_saving ? "Saving…" : "Save");
m_saveButton->setText(m_saving ? tr("Saving…") : tr("Save"));
m_saveButton->setEnabled(!m_saving && m_editor->complete());
m_scroll->requestLayout();
}

@ -7,6 +7,7 @@
#include <climits>
#include "base/event_loop.h"
#include "base/i18n.h"
#include "cutefish/networkmanager/types.h"
#include "pages/network/common/textchecks.h"
#include "pages/network/common/rows.h"
@ -23,6 +24,8 @@
#include "widgets/metrics.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace schema = cutefish::networkmanager::vpn::schema;
@ -213,7 +216,7 @@ private:
hasAdvanced |= field.advanced;
}
m_link = addChild<LinkLabel>("Show advanced options");
m_link = addChild<LinkLabel>(tr("Show advanced options"));
m_link->setVisible(hasAdvanced);
m_link->onClicked = [this] {
m_advancedShown = !m_advancedShown;
@ -254,7 +257,7 @@ private:
switch (field.type) {
case schema::Type::Password: {
row.text = row.row->addChild<TextField>();
row.text->setPlaceholder("Asked on connect");
row.text->setPlaceholder(tr("Asked on connect"));
row.text->setEchoMode(TextField::EchoMode::Password);
row.text->setLayoutHints({.fillWidth = true});
const std::string key = field.key;
@ -274,12 +277,12 @@ private:
}
case schema::Type::File: {
row.text = row.row->addChild<TextField>();
row.text->setPlaceholder(field.required ? "Required" : "Optional");
row.text->setPlaceholder(field.required ? tr("Required") : tr("Optional"));
row.text->setLayoutHints({.fillWidth = true});
const std::string key = field.key;
row.text->onTextEdited = [this, key, text = row.text] { setValue(key, text->text()); };
row.text->onAccepted = [this] { accepted(); };
Button* choose = row.row->addChild<Button>("Choose…");
Button* choose = row.row->addChild<Button>(tr("Choose…"));
choose->setLayoutHints({.fillHeight = true});
const std::string title = field.label;
choose->onClicked = [this, key, title] {
@ -320,8 +323,8 @@ private:
const bool number = field.type == schema::Type::Number;
row.text = row.row->addChild<TextField>();
row.text->setPlaceholder(!field.placeholder.empty() ? field.placeholder
: field.required ? "Required"
: "Optional");
: field.required ? tr("Required")
: tr("Optional"));
row.text->setLayoutHints({.fillWidth = true});
if (number) {
const long long maximum = field.maximum > 0 ? field.maximum : INT_MAX;
@ -339,10 +342,10 @@ private:
void loadStorage(FieldRow& row)
{
const uint32_t saveFlags = m_shared ? nm::SecretSystemOwned : nm::SecretAgentOwned;
std::vector<std::string> labels{m_shared ? "Saved for all" : "Saved", "Ask each time"};
std::vector<std::string> labels{m_shared ? tr("Saved for all") : tr("Saved"), tr("Ask each time")};
row.storageValues = {saveFlags, nm::SecretNotSaved};
if (row.field->allowNotRequired) {
labels.push_back("Not needed");
labels.push_back(tr("Not needed"));
row.storageValues.push_back(nm::SecretNotRequired);
}
row.storage->setItems(labels);
@ -410,7 +413,7 @@ private:
row.storage->setLayoutHints({.fillWidth = !saving, .preferredWidth = 120});
}
}
m_link->setText(m_advancedShown ? "Hide advanced options" : "Show advanced options");
m_link->setText(m_advancedShown ? tr("Hide advanced options") : tr("Show advanced options"));
m_advanced->setVisible(m_advancedShown);
requestLayout();
changed();
@ -442,12 +445,12 @@ public:
setSpacing(metrics::formRowSpacing);
setLayoutHints({.fillWidth = true});
m_privateKey = addField(this, "Private key", "Required");
m_privateKey = addField(this, tr("Private key"), tr("Required"));
m_privateKey->setEchoMode(TextField::EchoMode::Password);
m_address = addField(this, "Address", "10.0.0.2/24");
m_dns = addField(this, "DNS", "Optional");
m_address = addField(this, tr("Address"), "10.0.0.2/24");
m_dns = addField(this, tr("DNS"), tr("Optional"));
m_link = addChild<LinkLabel>("Show advanced options");
m_link = addChild<LinkLabel>(tr("Show advanced options"));
m_link->onClicked = [this] {
m_advancedShown = !m_advancedShown;
update();
@ -456,15 +459,15 @@ public:
m_advanced = addChild<ColumnLayout>();
m_advanced->setSpacing(metrics::formRowSpacing);
m_advanced->setLayoutHints({.fillWidth = true});
m_interface = addField(m_advanced, "Interface name", "From the name");
m_interface = addField(m_advanced, tr("Interface name"), tr("From the name"));
m_interface->setMaximumLength(15);
m_listenPort = addField(m_advanced, "Listen port", "Automatic");
m_listenPort = addField(m_advanced, tr("Listen port"), tr("Automatic"));
m_listenPort->setValidator([](std::string_view text) { return isNumberText(text, 65535); });
m_fwmark = addField(m_advanced, "Firewall mark", "Optional");
m_mtu = addField(m_advanced, "MTU", "Automatic");
m_fwmark = addField(m_advanced, tr("Firewall mark"), tr("Optional"));
m_mtu = addField(m_advanced, tr("MTU"), tr("Automatic"));
m_mtu->setValidator([](std::string_view text) { return isNumberText(text, INT_MAX); });
RowLayout* routes = addFormRow(m_advanced);
Label* routesLabel = routes->addChild<Label>("Add routes for the peers' allowed IPs");
Label* routesLabel = routes->addChild<Label>(tr("Add routes for the peers' allowed IPs"));
routesLabel->setWrap(true);
routesLabel->setLayoutHints({.fillWidth = true});
m_peerRoutes = routes->addChild<Switch>();
@ -474,7 +477,7 @@ public:
m_peers = addChild<ColumnLayout>();
m_peers->setSpacing(metrics::formRowSpacing);
m_peers->setLayoutHints({.fillWidth = true});
m_addPeer = addChild<LinkLabel>("Add peer");
m_addPeer = addChild<LinkLabel>(tr("Add peer"));
m_addPeer->onClicked = [this] {
addPeer({});
update();
@ -599,10 +602,10 @@ private:
card.card->setPadding(Margins::all(metrics::cardHorizontalPadding));
card.card->setSpacing(metrics::formRowSpacing);
RowLayout* header = addFormRow(card.card);
card.title = header->addChild<Label>("Peer");
card.title = header->addChild<Label>(tr("Peer"));
card.title->setWeight(600);
card.title->setLayoutHints({.fillWidth = true});
card.remove = header->addChild<Button>("Remove");
card.remove = header->addChild<Button>(tr("Remove"));
Card* owner = card.card;
card.remove->onClicked = [this, owner] {
const auto found = std::find_if(m_peerCards.begin(), m_peerCards.end(),
@ -617,17 +620,19 @@ private:
update();
cutefish::base::EventLoop::current()->post([gone] {});
};
card.publicKey = addField(card.card, "Public key", "Required");
card.publicKey = addField(card.card, tr("Public key"), tr("Required"));
card.publicKey->setText(peer.publicKey);
card.presharedKey = addField(card.card, "Preshared key", peer.hasPresharedKey ? "Kept" : "Optional");
card.presharedKey = addField(card.card, tr("Preshared key"),
// TRANSLATORS: a placeholder: the stored key stays unless a new one is typed.
peer.hasPresharedKey ? tr("Kept") : tr("Optional"));
card.presharedKey->setEchoMode(TextField::EchoMode::Password);
card.presharedKey->setText(peer.presharedKey);
card.hasPresharedKey = peer.hasPresharedKey;
card.endpoint = addField(card.card, "Endpoint", "vpn.example.com:51820");
card.endpoint = addField(card.card, tr("Endpoint"), "vpn.example.com:51820");
card.endpoint->setText(peer.endpoint);
card.allowedIps = addField(card.card, "Allowed IPs", "All traffic");
card.allowedIps = addField(card.card, tr("Allowed IPs"), tr("All traffic"));
card.allowedIps->setText(peer.allowedIps);
card.keepalive = addField(card.card, "Keepalive", "Off");
card.keepalive = addField(card.card, tr("Keepalive"), tr("Off"));
card.keepalive->setValidator([](std::string_view text) { return isNumberText(text, 65535); });
card.keepalive->setText(peer.keepalive);
std::copy(m_labels.end() - 5, m_labels.end(), card.labels);
@ -651,11 +656,12 @@ private:
void update()
{
m_link->setText(m_advancedShown ? "Hide advanced options" : "Show advanced options");
m_link->setText(m_advancedShown ? tr("Hide advanced options") : tr("Show advanced options"));
m_advanced->setVisible(m_advancedShown);
const bool several = m_peerCards.size() > 1;
for (size_t i = 0; i < m_peerCards.size(); ++i) {
m_peerCards[i].title->setText(several ? "Peer " + std::to_string(i + 1) : std::string("Peer"));
m_peerCards[i].title->setText(several ? format(tr("Peer {number}"), {{"number", std::to_string(i + 1)}})
: std::string(tr("Peer")));
m_peerCards[i].remove->setVisible(several);
// The card's own air above it.
m_peerCards[i].card->setLayoutHints({.fillWidth = true, .margins = {0, metrics::s, 0, 0}});
@ -695,9 +701,9 @@ VpnEditor::VpnEditor()
m_nameRow = addFormRow(this);
std::vector<Label*> unused;
m_nameLabel = addFieldLabel(m_nameRow, "Name", unused);
m_nameLabel = addFieldLabel(m_nameRow, tr("Name"), unused);
m_name = m_nameRow->addChild<TextField>();
m_name->setPlaceholder("Optional");
m_name->setPlaceholder(tr("Optional"));
m_name->setLayoutHints({.fillWidth = true});
m_name->onAccepted = [this] {
if (onAccepted)
@ -716,12 +722,12 @@ VpnEditor::VpnEditor()
setTrailingSwitchPadding(toggle);
return toggle;
};
m_shared = addSwitch("Available to all users");
m_shared = addSwitch(tr("Available to all users"));
m_shared->onToggled = [this](bool checked) {
if (m_plugin)
m_plugin->setShared(checked);
};
m_ownNetworkOnly = addSwitch("Use only for resources on its network");
m_ownNetworkOnly = addSwitch(tr("Use only for resources on its network"));
}
VpnEditor::~VpnEditor() = default;
@ -763,7 +769,7 @@ void VpnEditor::load(const nm::VpnForm& form)
addChild(std::move(switches));
}
m_notInstalled->setText("Install " + form.package + " to set up this kind of VPN.");
m_notInstalled->setText(format(tr("Install {package} to set up this kind of VPN."), {{"package", form.package}}));
m_notInstalled->setVisible(!form.installed && !form.kind.empty());
m_nameRow->setVisible(form.installed);
m_switches->setVisible(form.installed);

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "cutefish/networkmanager/handler.h"
#include "pages/network/common/networklist.h"
#include "pages/network/common/rows.h"
@ -22,6 +23,8 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -71,8 +74,8 @@ public:
|| item.connectionState == nm::ConnectionStatus::Deactivating;
m_name->setText(item.name);
m_detail->setText(!item.statusMessage.empty() ? item.statusMessage
: item.connectionState == nm::ConnectionStatus::Activating ? std::string("Connecting…")
: needsUserName() ? std::string("Add your user name to connect.")
: item.connectionState == nm::ConnectionStatus::Activating ? std::string(tr("Connecting…"))
: needsUserName() ? std::string(tr("Add your user name to connect."))
: item.vpnType);
m_problem = !item.statusMessage.empty() && item.canActivate;
m_busy->setVisible(busy);
@ -122,7 +125,7 @@ class VpnPage : public Page
{
public:
explicit VpnPage(SettingsWindow& window)
: Page(window, "VPN")
: Page(window, tr("VPN"))
, m_addDialog(window, m_handler)
, m_importChooser(&window.window())
{
@ -142,17 +145,18 @@ public:
};
return row;
});
m_add = m_card->addChild<ListRow>("Add VPN…");
m_add = m_card->addChild<ListRow>(tr("Add VPN…"));
m_add->setName("vpn:add");
m_add->onClicked = [this] { m_addDialog.show(); };
m_import = m_card->addChild<ListRow>("Import from file…");
m_import = m_card->addChild<ListRow>(tr("Import from file…"));
m_import->setName("vpn:import");
m_import->onClicked = [this] {
m_importChooser.open();
};
m_importChooser.setTitle("Import VPN Configuration");
m_importChooser.setFilters({FileChooser::Filter::fromNameFilter("VPN configurations (*.ovpn *.conf *.pcf)"),
FileChooser::Filter::fromNameFilter("All files (*)")});
m_importChooser.setTitle(tr("Import VPN Configuration"));
m_importChooser.setFilters({FileChooser::Filter::fromNameFilter(format(tr("VPN configurations ({patterns})"),
{{"patterns", "*.ovpn *.conf *.pcf"}})),
FileChooser::Filter::fromNameFilter(tr("All files (*)"))});
m_importChooser.onAccepted = [this](const std::vector<std::string>& paths, const std::string&) {
if (paths.empty())
return;
@ -217,10 +221,10 @@ private:
m_add->setEnabled(!m_importing);
m_import->setEnabled(!m_importing);
m_import->setText(m_importing ? "Importing…" : "Import from file…");
m_import->setText(m_importing ? tr("Importing…") : tr("Import from file…"));
m_note->setText(!m_importError.empty() ? m_importError
: "Import the configuration file your VPN provider gives you, such as an "
"OpenVPN .ovpn or a WireGuard .conf file.");
: tr("Import the configuration file your VPN provider gives you, such "
"as an OpenVPN .ovpn or a WireGuard .conf file."));
requestLayout();
}

@ -6,6 +6,7 @@
#include <algorithm>
#include <cctype>
#include "base/i18n.h"
#include "ui/combo_box.h"
#include "ui/dialog_window.h"
#include "ui/label.h"
@ -17,6 +18,9 @@
#include "widgets/metrics.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
using cutefish::i18n::trNoop;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -30,14 +34,15 @@ struct SecurityChoice
};
static const SecurityChoice kSecurities[] = {
{"Open", "none"},
// TRANSLATORS: a Wi-Fi network's security: none, anyone can join.
{trNoop("Open"), "none"},
{"WEP", "wep"},
{"WPA", "wpa"},
{"WPA2/WPA3", "wpa2-wpa3"},
{"WPA3", "wpa3"},
{"WPA Enterprise", "wpa-eap"},
{"WPA2 Enterprise", "wpa2-eap"},
{"WPA3 Enterprise", "wpa3-eap"},
{trNoop("WPA Enterprise"), "wpa-eap"},
{trNoop("WPA2 Enterprise"), "wpa2-eap"},
{trNoop("WPA3 Enterprise"), "wpa3-eap"},
};
static constexpr int kDefaultSecurity = 3;
@ -102,7 +107,8 @@ bool NewNetworkDialog::isOpen() const
std::string NewNetworkDialog::title() const
{
return m_accessPoint.empty() ? std::string("Add Other Network") : "Join “" + m_networkName + "”";
return m_accessPoint.empty() ? std::string(tr("Add Other Network"))
: format(tr("Join “{network}”"), {{"network", m_networkName}});
}
std::string NewNetworkDialog::security() const
@ -188,20 +194,20 @@ void NewNetworkDialog::build()
return field;
};
m_nameLabel = addLabel("Name");
m_name = addField("Network name");
m_securityLabel = addLabel("Security");
m_nameLabel = addLabel(tr("Name"));
m_name = addField(tr("Network name"));
m_securityLabel = addLabel(tr("Security"));
std::vector<std::string> securities;
for (const SecurityChoice& choice : kSecurities)
securities.push_back(choice.text);
securities.push_back(tr(choice.text));
m_security = m_form->addChild<ComboBox>(securities);
m_security->setLayoutHints({.fillWidth = true});
m_security->setCurrentIndex(kDefaultSecurity);
m_security->onActivated = [this](int) { update(); };
m_userNameLabel = addLabel("Username");
m_userName = addField("Username");
m_passwordLabel = addLabel("Password");
m_password = addField("Password");
m_userNameLabel = addLabel(tr("Username"));
m_userName = addField(tr("Username"));
m_passwordLabel = addLabel(tr("Password"));
m_password = addField(tr("Password"));
m_password->setEchoMode(TextField::EchoMode::Password);
// Hidden rows leave the grid, so the fields would move as they appear.
@ -211,14 +217,15 @@ void NewNetworkDialog::build()
for (Label* label : m_labels)
label->setLayoutHints({.preferredWidth = labelWidth});
m_certificateNote = content->addChild<Label>("This network signs in with a certificate, which can't be set up here yet.");
m_certificateNote =
content->addChild<Label>(tr("This network signs in with a certificate, which can't be set up here yet."));
m_certificateNote->setWrap(true);
m_certificateNote->setLayoutHints({.fillWidth = true});
RowLayout* buttons = m_dialog->addButtonRow();
DialogButton* cancel = buttons->addChild<DialogButton>("Cancel");
DialogButton* cancel = buttons->addChild<DialogButton>(tr("Cancel"));
cancel->onClicked = [this] { close(); };
m_joinButton = buttons->addChild<DialogButton>("Join", true);
m_joinButton = buttons->addChild<DialogButton>(tr("Join"), true);
m_joinButton->onClicked = [this] { join(); };
}

@ -4,6 +4,7 @@
#include <algorithm>
#include <chrono>
#include "base/i18n.h"
#include "base/timer.h"
#include "cutefish/networkmanager/connections.h"
#include "cutefish/networkmanager/handler.h"
@ -28,6 +29,7 @@
using namespace cutefish::ui;
using namespace std::chrono_literals;
using cutefish::i18n::tr;
namespace nm = cutefish::networkmanager;
namespace settings::network {
@ -48,7 +50,7 @@ class WlanPage : public Page
{
public:
explicit WlanPage(SettingsWindow& window)
: Page(window, "Wi-Fi")
: Page(window, tr("Wi-Fi"))
, m_newNetwork(window)
{
content()->setSpacing(metrics::sectionSpacing);
@ -69,7 +71,7 @@ public:
m_view->setSpacing(metrics::sectionSpacing);
m_view->setLayoutHints({.fillWidth = true});
m_mainCard = m_view->addChild<Card>(Card::Style::List);
m_switch = m_mainCard->addChild<SwitchRow>("Wi-Fi");
m_switch = m_mainCard->addChild<SwitchRow>(tr("Wi-Fi"));
m_switch->setName("wlan:switch");
m_switch->onToggled = [this](bool checked) { wirelessToggled(checked); };
m_currentRows = std::make_unique<KeyedRows<WifiRow>>([this](View* parent) { return makeRow(parent); });
@ -78,13 +80,13 @@ public:
m_hotspotRow = m_mainCard->addChild<ListRow>();
m_hotspotRow->setName("wlan:hotspot");
m_hotspotRow->setLeading<Icon>("network-wireless-hotspot", 18);
m_hotspotLabel = m_hotspotRow->addContent<Label>("Hotspot");
m_hotspotLabel = m_hotspotRow->addContent<Label>(tr("Hotspot"));
m_hotspotChevron = m_hotspotRow->addContent<TrailingIcon>("chevron-right", 16);
m_hotspotRow->onClicked = [this] { settingsWindow().switchToPage("hotspot"); };
m_known = addSection("Known networks", false);
m_known = addSection(tr("Known networks"), false);
m_known.section->setName("wlan:known");
m_other = addSection("Other networks", true);
m_other = addSection(tr("Other networks"), true);
m_other.section->setName("wlan:other");
m_other.footer->setName("wlan:addOther");
@ -126,7 +128,7 @@ private:
section.card = section.section->addChild<Card>(Card::Style::List);
section.rows = std::make_unique<KeyedRows<WifiRow>>([this](View* parent) { return makeRow(parent); });
if (footer) {
section.footer = section.card->addChild<ListRow>("Add other…");
section.footer = section.card->addChild<ListRow>(tr("Add other…"));
// Lines the text up with the network names beside their icons.
View* spacer = section.footer->setLeading<View>();
spacer->setLayoutHints({.preferredWidth = 18, .preferredHeight = 18});

@ -1,6 +1,7 @@
// SPDX-FileCopyrightText: 2026 Reion Wong <reionwong@gmail.com>
// SPDX-License-Identifier: BSD-3-Clause
#include "base/i18n.h"
#include "pages/notifications/notifications.h"
#include "ui/icon.h"
#include "ui/label.h"
@ -13,6 +14,7 @@
#include "window/pagelist.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -22,24 +24,24 @@ class NotificationsPage : public Page
{
public:
explicit NotificationsPage(SettingsWindow& window)
: Page(window, "Notifications")
: Page(window, tr("Notifications"))
{
Card* card = content()->addChild<Card>(Card::Style::List);
m_doNotDisturb = card->addChild<SwitchRow>("Do Not Disturb");
m_doNotDisturb = card->addChild<SwitchRow>(tr("Do Not Disturb"));
m_doNotDisturb->setChecked(m_notifications.doNotDisturb());
m_doNotDisturb->onToggled = [this](bool checked) { m_notifications.setDoNotDisturb(checked); };
m_subscriptions.push_back(m_notifications.doNotDisturbChanged.connect(
[this] { m_doNotDisturb->setChecked(m_notifications.doNotDisturb()); }));
Section* section = content()->addChild<Section>("Applications");
Section* section = content()->addChild<Section>(tr("Applications"));
m_empty = section->addChild<Card>();
m_emptyLabel = m_empty->addChild<Label>("Applications appear here after they send a notification.");
m_emptyLabel = m_empty->addChild<Label>(tr("Applications appear here after they send a notification."));
m_emptyLabel->setWrap(true);
m_emptyLabel->setLayoutHints({.fillWidth = true});
m_list = section->addChild<Card>(Card::Style::List);
m_footnote = section->addChild<Footnote>(
"Notifications that skip the banner still go to the Notification Center. Turned off, "
"they are dropped.");
tr("Notifications that skip the banner still go to the Notification Center. Turned off, "
"they are dropped."));
m_subscriptions.push_back(m_notifications.applicationsChanged.connect([this] { updateApplications(); }));
updateApplications();
}
@ -66,7 +68,7 @@ private:
row->setLeading<ThemeIcon>(application.icon.empty() ? "application-x-executable" : application.icon, 24);
// Indexes follow Notifications::ApplicationMode.
RowComboBox* mode = row->addContent<RowComboBox>(
std::vector<std::string>{"Banners", "Notification Center only", "Off"});
std::vector<std::string>{tr("Banners"), tr("Notification Center only"), tr("Off")});
mode->setCurrentIndex(application.mode);
const std::string key = application.key;
mode->onActivated = [this, key](int index) { m_notifications.setApplicationMode(key, index); };

@ -6,6 +6,7 @@
#include <string>
#include <vector>
#include "base/i18n.h"
#include "base/signal.h"
#include "pages/power/powersettings.h"
#include "cutefish/powerprofiles/power_profiles.h"
@ -26,14 +27,23 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
using cutefish::i18n::trn;
using cutefish::i18n::trNoop;
namespace settings {
namespace {
const std::vector<int> kTimeouts{60, 120, 300, 600, 900, 1200, 1800, -1};
const std::vector<std::string> kTimeoutNames{"1 minute", "2 minutes", "5 minutes", "10 minutes",
"15 minutes", "20 minutes", "30 minutes", "Never"};
std::vector<std::string> timeoutNames()
{
std::vector<std::string> names;
for (const int timeout : kTimeouts)
names.push_back(timeout < 0 ? tr("Never") : trn("{n} minute", "{n} minutes", uint64_t(timeout / 60)));
return names;
}
int timeoutToIndex(int timeout)
{
@ -51,25 +61,25 @@ struct ProfileTile
};
constexpr ProfileTile kProfileTiles[] = {
{"power-saver", "images/powersave.svg", "Power save"},
{"balanced", "images/balanced.svg", "Balanced"},
{"performance", "images/performance.svg", "Performance"},
{"power-saver", "images/powersave.svg", trNoop("Power save")},
{"balanced", "images/balanced.svg", trNoop("Balanced")},
{"performance", "images/performance.svg", trNoop("Performance")},
};
class PowerPage : public Page
{
public:
explicit PowerPage(SettingsWindow& window)
: Page(window, "Power")
: Page(window, tr("Power"))
{
// Mode.
m_mode = content()->addChild<Section>("Mode");
m_mode = content()->addChild<Section>(tr("Mode"));
m_mode->setName("power:mode");
Card* modes = m_mode->addChild<Card>();
RowLayout* tiles = modes->addChild<RowLayout>();
tiles->setSpacing(metrics::tileSpacing);
for (const ProfileTile& tile : kProfileTiles) {
ImageSelectionItem* item = tiles->addChild<ImageSelectionItem>(dataPath(tile.picture), tile.name,
ImageSelectionItem* item = tiles->addChild<ImageSelectionItem>(dataPath(tile.picture), tr(tile.name),
SizeF{80, 80});
const std::string profile = tile.profile;
item->onClicked = [this, profile] { m_profiles.setActiveProfile(profile); };
@ -82,17 +92,17 @@ public:
// Display and sleep.
Card* idle = content()->addChild<Card>(Card::Style::List);
m_batteryRow = idle->addChild<ListRow>("Turn off display when inactive on battery");
m_batteryRow = idle->addChild<ListRow>(tr("Turn off display when inactive on battery"));
m_batteryRow->setName("power:batteryscreenoff");
m_batteryRow->setInteractive(false);
RowComboBox* batteryScreenOff = m_batteryRow->addContent<RowComboBox>(kTimeoutNames);
RowComboBox* batteryScreenOff = m_batteryRow->addContent<RowComboBox>(timeoutNames());
batteryScreenOff->setCurrentIndex(timeoutToIndex(m_settings.batteryScreenOff()));
batteryScreenOff->onActivated = [this](int index) { m_settings.setBatteryScreenOff(kTimeouts[index]); };
m_acRow = idle->addChild<ListRow>();
m_acRow->setName("power:acscreenoff");
m_acRow->setInteractive(false);
RowComboBox* acScreenOff = m_acRow->addContent<RowComboBox>(kTimeoutNames);
RowComboBox* acScreenOff = m_acRow->addContent<RowComboBox>(timeoutNames());
acScreenOff->setCurrentIndex(timeoutToIndex(m_settings.acScreenOff()));
acScreenOff->onActivated = [this](int index) { m_settings.setACScreenOff(kTimeouts[index]); };
@ -129,21 +139,21 @@ private:
std::string description;
if (active == "power-saver")
description = "Reduces performance and power consumption.";
description = tr("Reduces performance and power consumption.");
else if (active == "balanced")
description = "Standard performance and power consumption.";
description = tr("Standard performance and power consumption.");
else if (active == "performance")
description = "Higher performance, with more power consumption and heat.";
description = tr("Higher performance, with more power consumption and heat.");
m_description->setText(description);
m_description->setVisible(!description.empty());
const std::string& degraded = m_profiles.performanceDegraded();
if (degraded.find("lap-detected") != std::string::npos)
m_degraded->setText("Performance is limited because the computer is on your lap.");
m_degraded->setText(tr("Performance is limited because the computer is on your lap."));
else if (degraded.find("high-operating-temperature") != std::string::npos)
m_degraded->setText("Performance is limited because the computer is too hot.");
m_degraded->setText(tr("Performance is limited because the computer is too hot."));
else
m_degraded->setText("Performance is currently limited.");
m_degraded->setText(tr("Performance is currently limited."));
m_degraded->setVisible(active == "performance" && !degraded.empty());
}
@ -151,10 +161,10 @@ private:
{
const bool battery = hasBattery();
m_batteryRow->setVisible(battery);
m_acRow->setText(battery ? "Turn off display when inactive on power adapter"
: "Turn off display when inactive");
m_preventSleep->setText(battery ? "Prevent automatic sleeping on power adapter when the display is off"
: "Prevent automatic sleeping when the display is off");
m_acRow->setText(battery ? tr("Turn off display when inactive on power adapter")
: tr("Turn off display when inactive"));
m_preventSleep->setText(battery ? tr("Prevent automatic sleeping on power adapter when the display is off")
: tr("Prevent automatic sleeping when the display is off"));
}
powerprofiles::PowerProfiles m_profiles;

@ -8,6 +8,7 @@
#include <xkbcommon/xkbcommon-keysyms.h>
#include "base/event_loop.h"
#include "base/i18n.h"
#include "pages/shortcuts/shortcutkeys.h"
#include "ui/dialog_window.h"
#include "ui/event.h"
@ -20,6 +21,7 @@
#include "widgets/metrics.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -115,13 +117,13 @@ public:
m_prompt = m_row->addChild<Label>();
m_keys = m_row->addChild<ShortcutKeys>();
m_row->addChild<View>()->setLayoutHints({.fillWidth = true});
m_cancel = m_row->addChild<LinkLabel>("Cancel");
m_cancel = m_row->addChild<LinkLabel>(tr("Cancel"));
m_cancel->onClicked = [this] { m_dialog.stopRecording(); };
}
void setState(bool replacing, const std::vector<std::string>& parts)
{
m_prompt->setText(replacing ? "Press the new shortcut…" : "Press the shortcut…");
m_prompt->setText(replacing ? tr("Press the new shortcut…") : tr("Press the shortcut…"));
m_prompt->setVisible(parts.empty());
m_keys->setParts(parts);
m_keys->setVisible(!parts.empty());
@ -178,7 +180,7 @@ ShortcutEditDialog::ShortcutEditDialog(SettingsWindow& parent, ShortcutsModel& m
, m_action(std::move(action))
{
ColumnLayout* column = content();
m_noShortcut = column->addChild<Label>("No shortcut. Add one below.");
m_noShortcut = column->addChild<Label>(tr("No shortcut. Add one below."));
// The accelerators' rows, spaced as the dialog's own items are.
m_list = column->addChild<ColumnLayout>();
m_list->setSpacing(metrics::dialogSpacing);
@ -195,9 +197,9 @@ ShortcutEditDialog::ShortcutEditDialog(SettingsWindow& parent, ShortcutsModel& m
RowLayout* buttons = column->addChild<RowLayout>();
buttons->setSpacing(metrics::dialogButtonSpacing);
buttons->setLayoutHints({.fillWidth = true, .margins = {0, metrics::xs, 0, 0}});
m_add = buttons->addChild<DialogButton>("Add shortcut");
m_add = buttons->addChild<DialogButton>(tr("Add shortcut"));
m_add->onClicked = [this] { startRecording(std::nullopt); };
m_restore = buttons->addChild<DialogButton>("Restore default");
m_restore = buttons->addChild<DialogButton>(tr("Restore default"));
m_restore->onClicked = [this] {
const ShortcutsModel::Result result = m_model.restoreDefault(m_action);
m_message = result.message;
@ -245,15 +247,15 @@ void ShortcutEditDialog::update()
ShortcutKeys* keys = row->addChild<ShortcutKeys>(accelerator.parts);
keys->setDimmed(beingReplaced);
if (accelerator.type == "user") {
Label* custom = row->addChild<Label>("Custom");
Label* custom = row->addChild<Label>(tr("Custom"));
custom->setColor(ColorId::DisabledText);
custom->setFontSize(metrics::points(8));
}
row->addChild<View>()->setLayoutHints({.fillWidth = true});
if (!m_recording) {
LinkLabel* change = row->addChild<LinkLabel>("Change");
LinkLabel* change = row->addChild<LinkLabel>(tr("Change"));
change->onClicked = [this, accelerator] { startRecording(accelerator); };
LinkLabel* remove = row->addChild<LinkLabel>("Remove");
LinkLabel* remove = row->addChild<LinkLabel>(tr("Remove"));
remove->onClicked = [this, accelerator] {
const ShortcutsModel::Result result =
m_model.removeAccelerator(m_action, accelerator.keysym, accelerator.modifiers);

@ -8,10 +8,15 @@
#include <xkbcommon/xkbcommon.h>
#include "base/i18n.h"
#include "base/json.h"
using namespace cutefish::base;
using cutefish::i18n::format;
using cutefish::i18n::tr;
using cutefish::i18n::trc;
using cutefish::i18n::trNoop;
namespace settings {
@ -27,52 +32,53 @@ struct Layout
} // namespace
// The order here is the order on the page, like kAcceleratorLayouts.
static constexpr const char* kGeneral = "General";
static constexpr const char* kWindows = "Windows";
static constexpr const char* kDesks = "Desks";
static constexpr const char* kScreenshots = "Screenshots and Recording";
static constexpr const char* kGeneral = trNoop("General");
// TRANSLATORS: the group of keyboard shortcuts that act on windows.
static constexpr const char* kWindows = trNoop("Windows");
static constexpr const char* kDesks = trNoop("Desks");
static constexpr const char* kScreenshots = trNoop("Screenshots and Recording");
static const Layout kLayouts[] = {
{"toggle-app-list", kGeneral, "Open the launcher"},
{"lock-screen", kGeneral, "Lock the screen"},
{"toggle-clipboard-history", kGeneral, "Show the clipboard history"},
{"cycle-forward-mru", kWindows, "Switch to the next window"},
{"cycle-backward-mru", kWindows, "Switch to the previous window"},
{"toggle-overview", kWindows, "Show all windows"},
{"toggle-show-desktop", kWindows, "Show the desktop"},
{"toggle-maximized", kWindows, "Maximize or restore the window"},
{"window-minimize", kWindows, "Minimize the window"},
{"quit-application", kWindows, "Quit the application"},
{"hide-application", kWindows, "Hide the application"},
{"hide-other-applications", kWindows, "Hide other applications"},
{"toggle-fullscreen", kWindows, "Toggle full screen"},
{"window-cycle-snap-left", kWindows, "Snap the window to the left"},
{"window-cycle-snap-right", kWindows, "Snap the window to the right"},
{"close-window", kWindows, "Close the window"},
{"move-active-window-between-displays", kWindows, "Move the window to the next display"},
{"desks-activate-desk-left", kDesks, "Switch to the desk on the left"},
{"desks-activate-desk-right", kDesks, "Switch to the desk on the right"},
{"desks-move-active-item-left", kDesks, "Move the window to the desk on the left"},
{"desks-move-active-item-right", kDesks, "Move the window to the desk on the right"},
{"desks-new-desk", kDesks, "Add a desk"},
{"desks-remove-current-desk", kDesks, "Remove the desk"},
{"desks-activate-0", kDesks, "Switch to desk 1"},
{"desks-activate-1", kDesks, "Switch to desk 2"},
{"desks-activate-2", kDesks, "Switch to desk 3"},
{"desks-activate-3", kDesks, "Switch to desk 4"},
{"desks-activate-4", kDesks, "Switch to desk 5"},
{"desks-activate-5", kDesks, "Switch to desk 6"},
{"desks-activate-6", kDesks, "Switch to desk 7"},
{"desks-activate-7", kDesks, "Switch to desk 8"},
{"desks-toggle-assign-to-all-desks", kDesks, "Show the window on every desk"},
{"take-screenshot", kScreenshots, "Save a picture of the screen"},
{"take-screenshot-to-clipboard", kScreenshots, "Copy a picture of the screen"},
{"take-partial-screenshot", kScreenshots, "Save a picture of the selected area"},
{"take-partial-screenshot-to-clipboard", kScreenshots, "Copy a picture of the selected area"},
{"start-capture-mode", kScreenshots, "Screenshot and recording options"},
{"stop-screen-recording", kScreenshots, "Stop screen recording"},
{"toggle-app-list", kGeneral, trNoop("Open the launcher")},
{"lock-screen", kGeneral, trNoop("Lock the screen")},
{"toggle-clipboard-history", kGeneral, trNoop("Show the clipboard history")},
{"cycle-forward-mru", kWindows, trNoop("Switch to the next window")},
{"cycle-backward-mru", kWindows, trNoop("Switch to the previous window")},
{"toggle-overview", kWindows, trNoop("Show all windows")},
{"toggle-show-desktop", kWindows, trNoop("Show the desktop")},
{"toggle-maximized", kWindows, trNoop("Maximize or restore the window")},
{"window-minimize", kWindows, trNoop("Minimize the window")},
{"quit-application", kWindows, trNoop("Quit the application")},
{"hide-application", kWindows, trNoop("Hide the application")},
{"hide-other-applications", kWindows, trNoop("Hide other applications")},
{"toggle-fullscreen", kWindows, trNoop("Toggle full screen")},
{"window-cycle-snap-left", kWindows, trNoop("Snap the window to the left")},
{"window-cycle-snap-right", kWindows, trNoop("Snap the window to the right")},
{"close-window", kWindows, trNoop("Close the window")},
{"move-active-window-between-displays", kWindows, trNoop("Move the window to the next display")},
{"desks-activate-desk-left", kDesks, trNoop("Switch to the desk on the left")},
{"desks-activate-desk-right", kDesks, trNoop("Switch to the desk on the right")},
{"desks-move-active-item-left", kDesks, trNoop("Move the window to the desk on the left")},
{"desks-move-active-item-right", kDesks, trNoop("Move the window to the desk on the right")},
{"desks-new-desk", kDesks, trNoop("Add a desk")},
{"desks-remove-current-desk", kDesks, trNoop("Remove the desk")},
{"desks-activate-0", kDesks, trNoop("Switch to desk 1")},
{"desks-activate-1", kDesks, trNoop("Switch to desk 2")},
{"desks-activate-2", kDesks, trNoop("Switch to desk 3")},
{"desks-activate-3", kDesks, trNoop("Switch to desk 4")},
{"desks-activate-4", kDesks, trNoop("Switch to desk 5")},
{"desks-activate-5", kDesks, trNoop("Switch to desk 6")},
{"desks-activate-6", kDesks, trNoop("Switch to desk 7")},
{"desks-activate-7", kDesks, trNoop("Switch to desk 8")},
{"desks-toggle-assign-to-all-desks", kDesks, trNoop("Show the window on every desk")},
{"take-screenshot", kScreenshots, trNoop("Save a picture of the screen")},
{"take-screenshot-to-clipboard", kScreenshots, trNoop("Copy a picture of the screen")},
{"take-partial-screenshot", kScreenshots, trNoop("Save a picture of the selected area")},
{"take-partial-screenshot-to-clipboard", kScreenshots, trNoop("Copy a picture of the selected area")},
{"start-capture-mode", kScreenshots, trNoop("Screenshot and recording options")},
{"stop-screen-recording", kScreenshots, trNoop("Stop screen recording")},
};
// The keysyms of the keys the Qt version had a Qt key code for besides the
@ -135,7 +141,7 @@ void ShortcutsModel::reload()
Action action;
action.name = layout.name;
action.category = layout.category;
action.description = layout.description;
action.description = tr(layout.description);
for (const json::Value& value : it->second["accelerators"].toArray())
action.accelerators.push_back(acceleratorFromJson(value));
for (const json::Value& value : it->second["defaults"].toArray())
@ -163,41 +169,41 @@ ShortcutsModel::Accelerator ShortcutsModel::acceleratorFromJson(const json::Valu
std::string ShortcutsModel::keyDisplay(const std::string& keyName, const std::string& keyText) const
{
if (keyName == "Super_L" || keyName == "Super_R")
return m_commandKeys ? "Command" : "Meta";
static const std::map<std::string, std::string> names = {
return m_commandKeys ? trc("key", "Command") : trc("key", "Meta");
const std::map<std::string, std::string> names = {
{"Up", "↑"},
{"Down", "↓"},
{"Left", "←"},
{"Right", "→"},
{"Escape", "Esc"},
{"Return", "Enter"},
{"KP_Enter", "Enter"},
{"space", "Space"},
{"BackSpace", "Backspace"},
{"Delete", "Delete"},
{"Insert", "Insert"},
{"Home", "Home"},
{"End", "End"},
{"Prior", "Page Up"},
{"Next", "Page Down"},
{"Print", "Print Screen"},
{"Tab", "Tab"},
{"Menu", "Menu"},
{"XF86AudioRaiseVolume", "Volume Up"},
{"XF86AudioLowerVolume", "Volume Down"},
{"XF86AudioMute", "Mute"},
{"XF86MonBrightnessUp", "Brightness Up"},
{"XF86MonBrightnessDown", "Brightness Down"},
{"XF86AudioPlay", "Play/Pause"},
{"XF86AudioPause", "Pause"},
{"XF86AudioNext", "Next Track"},
{"XF86AudioPrev", "Previous Track"},
{"XF86AudioStop", "Stop"},
{"XF86Calculator", "Calculator"},
{"XF86Explorer", "File Manager"},
{"XF86Search", "Search"},
{"XF86HomePage", "Home Page"},
{"XF86Mail", "Mail"},
{"Escape", trc("key", "Esc")},
{"Return", trc("key", "Enter")},
{"KP_Enter", trc("key", "Enter")},
{"space", trc("key", "Space")},
{"BackSpace", trc("key", "Backspace")},
{"Delete", trc("key", "Delete")},
{"Insert", trc("key", "Insert")},
{"Home", trc("key", "Home")},
{"End", trc("key", "End")},
{"Prior", trc("key", "Page Up")},
{"Next", trc("key", "Page Down")},
{"Print", trc("key", "Print Screen")},
{"Tab", trc("key", "Tab")},
{"Menu", trc("key", "Menu")},
{"XF86AudioRaiseVolume", trc("key", "Volume Up")},
{"XF86AudioLowerVolume", trc("key", "Volume Down")},
{"XF86AudioMute", trc("key", "Mute")},
{"XF86MonBrightnessUp", trc("key", "Brightness Up")},
{"XF86MonBrightnessDown", trc("key", "Brightness Down")},
{"XF86AudioPlay", trc("key", "Play/Pause")},
{"XF86AudioPause", trc("key", "Pause")},
{"XF86AudioNext", trc("key", "Next Track")},
{"XF86AudioPrev", trc("key", "Previous Track")},
{"XF86AudioStop", trc("key", "Stop")},
{"XF86Calculator", trc("key", "Calculator")},
{"XF86Explorer", trc("key", "File Manager")},
{"XF86Search", trc("key", "Search")},
{"XF86HomePage", trc("key", "Home Page")},
{"XF86Mail", trc("key", "Mail")},
};
if (!keyText.empty())
return keyText;
@ -226,7 +232,7 @@ template<typename... Args>
ShortcutsModel::Result ShortcutsModel::call(const std::string& method, const Args&... args)
{
if (!m_service.isAvailable())
return {.result = "unavailable", .message = "The shortcut service is not available."};
return {.result = "unavailable", .message = tr("The shortcut service is not available.")};
const dbus::Reply reply = m_service.proxy().call(method, args...);
if (reply.isError()) {
fprintf(stderr, "Shortcut request %s failed: %s\n", method.c_str(), reply.error().message.c_str());
@ -251,32 +257,33 @@ std::string ShortcutsModel::messageFor(const std::string& result, const std::str
if (result == "success")
return {};
if (result == "conflict")
return "This shortcut is already used by \"" + conflictDescription + "\".";
return format(tr("This shortcut is already used by \"{action}\"."), {{"action", conflictDescription}});
if (result == "conflict-can-override")
return "This shortcut is used by \"" + conflictDescription + "\". Press it again to reassign it.";
return format(tr("This shortcut is used by \"{action}\". Press it again to reassign it."),
{{"action", conflictDescription}});
if (result == "missing-modifier") {
return m_commandKeys ? "Include Control, Option or Command, or use a function key."
: "Include Ctrl, Alt or Meta, or use a function key.";
return m_commandKeys ? tr("Include Control, Option or Command, or use a function key.")
: tr("Include Ctrl, Alt or Meta, or use a function key.");
}
if (result == "shift-only-not-allowed") {
return m_commandKeys ? "Shift cannot be the only modifier. Add Control, Option or Command."
: "Shift cannot be the only modifier. Add Ctrl, Alt or Meta.";
return m_commandKeys ? tr("Shift cannot be the only modifier. Add Control, Option or Command.")
: tr("Shift cannot be the only modifier. Add Ctrl, Alt or Meta.");
}
if (result == "key-not-allowed" || result == "reserved-key-not-allowed")
return "This key cannot be used in a shortcut.";
return tr("This key cannot be used in a shortcut.");
if (result == "maximum-accelerators-reached")
return "This action already has the maximum of five shortcuts.";
return tr("This action already has the maximum of five shortcuts.");
if (result == "non-meta-accelerator-warning") {
return m_commandKeys ? "Shortcuts without both Control and Command may be intercepted before applications "
"see them. Press it again to keep it."
: "Shortcuts without Meta may be intercepted before applications see them. Press it "
"again to keep it.";
return m_commandKeys ? tr("Shortcuts without both Control and Command may be intercepted before "
"applications see them. Press it again to keep it.")
: tr("Shortcuts without Meta may be intercepted before applications see them. Press it "
"again to keep it.");
}
if (result == "restore-success-with-conflicts")
return "Some default shortcuts are in use by other actions and were not restored.";
return tr("Some default shortcuts are in use by other actions and were not restored.");
if (result == "not-found")
return "This shortcut no longer exists.";
return "The shortcut could not be changed.";
return tr("This shortcut no longer exists.");
return tr("The shortcut could not be changed.");
}
ShortcutsModel::Result ShortcutsModel::addAccelerator(const std::string& action, uint32_t keycode, uint32_t keysym,
@ -320,13 +327,13 @@ std::vector<std::string> ShortcutsModel::modifierParts(uint32_t modifiers) const
// bar, Alt is Option and Ctrl is Control.
std::vector<std::string> parts;
if (modifiers & ControlDown)
parts.push_back(m_commandKeys ? "Control" : "Ctrl");
parts.push_back(m_commandKeys ? trc("key", "Control") : trc("key", "Ctrl"));
if (modifiers & AltDown)
parts.push_back(m_commandKeys ? "Option" : "Alt");
parts.push_back(m_commandKeys ? trc("key", "Option") : trc("key", "Alt"));
if (modifiers & ShiftDown)
parts.push_back("Shift");
parts.push_back(trc("key", "Shift"));
if (modifiers & MetaDown)
parts.push_back(m_commandKeys ? "Command" : "Meta");
parts.push_back(m_commandKeys ? trc("key", "Command") : trc("key", "Meta"));
return parts;
}

@ -7,6 +7,7 @@
#include <vector>
#include "base/event_loop.h"
#include "base/i18n.h"
#include "pages/shortcuts/shortcuteditdialog.h"
#include "pages/shortcuts/shortcutkeys.h"
#include "pages/shortcuts/shortcutsmodel.h"
@ -24,6 +25,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -79,7 +81,7 @@ public:
explicit ShortcutRow(const ShortcutsModel::Action& action)
: ListRow(action.description)
{
m_none = addContent<Label>("None");
m_none = addContent<Label>(tr("None"));
m_none->setVisible(action.accelerators.empty());
m_keys = addContent<AcceleratorStack>();
// Air above and below, so a stack of two makes the row grow.
@ -125,17 +127,17 @@ class ShortcutsPage : public Page
{
public:
explicit ShortcutsPage(SettingsWindow& window)
: Page(window, "Keyboard Shortcuts")
: Page(window, tr("Keyboard Shortcuts"))
{
m_unavailable = content()->addChild<NoteCard>(
"Keyboard shortcuts are managed by the Cutefish compositor, which is not running.");
tr("Keyboard shortcuts are managed by the Cutefish compositor, which is not running."));
m_intro = content()->addChild<Footnote>();
for (const std::string& category : ShortcutsModel::categories()) {
Section* section = content()->addChild<Section>(category);
Section* section = content()->addChild<Section>(tr(category.c_str()));
m_sections.push_back(section);
m_cards.push_back(section->addChild<Card>(Card::Style::List));
}
m_restoreAll = content()->addChild<StandardButton>("Restore all defaults");
m_restoreAll = content()->addChild<StandardButton>(tr("Restore all defaults"));
m_restoreAll->setName("shortcuts:restoreAll");
m_restoreAll->setLayoutHints({.fillWidth = true});
m_restoreAll->onClicked = [this] { confirmRestoreAll(); };
@ -151,10 +153,10 @@ private:
m_unavailable->setVisible(!available);
m_intro->setVisible(available);
m_intro->setText(m_model.commandKeys()
? "Click a system shortcut to change it. Shortcuts with both Control and Command are "
"the safest choice: applications never get them."
: "Click a system shortcut to change it. Shortcuts with Meta are the safest choice: "
"applications never use them.");
? tr("Click a system shortcut to change it. Shortcuts with both Control and Command "
"are the safest choice: applications never get them.")
: tr("Click a system shortcut to change it. Shortcuts with Meta are the safest "
"choice: applications never use them."));
const std::vector<std::string> categories = ShortcutsModel::categories();
for (size_t i = 0; i < categories.size(); ++i) {
m_sections[i]->setVisible(available);
@ -189,11 +191,11 @@ private:
void confirmRestoreAll()
{
m_confirm = std::make_unique<ConfirmDialog>(&settingsWindow().window(), "Restore all defaults",
"Every shortcut goes back to its default. Your changes will be "
"lost.");
m_confirm->setCancelText("Cancel");
m_confirm->setConfirmText("Restore");
m_confirm = std::make_unique<ConfirmDialog>(&settingsWindow().window(), tr("Restore all defaults"),
tr("Every shortcut goes back to its default. Your changes will "
"be lost."));
m_confirm->setCancelText(tr("Cancel"));
m_confirm->setConfirmText(tr("Restore"));
m_confirm->setDestructive(true);
m_confirm->onAccepted = [this] { m_model.restoreAllDefaults(); };
m_confirm->show();

@ -7,6 +7,7 @@
#include <string>
#include <vector>
#include "base/i18n.h"
#include "cutefish/audio/audio_model.h"
#include "cutefish/audio/backend.h"
#include "cutefish/audio/volume_monitor.h"
@ -27,6 +28,9 @@
using namespace cutefish;
using namespace cutefish::audio;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
using cutefish::i18n::trc;
namespace settings {
@ -34,17 +38,17 @@ namespace settings {
static std::string deviceType(const AudioObject& device)
{
if (device.isVirtualDevice())
return "Virtual";
return tr("Virtual");
const std::string bus = device.bus();
if (bus == "bluetooth")
return "Bluetooth";
return tr("Bluetooth");
if (bus == "usb")
return "USB";
const std::vector<AudioObject::Choice> ports = device.ports();
const int active = device.activePortIndex();
if (active >= 0 && active < int(ports.size()) && ports[active].name.starts_with("hdmi"))
return "HDMI";
return "Built-in";
return tr("Built-in");
}
static bool hasFrontPair(const AudioObject& device)
@ -140,14 +144,14 @@ class SoundPage : public Page
{
public:
explicit SoundPage(SettingsWindow& window)
: Page(window, "Sound")
: Page(window, tr("Sound"))
, m_backend(Backend::shared())
, m_outputs(AudioObject::Sink, m_backend)
, m_inputs(AudioObject::Source, m_backend)
, m_monitor(m_backend)
{
m_connecting = content()->addChild<Card>();
Label* connecting = m_connecting->addChild<Label>("Connecting to the audio service…");
Label* connecting = m_connecting->addChild<Label>(tr("Connecting to the audio service…"));
connecting->setWrap(true);
connecting->setLayoutHints({.fillWidth = true});
@ -155,13 +159,13 @@ public:
m_error = m_errorCard->addChild<Label>();
m_error->setWrap(true);
m_error->setLayoutHints({.fillWidth = true});
Button* dismiss = m_errorCard->addChild<Button>("Dismiss");
Button* dismiss = m_errorCard->addChild<Button>(tr("Dismiss"));
dismiss->onClicked = [this] { setOperationError({}); };
ColumnLayout* devices = content()->addChild<ColumnLayout>();
devices->setSpacing(metrics::l);
devices->setLayoutHints({.fillWidth = true});
m_tabs = devices->addChild<SegmentedControl>(std::vector<std::string>{"Output", "Input"});
m_tabs = devices->addChild<SegmentedControl>(std::vector<std::string>{tr("Output"), tr("Input")});
m_tabs->setName("tabs");
m_tabs->setLayoutHints({.fillWidth = true});
m_tabs->onCurrentChanged = [this](int) {
@ -175,7 +179,7 @@ public:
m_noDevices->setEnabled(false);
m_sliders = devices->addChild<Card>(Card::Style::List);
m_outputVolume = m_sliders->addChild<SliderRow>("Output volume");
m_outputVolume = m_sliders->addChild<SliderRow>(tr("Output volume"));
m_outputVolume->setIcons("audio-volume-zero", "audio-volume-high");
m_outputVolume->setRange(AudioObject::MinimalVolume, AudioObject::NormalVolume);
m_outputVolume->setStepSize(0.01);
@ -186,16 +190,16 @@ public:
device->setMuted(value == 0);
}
};
m_balance = m_sliders->addChild<SliderRow>("Balance");
m_balance = m_sliders->addChild<SliderRow>(tr("Balance"));
m_balance->setName("balance");
m_balance->setCaptions("Left", "Right");
m_balance->setCaptions(trc("balance", "Left"), trc("balance", "Right"));
m_balance->setRange(-1, 1);
m_balance->setStepSize(0.02);
m_balance->onMoved = [this](double value) {
if (const auto device = m_outputs.defaultNode())
setBalance(*device, value);
};
m_inputVolume = m_sliders->addChild<SliderRow>("Input volume");
m_inputVolume = m_sliders->addChild<SliderRow>(tr("Input volume"));
m_inputVolume->setRange(AudioObject::MinimalVolume, AudioObject::NormalVolume);
m_inputVolume->setStepSize(0.01);
m_inputVolume->onMoved = [this](double value) {
@ -204,7 +208,7 @@ public:
device->setMuted(false);
}
};
m_inputLevel = m_sliders->addChild<SliderRow>("Input level");
m_inputLevel = m_sliders->addChild<SliderRow>(tr("Input level"));
m_inputLevel->setMeterLevel(0);
m_subscriptions.push_back(m_backend->connectedChanged.connect([this] {
@ -235,7 +239,7 @@ private:
{
m_operationError = message;
m_errorCard->setVisible(!message.empty());
m_error->setText("Could not apply the audio change: " + message);
m_error->setText(format(tr("Could not apply the audio change: {error}"), {{"error", message}}));
}
void rebuildDevices()
@ -267,7 +271,7 @@ private:
const bool inputs = showInputs();
const bool connected = m_backend->isConnected();
m_connecting->setVisible(!connected);
m_noDevices->setText(inputs ? "No input devices" : "No output devices");
m_noDevices->setText(inputs ? tr("No input devices") : tr("No output devices"));
m_noDevices->setVisible(connected && model().count() == 0);
const std::shared_ptr<AudioObject> output = m_outputs.defaultNode();

@ -3,6 +3,7 @@
#include <cmath>
#include "base/i18n.h"
#include "pages/input/availability.h"
#include "pages/touchpad/touchpad.h"
#include "ui/slider.h"
@ -15,6 +16,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -33,40 +35,40 @@ class TouchpadPage : public Page
{
public:
explicit TouchpadPage(SettingsWindow& window)
: Page(window, "Touchpad")
: Page(window, tr("Touchpad"))
{
m_deviceCard = content()->addChild<Card>(Card::Style::List);
m_deviceCard->setName("touchpad:device");
m_enabled = m_deviceCard->addChild<SwitchRow>("Enable");
m_enabled = m_deviceCard->addChild<SwitchRow>(tr("Enable"));
m_enabled->onToggled = [this](bool checked) { m_touchpad.setEnabled(checked); };
m_disableWithMouse = m_deviceCard->addChild<SwitchRow>("Disable when a mouse is connected");
m_disableWithMouse = m_deviceCard->addChild<SwitchRow>(tr("Disable when a mouse is connected"));
m_disableWithMouse->onToggled = [this](bool checked) { m_touchpad.setDisableWithMouse(checked); };
m_disableWhileTyping = m_deviceCard->addChild<SwitchRow>("Disable while typing");
m_disableWhileTyping = m_deviceCard->addChild<SwitchRow>(tr("Disable while typing"));
m_disableWhileTyping->onToggled = [this](bool checked) { m_touchpad.setDisableWhileTyping(checked); };
m_gestureCard = content()->addChild<Card>(Card::Style::List);
m_gestureCard->setName("touchpad:gestures");
m_tapToClick = m_gestureCard->addChild<SwitchRow>("Tap to click");
m_tapToClick = m_gestureCard->addChild<SwitchRow>(tr("Tap to click"));
m_tapToClick->onToggled = [this](bool checked) { m_touchpad.setTapToClick(checked); };
m_tapDragging = m_gestureCard->addChild<SwitchRow>("Tap and drag");
m_tapDragging = m_gestureCard->addChild<SwitchRow>(tr("Tap and drag"));
m_tapDragging->setName("touchpad:tapDragging");
m_tapDragging->onToggled = [this](bool checked) { m_touchpad.setTapDragging(checked); };
m_secondaryClick = m_gestureCard->addChild<ListRow>("Secondary click");
m_secondaryClick = m_gestureCard->addChild<ListRow>(tr("Secondary click"));
m_secondaryClick->setName("touchpad:secondaryClick");
m_secondaryClick->setInteractive(false);
m_secondaryClickMethod = m_secondaryClick->addContent<RowComboBox>(
std::vector<std::string>{"Two-finger press", "Bottom-right corner press"});
std::vector<std::string>{tr("Two-finger press"), tr("Bottom-right corner press")});
m_secondaryClickMethod->onActivated = [this](int index) { m_touchpad.setTwoFingerSecondaryClick(index == 0); };
m_naturalScroll = m_gestureCard->addChild<SwitchRow>("Natural scrolling");
m_naturalScroll = m_gestureCard->addChild<SwitchRow>(tr("Natural scrolling"));
m_naturalScroll->onToggled = [this](bool checked) { m_touchpad.setNaturalScroll(checked); };
m_pointerSpeed = m_gestureCard->addChild<SliderRow>("Pointer speed");
m_pointerSpeed = m_gestureCard->addChild<SliderRow>(tr("Pointer speed"));
m_pointerSpeed->setRange(0, kPointerSteps);
m_pointerSpeed->setStepSize(1);
m_pointerSpeed->slider()->setSnapMode(Slider::SnapMode::SnapAlways);
m_pointerSpeed->onMoved = [this](double value) {
m_touchpad.setPointerAcceleration(std::round(value) * 2 / kPointerSteps - 1);
};
m_scrollSpeed = m_gestureCard->addChild<SliderRow>("Scroll speed");
m_scrollSpeed = m_gestureCard->addChild<SliderRow>(tr("Scroll speed"));
m_scrollSpeed->setName("touchpad:scrollSpeed");
m_scrollSpeed->setRange(0, kScrollSteps);
m_scrollSpeed->setStepSize(1);

@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include "base/i18n.h"
#include "pages/input/availability.h"
#include "pages/input/compositorservice.h"
#include "widgets/card.h"
@ -14,6 +15,7 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -48,7 +50,7 @@ class TouchscreenPage : public Page
{
public:
explicit TouchscreenPage(SettingsWindow& window)
: Page(window, "Touchscreen")
: Page(window, tr("Touchscreen"))
{
m_card = content()->addChild<Card>(Card::Style::List);
m_changed = m_touchscreen.changed().connect([this] { update(); });

@ -7,6 +7,7 @@
#include <cctype>
#include <cmath>
#include "base/i18n.h"
#include "ui/color_provider.h"
#include "ui/icon.h"
#include "ui/image_view.h"
@ -20,6 +21,8 @@
#include "widgets/metrics.h"
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -302,7 +305,7 @@ public:
, m_addTile(addTile)
{
if (m_addTile) {
m_addLabel = addChild<Label>("Add wallpaper");
m_addLabel = addChild<Label>(tr("Add wallpaper"));
return;
}
m_placeholder = addChild<Icon>("image", 32);
@ -613,8 +616,9 @@ void WallpaperGrid::updateTiles()
void WallpaperGrid::updateHeader()
{
m_count->setVisible(!m_wallpapers.empty());
m_count->setText("(" + std::to_string(m_wallpapers.size()) + ")");
m_showAll->setText(m_expanded ? "Show less" : "Show all");
// TRANSLATORS: the number of wallpapers after a section's title, such as "(12)".
m_count->setText(format(tr("({count})"), {{"count", std::to_string(m_wallpapers.size())}}));
m_showAll->setText(m_expanded ? tr("Show less") : tr("Show all"));
m_title->requestLayout();
}

@ -5,6 +5,7 @@
#include <string>
#include <vector>
#include "base/i18n.h"
#include "cutefish/appearance/appearance.h"
#include "pages/wallpaper/wallpapergrid.h"
#include "pages/wallpaper/wallpapers.h"
@ -21,6 +22,8 @@
using namespace cutefish;
using namespace cutefish::ui;
using cutefish::i18n::format;
using cutefish::i18n::tr;
namespace settings {
@ -30,12 +33,12 @@ class WallpaperPage : public Page
{
public:
explicit WallpaperPage(SettingsWindow& window)
: Page(window, "Wallpaper")
: Page(window, tr("Wallpaper"))
{
Card* card = content()->addChild<Card>();
card->setSpacing(metrics::sectionSpacing);
m_user = card->addChild<WallpaperGrid>("Your wallpapers");
m_user = card->addChild<WallpaperGrid>(tr("Your wallpapers"));
m_user->setName("wallpaper:user");
m_user->setLayoutHints({.fillWidth = true});
m_user->setShowAddWallpaper(true);
@ -44,7 +47,7 @@ public:
m_user->onAddRequested = [this](PointF position) { m_addMenu.popup(&settingsWindow().window(), position); };
m_user->onRemoveRequested = [this](const std::string& path) { m_appearance.removeUserWallpaper(path); };
m_system = card->addChild<WallpaperGrid>("System");
m_system = card->addChild<WallpaperGrid>(tr("System"));
m_system->setName("wallpaper:system");
m_system->setLayoutHints({.fillWidth = true});
m_system->setWallpapers(wallpapers::system());
@ -57,7 +60,7 @@ public:
ColumnLayout* colors = card->addChild<ColumnLayout>();
colors->setSpacing(metrics::cardSpacing);
colors->setLayoutHints({.fillWidth = true});
colors->addChild<Label>("Colors");
colors->addChild<Label>(tr("Colors"));
m_swatches = colors->addChild<ColorSwatches>();
m_swatches->setName("wallpaper:colors");
m_swatches->setLayoutHints({.fillWidth = true});
@ -66,8 +69,8 @@ public:
m_appearance.setBackgroundColor(color);
};
m_addMenu.addItem("Choose file…", [this] { chooseFile(); });
m_addMenu.addItem("Choose folder…", [this] { chooseFolder(); });
m_addMenu.addItem(tr("Choose file…"), [this] { chooseFile(); });
m_addMenu.addItem(tr("Choose folder…"), [this] { chooseFolder(); });
m_changed = m_appearance.changed.connect([this] { update(); });
m_folderImagesChanged = m_appearance.wallpaperFolderImagesChanged.connect([this](const std::string& folder) {
@ -90,9 +93,10 @@ private:
void chooseFile()
{
m_chooser = std::make_unique<FileChooser>(&settingsWindow().window());
m_chooser->setTitle("Add wallpaper");
m_chooser->setFilters(
{FileChooser::Filter::fromNameFilter("Image files (*.jpg *.jpeg *.png *.webp *.bmp *.tif *.tiff *.avif)")});
m_chooser->setTitle(tr("Add wallpaper"));
m_chooser->setFilters({FileChooser::Filter::fromNameFilter(
format(tr("Image files ({patterns})"),
{{"patterns", "*.jpg *.jpeg *.png *.webp *.bmp *.tif *.tiff *.avif"}}))});
m_chooser->onAccepted = [this](const std::vector<std::string>& paths, const std::string&) {
if (!paths.empty())
select(paths.front());
@ -104,7 +108,7 @@ private:
{
m_chooser = std::make_unique<FileChooser>(&settingsWindow().window());
m_chooser->setMode(FileChooser::Mode::OpenFolder);
m_chooser->setTitle("Add Wallpaper Folder");
m_chooser->setTitle(tr("Add Wallpaper Folder"));
m_chooser->onAccepted = [this](const std::vector<std::string>& paths, const std::string&) {
if (!paths.empty())
m_appearance.addWallpaperFolder(paths.front());

@ -3,16 +3,18 @@
#include "widgets/searchfield.h"
#include "base/i18n.h"
#include "widgets/metrics.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
static constexpr double kHeight = 28;
SearchField::SearchField()
: cutefish::ui::SearchField("Search")
: cutefish::ui::SearchField(tr("Search"))
{
setIconMetrics(metrics::s, metrics::xs, metrics::xs);
setLayoutHints({.fillWidth = true});

@ -7,6 +7,7 @@
#include <chrono>
#include <cmath>
#include "base/i18n.h"
#include "base/timer.h"
#include "ui/color_provider.h"
#include "ui/icon.h"
@ -17,6 +18,7 @@
#include "widgets/metrics.h"
using namespace cutefish::ui;
using cutefish::i18n::trc;
namespace settings {
@ -39,8 +41,8 @@ public:
{
m_left = addChild<Label>();
m_right = addChild<Label>();
m_leftReference = addChild<Label>("Left");
m_rightReference = addChild<Label>("Right");
m_leftReference = addChild<Label>(trc("balance", "Left"));
m_rightReference = addChild<Label>(trc("balance", "Right"));
for (Label* label : {m_left, m_right, m_leftReference, m_rightReference}) {
label->setFontSize(kCaptionPixelSize);
// Sized to their text, which rounding must not cut.

@ -5,6 +5,7 @@
#include <algorithm>
#include "base/i18n.h"
#include "ui/painter.h"
#include "ui/cell_layout.h"
#include "ui/theme.h"
@ -13,6 +14,7 @@
#include "window/settingswindow.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -74,7 +76,7 @@ void Page::paint(Painter& painter)
PendingPage::PendingPage(SettingsWindow& window, std::string title)
: Page(window, std::move(title))
{
content()->addChild<Footnote>("This page is not ported yet.");
content()->addChild<Footnote>(tr("This page is not ported yet."));
}
} // namespace settings

@ -5,9 +5,11 @@
#include <map>
#include "base/i18n.h"
#include "window/page.h"
using namespace cutefish::ui;
using cutefish::i18n::trNoop;
namespace settings {
@ -18,83 +20,93 @@ static std::vector<PageInfo> makePages()
// Sidebar pages, in sidebar order. Ethernet and Bluetooth join it once
// their hardware is found, after Wi-Fi.
add({.id = "wlan", .name = "wlan", .title = "Wi-Fi", .icon = "network-wireless-connected-100",
.iconColor = Color::rgb(0x0067FF), .category = "Network",
.keywords = "wireless, WLAN, network, internet, password, DNS, IP address"});
add({.id = "ethernet", .name = "ethernet", .title = "Ethernet", .icon = "network.svg",
.iconColor = Color::rgb(0x0067FF), .category = "Network",
.keywords = "wired, LAN, network, internet, cable, IP address", .initiallyAvailable = false});
add({.id = "bluetooth", .name = "bluetooth", .title = "Bluetooth", .icon = "bluetooth.svg",
.iconColor = Color::rgb(0x0067FF), .category = "Network",
.keywords = "wireless, devices, pairing, headphones", .initiallyAvailable = false});
add({.id = "network", .name = "network", .title = "Network", .icon = "globe.svg",
.iconColor = Color::rgb(0x0067FF), .category = "Network",
.keywords = "VPN, proxy, hotspot, tethering, internet"});
add({.id = "appearance", .name = "appearance", .title = "Appearance", .icon = "appearance.svg",
.iconColor = Color::rgb(0x03B4CB), .category = "Desktop",
.keywords = "theme, dark mode, light mode, accent color, dim wallpaper"});
add({.id = "background", .name = "background", .title = "Wallpaper", .icon = "wallpaper.svg",
.iconColor = Color::rgb(0x34B4A7), .category = "Desktop",
.keywords = "background, desktop picture, image"});
add({.id = "dock", .name = "dock", .title = "Dock", .icon = "dock.svg", .iconColor = Color::rgb(0x8585FC),
.category = "Desktop", .keywords = "taskbar, position, size, auto hide, glass effect"});
add({.id = "notifications", .name = "notifications", .title = "Notifications", .icon = "notifications.svg",
.iconColor = Color::rgb(0xF16884), .category = "Desktop", .keywords = "do not disturb, banners, alerts"});
add({.id = "display", .name = "display", .title = "Display", .icon = "display.svg",
.iconColor = Color::rgb(0x0087ED), .category = "Devices",
.keywords = "monitor, screen, resolution, refresh rate, scaling, brightness, rotation"});
add({.id = "sound", .name = "sound", .title = "Sound", .icon = "sound.svg", .iconColor = Color::rgb(0xF16884),
.category = "Devices",
.keywords = "audio, volume, speakers, headphones, microphone, input, output"});
add({.id = "keyboard", .name = "keyboard", .title = "Keyboard", .icon = "keyboard.svg",
.iconColor = Color::rgb(0x5E6AD2), .category = "Devices",
.keywords = "input method, key repeat, keyboard style"});
add({.id = "mouse", .name = "mouse", .title = "Mouse", .icon = "cursor.svg", .iconColor = Color::rgb(0x3385FF),
.category = "Devices", .keywords = "pointer speed, acceleration, natural scrolling, left-handed"});
add({.id = "touchpad", .name = "touchpad", .title = "Touchpad", .icon = "touchpad.svg",
.iconColor = Color::rgb(0x999999), .category = "Devices",
.keywords = "trackpad, tap to click, natural scrolling, secondary click, pointer speed"});
add({.id = "touchscreen", .name = "touchscreen", .title = "Touchscreen", .icon = "touchscreen.svg",
.iconColor = Color::rgb(0x999999), .category = "Devices", .keywords = "touch, touch screen, touch panel"});
add({.id = "accounts", .name = "accounts", .title = "Users", .icon = "accounts.svg",
.iconColor = Color::rgb(0xDA7C43), .category = "System",
.keywords = "accounts, password, login, administrator, avatar"});
add({.id = "language", .name = "language", .title = "Language", .icon = "language.svg",
.iconColor = Color::rgb(0x20A7FF), .category = "System", .keywords = "region, locale, system language"});
add({.id = "datetime", .name = "datetime", .title = "Date & Time", .icon = "datetime.svg",
.iconColor = Color::rgb(0x418CFF), .category = "System",
.keywords = "clock, time zone, 24-hour time, NTP, sync"});
add({.id = "defaultapps", .name = "defaultapps", .title = "Default Applications", .icon = "defaultapps.svg",
.iconColor = Color::rgb(0x418CFF), .category = "System", .keywords = "web browser, email client, terminal"});
add({.id = "battery", .name = "battery", .title = "Battery", .icon = "battery.svg",
.iconColor = Color::rgb(0x2EC347), .category = "System",
.keywords = "charge, charging limit, battery health, capacity, history, percentage"});
add({.id = "power", .name = "power", .title = "Power", .icon = "power.svg", .iconColor = Color::rgb(0xFE8433),
.category = "System", .keywords = "energy, performance, power saving, sleep, turn off display"});
add({.id = "about", .name = "about", .title = "About", .icon = "about.svg", .iconColor = Color::rgb(0x24A7FD),
.category = "System", .keywords = "system, version, kernel, processor, memory, storage, hardware"});
add({.id = "wlan", .name = "wlan", .title = trNoop("Wi-Fi"), .icon = "network-wireless-connected-100",
.iconColor = Color::rgb(0x0067FF), .category = trNoop("Network"),
.keywords = trNoop("wireless, WLAN, network, internet, password, DNS, IP address")});
add({.id = "ethernet", .name = "ethernet", .title = trNoop("Ethernet"), .icon = "network.svg",
.iconColor = Color::rgb(0x0067FF), .category = trNoop("Network"),
.keywords = trNoop("wired, LAN, network, internet, cable, IP address"), .initiallyAvailable = false});
add({.id = "bluetooth", .name = "bluetooth", .title = trNoop("Bluetooth"), .icon = "bluetooth.svg",
.iconColor = Color::rgb(0x0067FF), .category = trNoop("Network"),
.keywords = trNoop("wireless, devices, pairing, headphones"), .initiallyAvailable = false});
add({.id = "network", .name = "network", .title = trNoop("Network"), .icon = "globe.svg",
.iconColor = Color::rgb(0x0067FF), .category = trNoop("Network"),
.keywords = trNoop("VPN, proxy, hotspot, tethering, internet")});
add({.id = "appearance", .name = "appearance", .title = trNoop("Appearance"), .icon = "appearance.svg",
.iconColor = Color::rgb(0x03B4CB), .category = trNoop("Desktop"),
.keywords = trNoop("theme, dark mode, light mode, accent color, dim wallpaper")});
add({.id = "background", .name = "background", .title = trNoop("Wallpaper"), .icon = "wallpaper.svg",
.iconColor = Color::rgb(0x34B4A7), .category = trNoop("Desktop"),
.keywords = trNoop("background, desktop picture, image")});
add({.id = "dock", .name = "dock", .title = trNoop("Dock"), .icon = "dock.svg",
.iconColor = Color::rgb(0x8585FC), .category = trNoop("Desktop"),
.keywords = trNoop("taskbar, position, size, auto hide, glass effect")});
add({.id = "notifications", .name = "notifications", .title = trNoop("Notifications"),
.icon = "notifications.svg", .iconColor = Color::rgb(0xF16884), .category = trNoop("Desktop"),
.keywords = trNoop("do not disturb, banners, alerts")});
add({.id = "display", .name = "display", .title = trNoop("Display"), .icon = "display.svg",
.iconColor = Color::rgb(0x0087ED), .category = trNoop("Devices"),
.keywords = trNoop("monitor, screen, resolution, refresh rate, scaling, brightness, rotation")});
add({.id = "sound", .name = "sound", .title = trNoop("Sound"), .icon = "sound.svg",
.iconColor = Color::rgb(0xF16884), .category = trNoop("Devices"),
.keywords = trNoop("audio, volume, speakers, headphones, microphone, input, output")});
add({.id = "keyboard", .name = "keyboard", .title = trNoop("Keyboard"), .icon = "keyboard.svg",
.iconColor = Color::rgb(0x5E6AD2), .category = trNoop("Devices"),
.keywords = trNoop("input method, key repeat, keyboard style")});
add({.id = "mouse", .name = "mouse", .title = trNoop("Mouse"), .icon = "cursor.svg",
.iconColor = Color::rgb(0x3385FF), .category = trNoop("Devices"),
.keywords = trNoop("pointer speed, acceleration, natural scrolling, left-handed")});
add({.id = "touchpad", .name = "touchpad", .title = trNoop("Touchpad"), .icon = "touchpad.svg",
.iconColor = Color::rgb(0x999999), .category = trNoop("Devices"),
.keywords = trNoop("trackpad, tap to click, natural scrolling, secondary click, pointer speed")});
add({.id = "touchscreen", .name = "touchscreen", .title = trNoop("Touchscreen"), .icon = "touchscreen.svg",
.iconColor = Color::rgb(0x999999), .category = trNoop("Devices"),
.keywords = trNoop("touch, touch screen, touch panel")});
add({.id = "accounts", .name = "accounts", .title = trNoop("Users"), .icon = "accounts.svg",
.iconColor = Color::rgb(0xDA7C43), .category = trNoop("System"),
.keywords = trNoop("accounts, password, login, administrator, avatar")});
add({.id = "language", .name = "language", .title = trNoop("Language"), .icon = "language.svg",
.iconColor = Color::rgb(0x20A7FF), .category = trNoop("System"),
.keywords = trNoop("region, locale, system language")});
add({.id = "datetime", .name = "datetime", .title = trNoop("Date & Time"), .icon = "datetime.svg",
.iconColor = Color::rgb(0x418CFF), .category = trNoop("System"),
.keywords = trNoop("clock, time zone, 24-hour time, NTP, sync")});
add({.id = "defaultapps", .name = "defaultapps", .title = trNoop("Default Applications"),
.icon = "defaultapps.svg", .iconColor = Color::rgb(0x418CFF), .category = trNoop("System"),
.keywords = trNoop("web browser, email client, terminal")});
add({.id = "battery", .name = "battery", .title = trNoop("Battery"), .icon = "battery.svg",
.iconColor = Color::rgb(0x2EC347), .category = trNoop("System"),
.keywords = trNoop("charge, charging limit, battery health, capacity, history, percentage")});
add({.id = "power", .name = "power", .title = trNoop("Power"), .icon = "power.svg",
.iconColor = Color::rgb(0xFE8433), .category = trNoop("System"),
.keywords = trNoop("energy, performance, power saving, sleep, turn off display")});
add({.id = "about", .name = "about", .title = trNoop("About"), .icon = "about.svg",
.iconColor = Color::rgb(0x24A7FD), .category = trNoop("System"),
.keywords = trNoop("system, version, kernel, processor, memory, storage, hardware")});
// Sub pages, opened from inside a sidebar page; search lists them after it.
add({.id = "vpn", .name = "vpn", .title = "VPN", .icon = "network-vpn", .iconColor = Color::rgb(0x48484C),
.keywords = "VPN, WireGuard, OpenVPN, tunnel, network", .parentId = "network"});
add({.id = "proxy", .name = "proxy", .title = "Proxy", .icon = "proxy.svg", .iconColor = Color::rgb(0x0067FF),
.keywords = "HTTP, HTTPS, SOCKS, FTP, PAC, network", .parentId = "network"});
add({.id = "hotspot", .name = "hotspot", .title = "Hotspot", .icon = "network-wireless-hotspot",
.iconColor = Color::rgb(0x2EBC6B), .keywords = "tethering, share internet, access point, Wi-Fi, wireless",
add({.id = "vpn", .name = "vpn", .title = trNoop("VPN"), .icon = "network-vpn",
.iconColor = Color::rgb(0x48484C), .keywords = trNoop("VPN, WireGuard, OpenVPN, tunnel, network"),
.parentId = "network"});
add({.id = "fonts", .title = "Fonts", .icon = "fonts.svg", .iconColor = Color::rgb(0xFE8433),
.keywords = "general font, fixed font, font size, anti-aliasing, hinting", .parentId = "appearance"});
add({.id = "icontheme", .title = "Icon Theme", .icon = "icontheme.svg", .iconColor = Color::rgb(0x2EC347),
.keywords = "icons", .parentId = "appearance"});
add({.id = "cursortheme", .title = "Cursor Style", .icon = "cursor.svg", .iconColor = Color::rgb(0x3385FF),
.keywords = "cursor theme, cursor size, pointer", .parentId = "appearance"});
add({.id = "keyboardlayout", .title = "Keyboard Layout", .icon = "keyboard.svg",
.iconColor = Color::rgb(0x5E6AD2), .keywords = "input source", .parentId = "keyboard"});
add({.id = "hotcorners", .name = "hotcorners", .title = "Hot Corners", .icon = "dock.svg",
.iconColor = Color::rgb(0x8585FC), .keywords = "screen corners, show desktop, show all windows",
add({.id = "proxy", .name = "proxy", .title = trNoop("Proxy"), .icon = "proxy.svg",
.iconColor = Color::rgb(0x0067FF), .keywords = trNoop("HTTP, HTTPS, SOCKS, FTP, PAC, network"),
.parentId = "network"});
add({.id = "hotspot", .name = "hotspot", .title = trNoop("Hotspot"), .icon = "network-wireless-hotspot",
.iconColor = Color::rgb(0x2EBC6B),
.keywords = trNoop("tethering, share internet, access point, Wi-Fi, wireless"), .parentId = "network"});
add({.id = "fonts", .title = trNoop("Fonts"), .icon = "fonts.svg", .iconColor = Color::rgb(0xFE8433),
.keywords = trNoop("general font, fixed font, font size, anti-aliasing, hinting"), .parentId = "appearance"});
add({.id = "icontheme", .title = trNoop("Icon Theme"), .icon = "icontheme.svg", .iconColor = Color::rgb(0x2EC347),
.keywords = trNoop("icons"), .parentId = "appearance"});
add({.id = "cursortheme", .title = trNoop("Cursor Style"), .icon = "cursor.svg", .iconColor = Color::rgb(0x3385FF),
.keywords = trNoop("cursor theme, cursor size, pointer"), .parentId = "appearance"});
add({.id = "keyboardlayout", .title = trNoop("Keyboard Layout"), .icon = "keyboard.svg",
.iconColor = Color::rgb(0x5E6AD2), .keywords = trNoop("input source"), .parentId = "keyboard"});
add({.id = "hotcorners", .name = "hotcorners", .title = trNoop("Hot Corners"), .icon = "dock.svg",
.iconColor = Color::rgb(0x8585FC), .keywords = trNoop("screen corners, show desktop, show all windows"),
.parentId = "dock"});
add({.id = "shortcuts", .title = "Keyboard Shortcuts", .icon = "keyboard.svg", .iconColor = Color::rgb(0x5E6AD2),
.keywords = "hotkeys, key bindings", .parentId = "keyboard"});
add({.id = "shortcuts", .title = trNoop("Keyboard Shortcuts"), .icon = "keyboard.svg",
.iconColor = Color::rgb(0x5E6AD2), .keywords = trNoop("hotkeys, key bindings"), .parentId = "keyboard"});
return pages;
}

@ -8,6 +8,7 @@
#include <cmath>
#include "base/event_loop.h"
#include "base/i18n.h"
#include "base/timer.h"
#include "ui/application.h"
#include "ui/color_provider.h"
@ -27,6 +28,7 @@
#include "window/sidebar.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -200,7 +202,7 @@ SettingsWindow::SettingsWindow()
: m_window(std::make_unique<Window>())
, m_revealTimer(std::make_unique<cutefish::base::Timer>())
{
m_window->setTitle("Settings");
m_window->setTitle(tr("Settings"));
m_window->resize({900, 610});
// Half of a 1280-wide screen, so the window can be split beside another.
m_window->setMinimumSize({640, 600});
@ -361,7 +363,7 @@ void SettingsWindow::showHistoryEntry(int index)
if (info && info->create)
page = info->create(*this);
if (!page)
page = std::make_unique<PendingPage>(*this, info ? info->title : entry.page);
page = std::make_unique<PendingPage>(*this, info ? tr(info->title.c_str()) : entry.page);
page->setName("page:" + entry.page);
m_page = page.get();
m_root->setPage(std::move(page));

@ -6,6 +6,7 @@
#include <algorithm>
#include <cmath>
#include "base/i18n.h"
#include "ui/color_provider.h"
#include "ui/keys.h"
#include "ui/label.h"
@ -21,6 +22,7 @@
#include "window/pagelist.h"
using namespace cutefish::ui;
using cutefish::i18n::tr;
namespace settings {
@ -43,7 +45,7 @@ public:
: m_page(page)
{
m_icon = addChild<RowIcon>(page.icon, page.iconColor);
m_title = addChild<Label>(page.title);
m_title = addChild<Label>(tr(page.title.c_str()));
m_title->setFontSize(metrics::points(kItemPointSize));
m_title->setVerticallyCentered(true);
}
@ -179,7 +181,7 @@ public:
const Entry& entry = m_entries[i];
if (i == 0 || entry.rootPage->category != category) {
category = entry.rootPage->category;
addChild<SidebarSection>(category);
addChild<SidebarSection>(tr(category.c_str()));
}
SidebarItem* item = addChild<SidebarItem>(*entry.page);
item->setName(m_namePrefix + entry.page->id);
@ -266,7 +268,7 @@ private:
Sidebar::Sidebar()
{
m_title = addChild<Label>("Settings");
m_title = addChild<Label>(tr("Settings"));
m_title->setFontSize(metrics::points(kTitlePointSize));
m_title->setVerticallyCentered(true);
m_search = addChild<SearchField>();
@ -290,7 +292,7 @@ Sidebar::Sidebar()
m_resultsList = m_resultsScroll->setContent<SidebarList>();
m_resultsList->setNamePrefix("result:");
m_resultsList->onClicked = [this](int index) { openResult(index); };
m_noResults = addChild<Label>("No results");
m_noResults = addChild<Label>(tr("No results"));
m_noResults->setElide(Label::Elide::None);
m_noResults->setVisible(false);
}
@ -467,13 +469,14 @@ void Sidebar::updateResults()
if (searching) {
const std::vector<std::string> words = queryWords(m_search->text());
for (const PageInfo* entry : m_entries) {
if (matches(words, {entry->title, entry->category, entry->keywords}))
if (matches(words, {tr(entry->title.c_str()), tr(entry->category.c_str()), tr(entry->keywords.c_str())}))
m_results.push_back({entry, entry});
for (const PageInfo& sub : pages()) {
if (sub.parentId != entry->id
|| std::find(m_unavailableSubPages.begin(), m_unavailableSubPages.end(), sub.id)
!= m_unavailableSubPages.end()
|| !matches(words, {sub.title, entry->title, entry->category, sub.keywords}))
|| !matches(words, {tr(sub.title.c_str()), tr(entry->title.c_str()), tr(entry->category.c_str()),
tr(sub.keywords.c_str())}))
continue;
m_results.push_back({&sub, entry});
}

Loading…
Cancel
Save