From 1e73388791bccc6f49797962c0944ac02f59b03f Mon Sep 17 00:00:00 2001 From: Stefan Brand Date: Sun, 9 Feb 2014 10:59:50 +0100 Subject: [PATCH] Version 0.6 Added internationalization support Added german translation Tokens can be switched on the Cover Some minor UI-tweaks --- .gitignore | 1 + harbour-sailotp.pro | 15 +++ i18n/de.ts | 263 +++++++++++++++++++++++++++++++++++++++ i18n/en.ts | 253 +++++++++++++++++++++++++++++++++++++ qml/cover/CoverPage.qml | 60 +++++---- qml/harbour-sailotp.qml | 37 ++++++ qml/lib/storage.js | 4 +- qml/pages/About.qml | 22 ++-- qml/pages/AddOTP.qml | 31 +++-- qml/pages/ExportPage.qml | 50 +++++--- qml/pages/MainView.qml | 70 +++++------ rpm/harbour-sailotp.yaml | 6 +- src/harbour-sailotp.cpp | 12 +- 13 files changed, 720 insertions(+), 104 deletions(-) create mode 100644 i18n/de.ts create mode 100644 i18n/en.ts 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..2a83634 --- /dev/null +++ b/i18n/de.ts @@ -0,0 +1,263 @@ + + + + + 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) + + + + A Simple Sailfish OTP Generator +(RFC 6238/4226 compatible) + Ein einfacher Sailfish OTP-Generator +(RFC 6238/4226-kompatibel) + + + + Copyright: Stefan Brand +License: BSD (3-clause) + Copyright: Stefan Brand +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..068b2c7 --- /dev/null +++ b/i18n/en.ts @@ -0,0 +1,253 @@ + + + + + About + + + A Simple Sailfish OTP Generator +(RFC 6238/4226 compatible) + + + + + Copyright: Stefan Brand +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/cover/CoverPage.qml b/qml/cover/CoverPage.qml index 7d504ee..f7c21c2 100644 --- a/qml/cover/CoverPage.qml +++ b/qml/cover/CoverPage.qml @@ -38,28 +38,30 @@ CoverBackground { property double lastUpdated: 0 + function updateOTP() { + // get seconds from current Date + var curDate = new Date(); + + if (lOTP.text == "------" || curDate.getSeconds() == 30 || curDate.getSeconds() == 0 || (curDate.getTime() - lastUpdated > 2000)) { + appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "TOTP", 0); + } + + // Change color of the OTP to red if less than 5 seconds left + if (29 - (curDate.getSeconds() % 30) < 5) { + lOTP.color = "red" + } else { + lOTP.color = Theme.highlightColor + } + + lastUpdated = curDate.getTime(); + } + Timer { interval: 1000 // Timer runs only when cover is visible and favourite is set running: !Qt.application.active && appWin.coverSecret != "" && appWin.coverType == "TOTP" repeat: true - onTriggered: { - // get seconds from current Date - var curDate = new Date(); - - if (lOTP.text == "------" || curDate.getSeconds() == 30 || curDate.getSeconds() == 0 || (curDate.getTime() - lastUpdated > 2000)) { - appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "TOTP", 0); - } - - // Change color of the OTP to red if less than 5 seconds left - if (29 - (curDate.getSeconds() % 30) < 5) { - lOTP.color = "red" - } else { - lOTP.color = Theme.highlightColor - } - - lastUpdated = curDate.getTime(); - } + onTriggered: updateOTP(); } // Show the SailOTP Logo @@ -79,7 +81,6 @@ CoverBackground { Label { text: appWin.coverTitle anchors.horizontalCenter: parent.horizontalCenter - color: Theme.secondaryColor } Label { id: lOTP @@ -91,11 +92,28 @@ CoverBackground { } // CoverAction to update a HOTP-Token, only visible for HOTP-Type Tokens CoverActionList { - enabled: appWin.coverType == "HOTP" ? true : false CoverAction { - iconSource: "image://theme/icon-m-refresh" + iconSource: appWin.coverType == "HOTP" ? "image://theme/icon-cover-refresh" : "image://theme/icon-cover-previous" onTriggered: { - appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "HOTP", DB.getCounter(appWin.coverTitle, appWin.coverSecret, true)); + if (appWin.coverType == "HOTP") { + appWin.coverOTP = OTP.calcOTP(appWin.coverSecret, "HOTP", DB.getCounter(appWin.coverTitle, appWin.coverSecret, true)); + } else { + var index = appWin.coverIndex - 1 + if (index < 0) index = appWin.listModel.count - 1 + appWin.setCover(index); + DB.setFav(appWin.coverTitle, appWin.coverSecret) + if (appWin.coverType == "TOTP") updateOTP(); + } + } + } + CoverAction { + iconSource: "image://theme/icon-cover-next" + onTriggered: { + var index = appWin.coverIndex + 1 + if (index >= appWin.listModel.count) index = 0 + appWin.setCover(index); + DB.setFav(appWin.coverTitle, appWin.coverSecret) + if (appWin.coverType == "TOTP") updateOTP(); } } } diff --git a/qml/harbour-sailotp.qml b/qml/harbour-sailotp.qml index fb555f1..a30e94d 100644 --- a/qml/harbour-sailotp.qml +++ b/qml/harbour-sailotp.qml @@ -37,15 +37,52 @@ ApplicationWindow id: appWin // Properties to pass values between MainPage and Cover + property alias listModel: otpListModel property string coverTitle: "SailOTP" property string coverSecret: "" property string coverType: "" property string coverOTP: "------" + property int coverIndex: 0 + + // Global Listmodel for Tokens + ListModel { id: otpListModel } + + // Global Component for showing notification banners + NotifyBanner { id: notify } + + // Add an entry to the list + function appendOTP(title, secret, type, counter, fav) { + listModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "otp": "------"}); + } + + // Set the OTP shown on the Cover + function setCover(index) { + if (index >= 0 && index < listModel.count) { + coverTitle = listModel.get(index).title; + coverSecret = listModel.get(index).secret; + coverType = listModel.get(index).type; + coverIndex = index; + if (coverType == "TOTP") { coverOTP = "------"; } else { coverOTP = listModel.get(index).otp; } + for (var i=0; i(RFC 6238/4226 compatible)" + width: parent.width + horizontalAlignment: TextEdit.Center + readOnly: true + text: qsTr("A Simple Sailfish OTP Generator\n(RFC 6238/4226 compatible)") color: "white" } - Text { + TextArea { id: copyright anchors.horizontalCenter: parent.horizontalCenter anchors.top: desc.bottom - anchors.topMargin: 20 - text: "Copyright: Stefan Brand
License: BSD (3-clause)" + width: parent.width + horizontalAlignment: TextEdit.Center + readOnly: true + text: qsTr("Copyright: Stefan Brand\nLicense: BSD (3-clause)") color: "white" } Button { id: homepage anchors.horizontalCenter: parent.horizontalCenter anchors.top: copyright.bottom - anchors.topMargin: 20 text: "Source Code" onClicked: { Qt.openUrlExternally("https://www.seiichiro0185.org/gitlab/seiichiro0185/harbour-sailotp.git") @@ -81,7 +85,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..752715d 100644 --- a/qml/pages/AddOTP.qml +++ b/qml/pages/AddOTP.qml @@ -54,45 +54,56 @@ 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 + + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: otpSecret.focus = true } 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 + + EnterKey.enabled: text.length > 15 + EnterKey.iconSource: paramType == "TOTP" ? "image://theme/icon-m-enter-accept" : "image://theme/icon-m-enter-next" + EnterKey.onClicked: paramType == "TOTP" ? addOTP.accept() : otpCounter.focus = true } TextField { 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 } + + EnterKey.iconSource: "image://theme/icon-m-enter-accept" + EnterKey.onClicked: addOTP.accept() } Component.onCompleted: { typeSel.currentIndex = paramType == "HOTP" ? 1 : 0 } } diff --git a/qml/pages/ExportPage.qml b/qml/pages/ExportPage.qml index 7d56ec4..2513dca 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,45 +89,57 @@ 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 + + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-enter-next" + EnterKey.onClicked: filePassword.focus = true } TextSwitch { 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 + + EnterKey.enabled: text.length > 0 + EnterKey.iconSource: mode == "export" ? "image://theme/icon-m-enter-next" : "image://theme/icon-m-enter-accept" + EnterKey.onClicked: mode == "export" ? filePasswordCheck.focus = true : exportPage.accept() } 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 horizontalAlignment: TextInput.AlignLeft + + EnterKey.enabled: filePassword.text == filePasswordCheck.text && filePassword.text.length > 0 + EnterKey.iconSource: "image://theme/icon-m-enter-accept" + EnterKey.onClicked: exportPage.accept() } Text { @@ -143,7 +155,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 +171,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 +193,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 +212,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..4641347 100644 --- a/qml/pages/MainView.qml +++ b/qml/pages/MainView.qml @@ -36,18 +36,9 @@ import "../lib/crypto.js" as OTP Page { id: mainPage - ListModel { - id: otpListModel - } - // This holds the time of the last update of the page as Unix Timestamp (in Milliseconds) property double lastUpdated: 0 - // Add an entry to the list - function appendOTP(title, secret, type, counter, fav) { - otpListModel.append({"secret": secret, "title": title, "fav": fav, "type": type, "counter": counter, "otp": "------"}); - } - // Hand favorite over to the cover function setCoverOTP(title, secret, type) { appWin.coverTitle = title @@ -63,7 +54,7 @@ Page { // Reload the List of OTPs from storage function refreshOTPList() { otpList.visible = false; - otpListModel.clear(); + appWin.listModel.clear(); DB.getOTP(); refreshOTPValues(); otpList.visible = true; @@ -76,16 +67,16 @@ Page { var seconds = curDate.getSeconds(); // Iterate over all List entries - for (var i=0; i 2000)) { - var curOTP = OTP.calcOTP(otpListModel.get(i).secret, "TOTP") - otpListModel.setProperty(i, "otp", curOTP); + if (appWin.listModel.get(i).otp == "------" || seconds == 30 || seconds == 0 || (curDate.getTime() - lastUpdated > 2000)) { + var curOTP = OTP.calcOTP(appWin.listModel.get(i).secret, "TOTP") + appWin.listModel.setProperty(i, "otp", curOTP); } - } else if (appWin.coverType == "HOTP" && (curDate.getTime() - lastUpdated > 2000) && otpListModel.get(i).fav == 1) { + } else if (appWin.coverType == "HOTP" && (curDate.getTime() - lastUpdated > 2000) && appWin.listModel.get(i).fav == 1) { // If we are coming back from the CoverPage update OTP value if current favourite is HOTP - otpListModel.setProperty(i, "otp", appWin.coverOTP); + appWin.listModel.setProperty(i, "otp", appWin.coverOTP); } } @@ -98,7 +89,7 @@ Page { Timer { interval: 500 // Timer only runs when app is acitive and we have entries - running: Qt.application.active && otpListModel.count + running: Qt.application.active && appWin.listModel.count repeat: true onTriggered: refreshOTPValues(); } @@ -108,19 +99,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}) } } @@ -132,7 +123,7 @@ Page { anchors.top: parent.top anchors.topMargin: 48 // Only show when there are enries - visible: otpListModel.count + visible: appWin.listModel.count } SilicaListView { @@ -141,13 +132,13 @@ Page { title: "SailOTP" } anchors.fill: parent - model: otpListModel + model: appWin.listModel width: parent.width 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 +149,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); appWin.listModel.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() @@ -178,19 +169,19 @@ Page { onClicked: { if (fav == 0) { DB.setFav(title, secret) - setCoverOTP(title, secret, type) + appWin.setCover(index) if (type == "HOTP") appWin.coverOTP = otp - for (var i=0; i= 0.0.10 +- Qt5Core +- Qt5Qml +- Qt5Quick Requires: - sailfishsilica-qt5 >= 0.10.9 Files: @@ -30,4 +33,5 @@ Files: - /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(); }