diff --git a/.gitignore b/.gitignore index 8ff7975..04ef584 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ *.pro.user *.pro.user.* rpm/harbour-sailotp.spec +i18n/*.qm diff --git a/harbour-sailotp.pro b/harbour-sailotp.pro index c8511ac..aa2e631 100644 --- a/harbour-sailotp.pro +++ b/harbour-sailotp.pro @@ -35,3 +35,18 @@ OTHER_FILES += qml/harbour-sailotp.qml \ HEADERS += \ src/fileio.h +i18n.files = i18n/*.qm +i18n.path = /usr/share/$${TARGET}/i18n + +INSTALLS += i18n + +lupdate_only { + SOURCES = qml/*.qml \ + qml/pages/*.qml \ + qml/covers/*.qml \ + qml/components/*.qml + + TRANSLATIONS = i18n/de.ts \ + i18n/en.ts +} + diff --git a/i18n/de.ts b/i18n/de.ts new file mode 100644 index 0000000..b504539 --- /dev/null +++ b/i18n/de.ts @@ -0,0 +1,251 @@ + + + + + About + + + A Simple Sailfish OTP Generator<br />(RFC 6238/4226 compatible) + Ein einfacher Sailfish OTP-Generator<br/>(RFC 6238/4226-kompatibel) + + + + Copyright: Stefan Brand<br />License: BSD (3-clause) + Copyright: Stefan Brand<br/>Lizenz: BSD (3-Klausel) + + + + SailOTP uses the following third party libs: + SailOTP verwendet folgende externe Bibliotheken: + + + + AddOTP + + + Save + Speichern + + + + Add + Hinzufügen + + + + Type + Typ + + + + Time-based (TOTP) + Zeitbasiert (TOTP) + + + + Counter-based (HOTP) + Zählerbasiert (HOTP) + + + + Title + Titel + + + + Title for the OTP + Titel für das Token + + + + Secret (at least 16 characters) + Schlüssel (mindestens 16 Zeichen) + + + + Secret OTP Key + Geheimer Schlüssel + + + + Next Counter Value + Nächster Zählerwert + + + + Next Value of the Counter + Nächster Wert für den Zähler + + + + ExportPage + + + File already exists, choose "Overwrite existing" to overwrite it. + Datei existiert, aktiviere "Existierende überschreiben" um sie zu ersetzen. + + + + Given file does not exist! + Gewählte Datei existiert nicht! + + + + Export + Export + + + + Import + Import + + + + Filename + Dateiname + + + + File to import + Aus Datei importieren + + + + File to export + In Datei exportieren + + + + Overwrite existing + Existierende überschreiben + + + + Password + Passwort + + + + Password for the file + Passwort für die Datei + + + + Passwords don't match! + Passwörter nicht identisch! + + + + Passwords match! + Passwörter identisch! + + + + Repeated Password for the file + Passwort wiederholen + + + + Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import. + Hier können Tokens aus einer Datei importiert werden. Gib die Datei und das beim Export gewählte Passwort ein. Nach links ziehen um zu starten. + + + + Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export. + Hier können Tokens in eine Datei exportiert werden. Die Datei wird mit AES-256-CBC verschlüsselt und Base64-kodiert. Wähle ein starkes Passwort, die Datei enthält die geheimen Schlüssel zur Erzeugung der Tokens für deine Accounts. Nach links ziehen um zu starten. + + + + Error writing to file + Fehler beim Schreiben der Datei + + + + Token Database exported to + Datenbank exportiert nach + + + + Could not encrypt tokens. Error: + Fehler beim Verschlüsseln. Fehler: + + + + Could not read tokens from Database + Datenbank konnte nicht gelesen werden + + + + Tokens imported from + Tokens importiert aus + + + + Unable to decrypt file, did you use the right password? + Fehler beim entschlüsseln, falsches Passwort? + + + + Could not read from file + Datei konnte nicht gelesen werden + + + + MainView + + + About + Über + + + + Export Token-DB + Datenbank exportieren + + + + Import Token-DB + Datenbank importieren + + + + Add Token + Token hinzufügen + + + + Nothing here + Hier ist nichts + + + + Pull down to add a OTP + Nach unten ziehen zum hinzufügen + + + + Deleting + Lösche + + + + Token for + Token für + + + + copied to clipboard + kopiert + + + + Edit + Bearbeiten + + + + Delete + Löschen + + + diff --git a/i18n/en.ts b/i18n/en.ts new file mode 100644 index 0000000..d5d94f7 --- /dev/null +++ b/i18n/en.ts @@ -0,0 +1,251 @@ + + + + + About + + + A Simple Sailfish OTP Generator<br />(RFC 6238/4226 compatible) + + + + + Copyright: Stefan Brand<br />License: BSD (3-clause) + + + + + SailOTP uses the following third party libs: + + + + + AddOTP + + + Save + + + + + Add + + + + + Type + + + + + Time-based (TOTP) + + + + + Counter-based (HOTP) + + + + + Title + + + + + Title for the OTP + + + + + Secret (at least 16 characters) + + + + + Secret OTP Key + + + + + Next Counter Value + + + + + Next Value of the Counter + + + + + ExportPage + + + File already exists, choose "Overwrite existing" to overwrite it. + + + + + Given file does not exist! + + + + + Export + + + + + Import + + + + + Filename + + + + + File to import + + + + + File to export + + + + + Overwrite existing + + + + + Password + + + + + Password for the file + + + + + Passwords don't match! + + + + + Passwords match! + + + + + Repeated Password for the file + + + + + Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import. + + + + + Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export. + + + + + Error writing to file + + + + + Token Database exported to + + + + + Could not encrypt tokens. Error: + + + + + Could not read tokens from Database + + + + + Tokens imported from + + + + + Unable to decrypt file, did you use the right password? + + + + + Could not read from file + + + + + MainView + + + About + + + + + Export Token-DB + + + + + Import Token-DB + + + + + Add Token + + + + + Nothing here + + + + + Pull down to add a OTP + + + + + Deleting + + + + + Token for + + + + + copied to clipboard + + + + + Edit + + + + + Delete + + + + diff --git a/qml/pages/About.qml b/qml/pages/About.qml index 7fe3b02..a9a75df 100644 --- a/qml/pages/About.qml +++ b/qml/pages/About.qml @@ -51,7 +51,7 @@ Page { anchors.horizontalCenter: parent.horizontalCenter anchors.top: name.bottom anchors.topMargin: 20 - text: "A Simple Sailfish OTP Generator
(RFC 6238/4226 compatible)" + text: qsTr("A Simple Sailfish OTP Generator
(RFC 6238/4226 compatible)") color: "white" } Text { @@ -59,7 +59,7 @@ Page { anchors.horizontalCenter: parent.horizontalCenter anchors.top: desc.bottom anchors.topMargin: 20 - text: "Copyright: Stefan Brand
License: BSD (3-clause)" + text: qsTr("Copyright: Stefan Brand
License: BSD (3-clause)") color: "white" } Button { @@ -81,7 +81,7 @@ Page { font.pixelSize: Theme.fontSizeSmall horizontalAlignment: TextEdit.Center readOnly: true - text: "SailOTP uses the following third party libs:\n\nhttp://caligatio.github.io/jsSHA/\nhttps://github.com/mdp/gibberish-aes" + text: qsTr("SailOTP uses the following third party libs:")+"\n\nhttp://caligatio.github.io/jsSHA/\nhttps://github.com/mdp/gibberish-aes" color: "white" } } diff --git a/qml/pages/AddOTP.qml b/qml/pages/AddOTP.qml index a96b3b1..66f5e3c 100644 --- a/qml/pages/AddOTP.qml +++ b/qml/pages/AddOTP.qml @@ -54,22 +54,22 @@ Dialog { Column { anchors.fill: parent DialogHeader { - acceptText: paramLabel != "" ? "Save" : "Add" + acceptText: paramLabel != "" ? qsTr("Save") : qsTr("Add") } ComboBox { id: typeSel - label: "Type" + label: qsTr("Type") menu: ContextMenu { - MenuItem { text: "Time-based (TOTP)"; onClicked: { paramType = "TOTP" } } - MenuItem { text: "Counter-based (HOTP)"; onClicked: { paramType = "HOTP" } } + MenuItem { text: qsTr("Time-based (TOTP)"); onClicked: { paramType = "TOTP" } } + MenuItem { text: qsTr("Counter-based (HOTP)"); onClicked: { paramType = "HOTP" } } } } TextField { id: otpLabel width: parent.width - label: "Title" - placeholderText: "Title for the OTP" + label: qsTr("Title") + placeholderText: qsTr("Title for the OTP") text: paramLabel != "" ? paramLabel : "" focus: true horizontalAlignment: TextInput.AlignLeft @@ -77,9 +77,9 @@ Dialog { TextField { id: otpSecret width: parent.width - label: "Secret (at least 16 characters)" + label: qsTr("Secret (at least 16 characters)") text: paramKey != "" ? paramKey : "" - placeholderText: "Secret OTP Key" + placeholderText: qsTr("Secret OTP Key") focus: true horizontalAlignment: TextInput.AlignLeft } @@ -87,9 +87,9 @@ Dialog { id: otpCounter width: parent.width visible: paramType == "HOTP" ? true : false - label: "Next Counter Value" + label: qsTr("Next Counter Value") text: paramCounter - placeholderText: "Next Value of the Counter" + placeholderText: qsTr("Next Value of the Counter") focus: true horizontalAlignment: TextInput.AlignLeft validator: IntValidator { bottom: 0 } diff --git a/qml/pages/ExportPage.qml b/qml/pages/ExportPage.qml index 7d56ec4..50ead8f 100644 --- a/qml/pages/ExportPage.qml +++ b/qml/pages/ExportPage.qml @@ -58,7 +58,7 @@ Dialog { function checkFileName(file) { if (mode == "export") { if (exportFile.exists(file) && !fileOverwrite.checked) { - notify.show("File already exists, choose \"Overwrite existing\" to overwrite it.", 4000); + notify.show(qsTr("File already exists, choose \"Overwrite existing\" to overwrite it."), 4000); return(false) } else { return(true) @@ -67,7 +67,7 @@ Dialog { if (exportFile.exists(file)) { return(true) } else { - notify.show("Given file does not exist!", 4000); + notify.show(qsTr("Given file does not exist!"), 4000); return(false) } } @@ -89,15 +89,15 @@ Dialog { Column { anchors.fill: parent DialogHeader { - acceptText: mode == "export" ? "Export" : "Import" + acceptText: mode == "export" ? qsTr("Export") : qsTr("Import") } TextField { id: fileName width: parent.width text: mode == "export" ? creFileName() : XDG_HOME_DIR + "/"; - label: "Filename" - placeholderText: mode == "import" ? "File to import" : "File to export" + label: qsTr("Filename") + placeholderText: mode == "import" ? qsTr("File to import") : qsTr("File to export") focus: true horizontalAlignment: TextInput.AlignLeft } @@ -106,14 +106,14 @@ Dialog { id: fileOverwrite checked: false visible: mode == "export" - text: "Overwrite existing" + text: qsTr("Overwrite existing") } TextField { id: filePassword width: parent.width - label: "Password" - placeholderText: "Password for the file" + label: qsTr("Password") + placeholderText: qsTr("Password for the file") echoMode: TextInput.Password focus: true horizontalAlignment: TextInput.AlignLeft @@ -122,8 +122,8 @@ Dialog { TextField { id: filePasswordCheck width: parent.width - label: (filePassword.text != filePasswordCheck.text && filePassword.text.length > 0) ? "Passwords don't match!" : "Passwords match!" - placeholderText: "Repeated Password for the file" + label: (filePassword.text != filePasswordCheck.text && filePassword.text.length > 0) ? qsTr("Passwords don't match!") : qsTr("Passwords match!") + placeholderText: qsTr("Repeated Password for the file") visible: mode == "export" echoMode: TextInput.Password focus: true @@ -143,7 +143,7 @@ Dialog { color: Theme.secondaryColor visible: mode == "import" - text: "Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import." + text: qsTr("Here you can Import Tokens from a file. Put in the file location and the password you used on export. Pull left to start the import.") } Text { @@ -159,7 +159,7 @@ Dialog { color: Theme.secondaryColor visible: mode == "export" - text: "Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export." + text: qsTr("Here you can export Tokens to a file. The exported file will be encrypted with AES-256-CBC and Base64 encoded. Choose a strong password, the file will contain the secrets used to generate the Tokens for your accounts. Pull left to start the export.") } } } @@ -181,15 +181,15 @@ Dialog { try { chipherText = Gibberish.AES.enc(plainText, filePassword.text); if (!exportFile.write(chipherText)) { - notify.show("Error writing to file "+ fileName.text, 4000); + notify.show(qsTr("Error writing to file ")+ fileName.text, 4000); } else { - notify.show("Token Database exported to "+ fileName.text, 4000); + notify.show(qsTr("Token Database exported to ")+ fileName.text, 4000); } } catch(e) { - notify.show("Could not encrypt tokens. Error: ", 4000); + notify.show(qsTr("Could not encrypt tokens. Error: "), 4000); } } else { - notify.show("Could not read tokens from Database", 4000); + notify.show(qsTr("Could not read tokens from Database"), 4000); } } else if(mode == "import") { // Import Tokens from File @@ -200,15 +200,15 @@ Dialog { var errormsg = "" plainText = Gibberish.AES.dec(chipherText, filePassword.text); if (DB.json2db(plainText, errormsg)) { - notify.show("Tokens imported from "+ fileName.text, 4000); + notify.show(qsTr("Tokens imported from ")+ fileName.text, 4000); } else { notify.show(errormsg, 4000); } } catch (e) { - notify.show("Unable to decrypt file, did you use the right password?", 4000); + notify.show(qsTr("Unable to decrypt file, did you use the right password?"), 4000); } } else { - notify.show("Could not read from file " + fileName.text, 4000); + notify.show(qsTr("Could not read from file ") + fileName.text, 4000); } } } diff --git a/qml/pages/MainView.qml b/qml/pages/MainView.qml index 7849f89..97461be 100644 --- a/qml/pages/MainView.qml +++ b/qml/pages/MainView.qml @@ -108,19 +108,19 @@ Page { PullDownMenu { MenuItem { - text: "About" + text: qsTr("About") onClicked: pageStack.push(Qt.resolvedUrl("About.qml")) } MenuItem { - text: "Export Token-DB" + text: qsTr("Export Token-DB") onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "export"}) } MenuItem { - text: "Import Token-DB" + text: qsTr("Import Token-DB") onClicked: pageStack.push(Qt.resolvedUrl("ExportPage.qml"), {parentPage: mainPage, mode: "import"}) } MenuItem { - text: "Add Token" + text: qsTr("Add Token") onClicked: pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage}) } } @@ -146,8 +146,8 @@ Page { ViewPlaceholder { enabled: otpList.count == 0 - text: "Nothing here" - hintText: "Pull down to add a OTP" + text: qsTr("Nothing here") + hintText: qsTr("Pull down to add a OTP") } delegate: ListItem { @@ -158,12 +158,12 @@ Page { function remove() { // Show 5s countdown, then delete from DB and List - remorseAction("Deleting", function() { DB.removeOTP(title, secret); otpListModel.remove(index) }) + remorseAction(qsTr("Deleting"), function() { DB.removeOTP(title, secret); otpListModel.remove(index) }) } onClicked: { Clipboard.text = otp - notify.show("Token for " + title + " copied to clipboard", 3000); + notify.show(qsTr("Token for ") + title + qsTr(" copied to clipboard"), 3000); } ListView.onRemove: animateRemoval() @@ -231,13 +231,13 @@ Page { id: otpContextMenu ContextMenu { MenuItem { - text: "Edit" + text: qsTr("Edit") onClicked: { pageStack.push(Qt.resolvedUrl("AddOTP.qml"), {parentPage: mainPage, paramLabel: title, paramKey: secret, paramType: type, paramCounter: DB.getCounter(title, secret, false)}) } } MenuItem { - text: "Delete" + text: qsTr("Delete") onClicked: remove() } } @@ -248,10 +248,7 @@ Page { Component.onCompleted: { // Load list of OTP-Entries refreshOTPList(); - console.log("SailOTP Version " + Qt.application.version + " started"); } } } } - - diff --git a/rpm/harbour-sailotp.yaml b/rpm/harbour-sailotp.yaml index 162cb15..f0faca5 100644 --- a/rpm/harbour-sailotp.yaml +++ b/rpm/harbour-sailotp.yaml @@ -15,19 +15,20 @@ QMakeOptions: - VERSION=%{version} - RELEASE=%{release} PkgConfigBR: -- sailfishapp >= 0.0.10 -- Qt5Core -- Qt5Qml - Qt5Quick +- Qt5Qml +- Qt5Core +- sailfishapp >= 0.0.10 Requires: - sailfishsilica-qt5 >= 0.10.9 Files: -- /usr/share/icons/hicolor/86x86/apps -- /usr/share/applications -- /usr/share/harbour-sailotp -- /usr/bin -- '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png' -- '%{_datadir}/applications/%{name}.desktop' -- '%{_datadir}/%{name}/qml' - '%{_bindir}' +- '%{_datadir}/%{name}/qml' +- '%{_datadir}/applications/%{name}.desktop' +- '%{_datadir}/icons/hicolor/86x86/apps/%{name}.png' +- /usr/bin +- /usr/share/harbour-sailotp +- /usr/share/applications +- /usr/share/icons/hicolor/86x86/apps +- /usr/share/harbour-sailotp/i18n PkgBR: [] diff --git a/src/harbour-sailotp.cpp b/src/harbour-sailotp.cpp index d1bc1ce..a21973f 100644 --- a/src/harbour-sailotp.cpp +++ b/src/harbour-sailotp.cpp @@ -34,21 +34,31 @@ int main(int argc, char *argv[]) { + // Get App and QML-View objects QScopedPointer app(SailfishApp::application(argc, argv)); QScopedPointer view(SailfishApp::createView()); + // Internationalization, Load the Language + QString locale = QLocale::system().name(); + QTranslator translator; + translator.load(locale,SailfishApp::pathTo(QString("i18n")).toLocalFile()); + app->installTranslator(&translator); + + // Set some global values app->setOrganizationName("harbour-sailotp"); app->setOrganizationDomain("harbour-sailotp"); app->setApplicationName("harbour-sailotp"); app->setApplicationVersion(APP_VERSION); + // Register FileIO Class qmlRegisterType("harbour.sailotp.FileIO", 1, 0, "FileIO"); + // Prepare the QML and set Homedir view->setSource(SailfishApp::pathTo("qml/harbour-sailotp.qml")); view->rootContext()->setContextProperty("XDG_HOME_DIR", QStandardPaths::writableLocation(QStandardPaths::HomeLocation)); view->show(); - + // Run the app return app->exec(); }