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
+
+
+ Ein einfacher Sailfish OTP-Generator<br/>(RFC 6238/4226-kompatibel)
+
+
+
+ Copyright: Stefan Brand<br/>Lizenz: BSD (3-Klausel)
+
+
+
+
+ Ein einfacher Sailfish OTP-Generator
+(RFC 6238/4226-kompatibel)
+
+
+
+
+ Copyright: Stefan Brand
+Lizenz: BSD (3-Klausel)
+
+
+
+
+ SailOTP verwendet folgende externe Bibliotheken:
+
+
+
+ AddOTP
+
+
+
+ Speichern
+
+
+
+
+ Hinzufügen
+
+
+
+
+ Typ
+
+
+
+
+ Zeitbasiert (TOTP)
+
+
+
+
+ Zählerbasiert (HOTP)
+
+
+
+
+ Titel
+
+
+
+
+ Titel für das Token
+
+
+
+
+ Schlüssel (mindestens 16 Zeichen)
+
+
+
+
+ Geheimer Schlüssel
+
+
+
+
+ Nächster Zählerwert
+
+
+
+
+ Nächster Wert für den Zähler
+
+
+
+ ExportPage
+
+
+
+ Datei existiert, aktiviere "Existierende überschreiben" um sie zu ersetzen.
+
+
+
+
+ Gewählte Datei existiert nicht!
+
+
+
+
+ Export
+
+
+
+
+ Import
+
+
+
+
+ Dateiname
+
+
+
+
+ Aus Datei importieren
+
+
+
+
+ In Datei exportieren
+
+
+
+
+ Existierende überschreiben
+
+
+
+
+ Passwort
+
+
+
+
+ Passwort für die Datei
+
+
+
+
+ Passwörter nicht identisch!
+
+
+
+
+ Passwörter identisch!
+
+
+
+
+ Passwort wiederholen
+
+
+
+
+ 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.
+
+
+
+
+ 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.
+
+
+
+
+ Fehler beim Schreiben der Datei
+
+
+
+
+ Datenbank exportiert nach
+
+
+
+
+ Fehler beim Verschlüsseln. Fehler:
+
+
+
+
+ Datenbank konnte nicht gelesen werden
+
+
+
+
+ Tokens importiert aus
+
+
+
+
+ Fehler beim entschlüsseln, falsches Passwort?
+
+
+
+
+ Datei konnte nicht gelesen werden
+
+
+
+ MainView
+
+
+
+ Über
+
+
+
+
+ Datenbank exportieren
+
+
+
+
+ Datenbank importieren
+
+
+
+
+ Token hinzufügen
+
+
+
+
+ Hier ist nichts
+
+
+
+
+ Nach unten ziehen zum hinzufügen
+
+
+
+
+ Lösche
+
+
+
+
+ Token für
+
+
+
+
+ kopiert
+
+
+
+
+ Bearbeiten
+
+
+
+
+ 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
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ AddOTP
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ExportPage
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ MainView
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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();
}