linux(i18n): add Turkish translations (#361)

* linux(i18n): add Turkish translations

- Add Qt Linguist translation infrastructure to CMakeLists.txt
- Wrap UI strings with qsTr() in Main.qml
- Wrap menu strings with tr() in trayiconmanager.cpp
- Add QTranslator loader in main.cpp for automatic locale detection
- Create Turkish translation file (librepods_tr.ts)

Translations include:
- Main window: connection status, noise control modes, settings
- Tray menu: all menu items and tooltips
- System notifications

* fix: allocate QTranslator on heap to ensure lifetime
This commit is contained in:
Mümin Köykıran
2025-12-10 04:12:51 +03:00
committed by GitHub
parent 0e1f784737
commit a75557d6dc
5 changed files with 217 additions and 28 deletions

View File

@@ -4,13 +4,18 @@ project(linux VERSION 0.1 LANGUAGES CXX)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 REQUIRED COMPONENTS Quick Widgets Bluetooth DBus)
find_package(Qt6 REQUIRED COMPONENTS Quick Widgets Bluetooth DBus LinguistTools)
find_package(OpenSSL REQUIRED)
find_package(PkgConfig REQUIRED)
pkg_check_modules(PULSEAUDIO REQUIRED libpulse)
qt_standard_project_setup()
# Translation files
set(TS_FILES
translations/librepods_tr.ts
)
qt_add_executable(librepods
main.cpp
logger.h
@@ -85,3 +90,13 @@ install(FILES assets/me.kavishdevar.librepods.desktop
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/applications")
install(FILES assets/librepods.png
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/icons/hicolor/512x512/apps")
# Translation support
qt_add_translations(librepods
TS_FILES ${TS_FILES}
QM_FILES_OUTPUT_VARIABLE QM_FILES
)
# Install translation files
install(FILES ${QM_FILES}
DESTINATION "${CMAKE_INSTALL_DATAROOTDIR}/librepods/translations")

View File

@@ -81,7 +81,7 @@ ApplicationWindow {
Label {
anchors.centerIn: parent
text: airPodsTrayApp.airpodsConnected ? "Connected" : "Disconnected"
text: airPodsTrayApp.airpodsConnected ? qsTr("Connected") : qsTr("Disconnected")
color: "white"
font.pixelSize: 12
font.weight: Font.Medium
@@ -130,7 +130,7 @@ ApplicationWindow {
SegmentedControl {
anchors.horizontalCenter: parent.horizontalCenter
model: ["Off", "Noise Cancellation", "Transparency", "Adaptive"]
model: [qsTr("Off"), qsTr("Noise Cancellation"), qsTr("Transparency"), qsTr("Adaptive")]
currentIndex: airPodsTrayApp.deviceInfo.noiseControlMode
onCurrentIndexChanged: airPodsTrayApp.setNoiseControlModeInt(currentIndex)
visible: airPodsTrayApp.airpodsConnected
@@ -153,21 +153,21 @@ ApplicationWindow {
onValueChanged: if (pressed) debounceTimer.restart()
Label {
text: "Adaptive Noise Level: " + parent.value
text: qsTr("Adaptive Noise Level: ") + parent.value
anchors.top: parent.bottom
}
}
Switch {
visible: airPodsTrayApp.airpodsConnected
text: "Conversational Awareness"
text: qsTr("Conversational Awareness")
checked: airPodsTrayApp.deviceInfo.conversationalAwareness
onCheckedChanged: airPodsTrayApp.setConversationalAwareness(checked)
}
Switch {
visible: airPodsTrayApp.airpodsConnected
text: "Hearing Aid"
text: qsTr("Hearing Aid")
checked: airPodsTrayApp.deviceInfo.hearingAidEnabled
onCheckedChanged: airPodsTrayApp.setHearingAidEnabled(checked)
}
@@ -189,7 +189,7 @@ ApplicationWindow {
id: settingsPage
Page {
id: settingsPageItem
title: "Settings"
title: qsTr("Settings")
ScrollView {
anchors.fill: parent
@@ -200,7 +200,7 @@ ApplicationWindow {
padding: 20
Label {
text: "Settings"
text: qsTr("Settings")
font.pixelSize: 24
// center the label
anchors.horizontalCenter: parent.horizontalCenter
@@ -210,19 +210,19 @@ ApplicationWindow {
spacing: 5 // Small gap between label and ComboBox
Label {
text: "Pause Behavior When Removing AirPods:"
text: qsTr("Pause Behavior When Removing AirPods:")
}
ComboBox {
width: parent.width // Ensures full width
model: ["One Removed", "Both Removed", "Never"]
model: [qsTr("One Removed"), qsTr("Both Removed"), qsTr("Never")]
currentIndex: airPodsTrayApp.earDetectionBehavior
onActivated: airPodsTrayApp.earDetectionBehavior = currentIndex
}
}
Switch {
text: "Cross-Device Connectivity with Android"
text: qsTr("Cross-Device Connectivity with Android")
checked: airPodsTrayApp.crossDeviceEnabled
onCheckedChanged: {
airPodsTrayApp.setCrossDeviceEnabled(checked)
@@ -230,26 +230,26 @@ ApplicationWindow {
}
Switch {
text: "Auto-Start on Login"
text: qsTr("Auto-Start on Login")
checked: airPodsTrayApp.autoStartManager.autoStartEnabled
onCheckedChanged: airPodsTrayApp.autoStartManager.autoStartEnabled = checked
}
Switch {
text: "Enable System Notifications"
text: qsTr("Enable System Notifications")
checked: airPodsTrayApp.notificationsEnabled
onCheckedChanged: airPodsTrayApp.notificationsEnabled = checked
}
Switch {
visible: airPodsTrayApp.airpodsConnected
text: "One Bud ANC Mode"
text: qsTr("One Bud ANC Mode")
checked: airPodsTrayApp.deviceInfo.oneBudANCMode
onCheckedChanged: airPodsTrayApp.deviceInfo.oneBudANCMode = checked
ToolTip {
visible: parent.hovered
text: "Enable ANC when using one AirPod\n(More noise reduction, but uses more battery)"
text: qsTr("Enable ANC when using one AirPod\n(More noise reduction, but uses more battery)")
delay: 500
}
}
@@ -257,7 +257,7 @@ ApplicationWindow {
Row {
spacing: 5
Label {
text: "Bluetooth Retry Attempts:"
text: qsTr("Bluetooth Retry Attempts:")
anchors.verticalCenter: parent.verticalCenter
}
SpinBox {
@@ -279,7 +279,7 @@ ApplicationWindow {
}
Button {
text: "Rename"
text: qsTr("Rename")
onClicked: airPodsTrayApp.renameAirPods(newNameField.text)
}
}
@@ -295,14 +295,14 @@ ApplicationWindow {
}
Button {
text: "Change Phone MAC"
text: qsTr("Change Phone MAC")
onClicked: airPodsTrayApp.setPhoneMac(newPhoneMacField.text)
}
}
Button {
text: "Show Magic Cloud Keys QR"
text: qsTr("Show Magic Cloud Keys QR")
onClicked: keysQrDialog.show()
}

View File

@@ -12,6 +12,10 @@
#include <QTimer>
#include <QProcess>
#include <QRegularExpression>
#include <QTranslator>
#include <QLibraryInfo>
#include <QDir>
#include <QStandardPaths>
#include "airpods_packets.h"
#include "logger.h"
@@ -987,6 +991,25 @@ private:
int main(int argc, char *argv[]) {
QApplication app(argc, argv);
// Load translations
QTranslator *translator = new QTranslator(&app);
QString locale = QLocale::system().name();
// Try to load translation from various locations
QStringList translationPaths = {
QCoreApplication::applicationDirPath() + "/translations",
QStandardPaths::writableLocation(QStandardPaths::GenericDataLocation) + "/librepods/translations",
"/usr/share/librepods/translations",
"/usr/local/share/librepods/translations"
};
for (const QString &path : translationPaths) {
if (translator->load("librepods_" + locale, path)) {
app.installTranslator(translator);
break;
}
}
QLocalServer::removeServer("app_server");
QFile stale("/tmp/app_server");

View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="tr_TR">
<context>
<name>Main</name>
<message>
<source>Connected</source>
<translation>Bağlı</translation>
</message>
<message>
<source>Disconnected</source>
<translation>Bağlantı Kesildi</translation>
</message>
<message>
<source>Off</source>
<translation>Kapalı</translation>
</message>
<message>
<source>Noise Cancellation</source>
<translation>Gürültü Engelleme</translation>
</message>
<message>
<source>Transparency</source>
<translation>Şeffaflık</translation>
</message>
<message>
<source>Adaptive</source>
<translation>Uyarlanabilir</translation>
</message>
<message>
<source>Adaptive Noise Level: </source>
<translation>Uyarlanabilir Gürültü Seviyesi: </translation>
</message>
<message>
<source>Conversational Awareness</source>
<translation>Konuşma Farkındalığı</translation>
</message>
<message>
<source>Hearing Aid</source>
<translation>İşitme Cihazı</translation>
</message>
<message>
<source>Settings</source>
<translation>Ayarlar</translation>
</message>
<message>
<source>Pause Behavior When Removing AirPods:</source>
<translation>AirPods Çıkarıldığında Duraklatma Davranışı:</translation>
</message>
<message>
<source>One Removed</source>
<translation>Biri Çıkarıldığında</translation>
</message>
<message>
<source>Both Removed</source>
<translation>İkisi de Çıkarıldığında</translation>
</message>
<message>
<source>Never</source>
<translation>Asla</translation>
</message>
<message>
<source>Cross-Device Connectivity with Android</source>
<translation>Android ile Çapraz Cihaz Bağlantısı</translation>
</message>
<message>
<source>Auto-Start on Login</source>
<translation>Oturum ıldığında Otomatik Başlat</translation>
</message>
<message>
<source>Enable System Notifications</source>
<translation>Sistem Bildirimlerini Etkinleştir</translation>
</message>
<message>
<source>One Bud ANC Mode</source>
<translation>Tek Kulaklık ANC Modu</translation>
</message>
<message>
<source>Enable ANC when using one AirPod
(More noise reduction, but uses more battery)</source>
<translation>Tek AirPod kullanırken ANC&apos;yi etkinleştir
(Daha fazla gürültü azaltma, ancak daha fazla pil kullanır)</translation>
</message>
<message>
<source>Bluetooth Retry Attempts:</source>
<translation>Bluetooth Yeniden Deneme Sayısı:</translation>
</message>
<message>
<source>Rename</source>
<translation>Yeniden Adlandır</translation>
</message>
<message>
<source>Change Phone MAC</source>
<translation>Telefon MAC Adresini Değiştir</translation>
</message>
<message>
<source>Show Magic Cloud Keys QR</source>
<translation>Magic Cloud Anahtarları QR&apos;ını Göster</translation>
</message>
</context>
<context>
<name>TrayIconManager</name>
<message>
<source>Battery Status: </source>
<translation>Pil Durumu: </translation>
</message>
<message>
<source>Open</source>
<translation></translation>
</message>
<message>
<source>Settings</source>
<translation>Ayarlar</translation>
</message>
<message>
<source>Toggle Conversational Awareness</source>
<translation>Konuşma Farkındalığını /Kapat</translation>
</message>
<message>
<source>Adaptive</source>
<translation>Uyarlanabilir</translation>
</message>
<message>
<source>Transparency</source>
<translation>Şeffaflık</translation>
</message>
<message>
<source>Noise Cancellation</source>
<translation>Gürültü Engelleme</translation>
</message>
<message>
<source>Off</source>
<translation>Kapalı</translation>
</message>
<message>
<source>Quit</source>
<translation>Çıkış</translation>
</message>
</context>
<context>
<name>AirPodsTrayApp</name>
<message>
<source>AirPods Disconnected</source>
<translation>AirPods Bağlantısı Kesildi</translation>
</message>
<message>
<source>Your AirPods have been disconnected</source>
<translation>AirPods&apos;unuzun bağlantısı kesildi</translation>
</message>
</context>
</TS>

View File

@@ -36,7 +36,7 @@ void TrayIconManager::showNotification(const QString &title, const QString &mess
void TrayIconManager::TrayIconManager::updateBatteryStatus(const QString &status)
{
trayIcon->setToolTip("Battery Status: " + status);
trayIcon->setToolTip(tr("Battery Status: ") + status);
updateIconFromBattery(status);
}
@@ -57,20 +57,20 @@ void TrayIconManager::updateConversationalAwareness(bool enabled)
void TrayIconManager::setupMenuActions()
{
// Open action
QAction *openAction = new QAction("Open", trayMenu);
QAction *openAction = new QAction(tr("Open"), trayMenu);
trayMenu->addAction(openAction);
connect(openAction, &QAction::triggered, qApp, [this](){emit openApp();});
// Settings Menu
QAction *settingsMenu = new QAction("Settings", trayMenu);
QAction *settingsMenu = new QAction(tr("Settings"), trayMenu);
trayMenu->addAction(settingsMenu);
connect(settingsMenu, &QAction::triggered, qApp, [this](){emit openSettings();});
trayMenu->addSeparator();
// Conversational Awareness Toggle
caToggleAction = new QAction("Toggle Conversational Awareness", trayMenu);
caToggleAction = new QAction(tr("Toggle Conversational Awareness"), trayMenu);
caToggleAction->setCheckable(true);
trayMenu->addAction(caToggleAction);
connect(caToggleAction, &QAction::triggered, this, [this](bool checked)
@@ -81,10 +81,10 @@ void TrayIconManager::setupMenuActions()
// Noise Control Options
noiseControlGroup = new QActionGroup(trayMenu);
const QPair<QString, NoiseControlMode> noiseOptions[] = {
{"Adaptive", NoiseControlMode::Adaptive},
{"Transparency", NoiseControlMode::Transparency},
{"Noise Cancellation", NoiseControlMode::NoiseCancellation},
{"Off", NoiseControlMode::Off}};
{tr("Adaptive"), NoiseControlMode::Adaptive},
{tr("Transparency"), NoiseControlMode::Transparency},
{tr("Noise Cancellation"), NoiseControlMode::NoiseCancellation},
{tr("Off"), NoiseControlMode::Off}};
for (auto option : noiseOptions)
{
@@ -100,7 +100,7 @@ void TrayIconManager::setupMenuActions()
trayMenu->addSeparator();
// Quit action
QAction *quitAction = new QAction("Quit", trayMenu);
QAction *quitAction = new QAction(tr("Quit"), trayMenu);
trayMenu->addAction(quitAction);
connect(quitAction, &QAction::triggered, qApp, &QApplication::quit);
}