diff --git a/.gitignore b/.gitignore index fb8a635..8573590 100644 --- a/.gitignore +++ b/.gitignore @@ -23,4 +23,4 @@ yarn-debug.log* yarn-error.log* -host.json +/src/__data__/host.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 0b5b87c..eea4bc6 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -28,8 +28,11 @@ "singleline", "submenu", "testid", + "totalin", + "totalout", "tsdoc", - "typeahead" + "typeahead", + "unsyncable" ], "i18next.defaultTranslatedLocale": "en", "i18next.i18nPaths": "public/locales" diff --git a/README.md b/README.md index 7eece89..814153d 100644 --- a/README.md +++ b/README.md @@ -57,8 +57,7 @@ Translation files are currently created manually in the [i18next JSON format](https://www.i18next.com/misc/json-format). You can find existing translations in [`public/locales`](public/locales). The -[English (GB) translation](public/locales/en/translation.json) is used as the -fallback. +[English (GB) translation](public/locales/en.json) is used as the fallback. Language files are named with [IETF language tags](https://en.wikipedia.org/wiki/IETF_language_tag). Short @@ -66,7 +65,8 @@ **IMPORTANT:** If you are adding a new language, you **must** add a listing for the language with the English name, native name, a country code (for the flag) -and the contributors list to [`languages.json`](languages.json). +and the contributors list to +[`src/__data__/languages.json`](src/__data__/languages.json). The library will automatically detect the language from your browser to use, but for the sake of testing, you can override it by running the following command in @@ -82,7 +82,7 @@ ### Providing host attribution To provide hosting credits in the sidebar footer, create the file -`host.json` in the project root with the following contents: +`host.json` in `src/__data__` with the following contents: ```json { diff --git a/public/locales/de.json b/public/locales/de.json new file mode 100644 index 0000000..b4b110b --- /dev/null +++ b/public/locales/de.json @@ -0,0 +1,91 @@ +{ + "app": { + "name": "KristWeb" + }, + + "nav": { + "connection": { + "online": "Online", + "offline": "Offline", + "connecting": "Verbindung wird hergestellt" + }, + + "search": "Krist-Netzwerk durchsuchen", + + "send": "Überweisen", + "request": "Zahlung anfordern" + }, + + "sidebar": { + "totalBalance": "Gesamtsaldo", + "guestIndicator": "Als Gast angemeldet", + "dashboard": "Dashboard", + "myWallets": "Meine Wallets", + "addressBook": "Adressbuch", + "transactions": "Transaktionen", + "names": "Namen", + "mining": "Mining", + "network": "Netzwerk", + "blocks": "Blöcke", + "statistics": "Statistiken", + "madeBy": "Entwickelt von <1>{{authorName}}", + "hostedBy": "Von <1>{{host}} bereitgestellt", + "github": "GitHub", + "credits": "Danksagung" + }, + + "credits": { + "madeBy": "Entwickelt von <1>{{authorName}}", + "supportersTitle": "Unterstützer", + "supportersDescription": "Dieses Projekt wurde ermöglicht durch die folgenden Personen:", + "supportButton": "KristWeb unterstützen", + "translatorsTitle": "Übersetzer", + "translatorsDescription": "Dieses Projekt wurde übersetzt von:", + "translateButton": "KristWeb übersetzen" + }, + + "dialog": { + "close": "Schließen" + }, + + "pagination": { + "justPage": "Seite {{page}}", + "pageWithTotal": "Seite {{page}} von {{total}}" + }, + + "loading": "Wird geladen...", + + "masterPassword": { + "dialogTitle": "Master-Passwort", + "passwordPlaceholder": "Master-Passwort", + "browseAsGuest": "Als Gast anmelden", + "createPassword": "Passwort erstellen", + "logIn": "Anmelden", + "forgotPassword": "Passwort vergessen?", + "intro": "Gebe ein Master-Passwort ein um deine Wallets zu verschlüsseln, oder melde dich als Gast an. <1>.", + "dontForgetPassword": "Bewahre dieses Passwort gut auf. Die gespeicherten Wallets können unter keinen Umständen wiederhergestellt werden!", + "loginIntro": "Gebe dein Master-Passwort ein um auf deine Wallets zuzugreifen, oder melde dich als Gast an.", + "learnMore": "mehr erfahren", + "errorPasswordRequired": "Fehlendes Passwort!", + "errorPasswordUnset": "Es wurde kein Master-Passwort gesetzt.", + "errorPasswordIncorrect": "Falsches Passwort.", + "errorUnknown": "Unbekannter Fehler.", + "helpWalletStorageTitle": "Hilfe: Speicherung von Wallets", + "helpWalletStorage": "Wenn du eine neue Wallet zu KristWeb hinzufügst, wird dessen Privatschlüssel mit Hilfe deines Master-Passworts verschlüsselt und in dem lokalen Speicher deines Browsers aufbewahrt.\nAlle Wallets sind mit genau diesem Master-Passwort verschlüsselt. Das bedeutet, dass du das Master-Passwort jedes Mal eingeben musst, wenn du KristWeb öffnest.\n Deine tatsächliche Krist-Wallet wird dabei nicht berührt.\nWenn du dich in KristWeb als Gast anmeldest, kannst du keine Wallets hinzufügen oder benutzen, sondern nur das Krist-Netzwerk durchsuchen." + }, + + "myWallets": { + "title": "Wallets", + "manageBackups": "Sicherungen verwalten", + "createWallet": "Wallet erstellen", + "addExistingWallet": "Bestehendes Wallet hinzufügen", + "searchPlaceholder": "Wallets durchsuchen...", + "categoryDropdownAll": "Alle Kategorien", + "columnLabel": "Kurzbeschreibung", + "columnAddress": "Adresse", + "columnBalance": "Saldo", + "columnNames": "Namen", + "columnCategory": "Kategorie", + "columnFirstSeen": "Erstmals erschienen" + } +} diff --git a/public/locales/de/translation.json b/public/locales/de/translation.json deleted file mode 100644 index b4b110b..0000000 --- a/public/locales/de/translation.json +++ /dev/null @@ -1,91 +0,0 @@ -{ - "app": { - "name": "KristWeb" - }, - - "nav": { - "connection": { - "online": "Online", - "offline": "Offline", - "connecting": "Verbindung wird hergestellt" - }, - - "search": "Krist-Netzwerk durchsuchen", - - "send": "Überweisen", - "request": "Zahlung anfordern" - }, - - "sidebar": { - "totalBalance": "Gesamtsaldo", - "guestIndicator": "Als Gast angemeldet", - "dashboard": "Dashboard", - "myWallets": "Meine Wallets", - "addressBook": "Adressbuch", - "transactions": "Transaktionen", - "names": "Namen", - "mining": "Mining", - "network": "Netzwerk", - "blocks": "Blöcke", - "statistics": "Statistiken", - "madeBy": "Entwickelt von <1>{{authorName}}", - "hostedBy": "Von <1>{{host}} bereitgestellt", - "github": "GitHub", - "credits": "Danksagung" - }, - - "credits": { - "madeBy": "Entwickelt von <1>{{authorName}}", - "supportersTitle": "Unterstützer", - "supportersDescription": "Dieses Projekt wurde ermöglicht durch die folgenden Personen:", - "supportButton": "KristWeb unterstützen", - "translatorsTitle": "Übersetzer", - "translatorsDescription": "Dieses Projekt wurde übersetzt von:", - "translateButton": "KristWeb übersetzen" - }, - - "dialog": { - "close": "Schließen" - }, - - "pagination": { - "justPage": "Seite {{page}}", - "pageWithTotal": "Seite {{page}} von {{total}}" - }, - - "loading": "Wird geladen...", - - "masterPassword": { - "dialogTitle": "Master-Passwort", - "passwordPlaceholder": "Master-Passwort", - "browseAsGuest": "Als Gast anmelden", - "createPassword": "Passwort erstellen", - "logIn": "Anmelden", - "forgotPassword": "Passwort vergessen?", - "intro": "Gebe ein Master-Passwort ein um deine Wallets zu verschlüsseln, oder melde dich als Gast an. <1>.", - "dontForgetPassword": "Bewahre dieses Passwort gut auf. Die gespeicherten Wallets können unter keinen Umständen wiederhergestellt werden!", - "loginIntro": "Gebe dein Master-Passwort ein um auf deine Wallets zuzugreifen, oder melde dich als Gast an.", - "learnMore": "mehr erfahren", - "errorPasswordRequired": "Fehlendes Passwort!", - "errorPasswordUnset": "Es wurde kein Master-Passwort gesetzt.", - "errorPasswordIncorrect": "Falsches Passwort.", - "errorUnknown": "Unbekannter Fehler.", - "helpWalletStorageTitle": "Hilfe: Speicherung von Wallets", - "helpWalletStorage": "Wenn du eine neue Wallet zu KristWeb hinzufügst, wird dessen Privatschlüssel mit Hilfe deines Master-Passworts verschlüsselt und in dem lokalen Speicher deines Browsers aufbewahrt.\nAlle Wallets sind mit genau diesem Master-Passwort verschlüsselt. Das bedeutet, dass du das Master-Passwort jedes Mal eingeben musst, wenn du KristWeb öffnest.\n Deine tatsächliche Krist-Wallet wird dabei nicht berührt.\nWenn du dich in KristWeb als Gast anmeldest, kannst du keine Wallets hinzufügen oder benutzen, sondern nur das Krist-Netzwerk durchsuchen." - }, - - "myWallets": { - "title": "Wallets", - "manageBackups": "Sicherungen verwalten", - "createWallet": "Wallet erstellen", - "addExistingWallet": "Bestehendes Wallet hinzufügen", - "searchPlaceholder": "Wallets durchsuchen...", - "categoryDropdownAll": "Alle Kategorien", - "columnLabel": "Kurzbeschreibung", - "columnAddress": "Adresse", - "columnBalance": "Saldo", - "columnNames": "Namen", - "columnCategory": "Kategorie", - "columnFirstSeen": "Erstmals erschienen" - } -} diff --git a/public/locales/en.json b/public/locales/en.json new file mode 100644 index 0000000..0a5e8a1 --- /dev/null +++ b/public/locales/en.json @@ -0,0 +1,214 @@ +{ + "app": { + "name": "KristWeb" + }, + + "nav": { + "connection": { + "online": "Online", + "offline": "Offline", + "connecting": "Connecting" + }, + + "search": "Search the Krist network", + + "send": "Send", + "request": "Request", + + "settings": "Settings" + }, + + "sidebar": { + "totalBalance": "Total Balance", + "guestIndicator": "Browsing as guest", + "dashboard": "Dashboard", + "myWallets": "My Wallets", + "addressBook": "Address Book", + "transactions": "Transactions", + "names": "Names", + "mining": "Mining", + "network": "Network", + "blocks": "Blocks", + "statistics": "Statistics", + "madeBy": "Made by <1>{{authorName}}", + "hostedBy": "Hosted by <1>{{host}}", + "github": "GitHub", + "credits": "Credits" + }, + + "dialog": { + "close": "Close", + "ok": "OK", + "cancel": "Cancel" + }, + + "pagination": { + "justPage": "Page {{page}}", + "pageWithTotal": "Page {{page}} of {{total}}" + }, + + "error": "Error", + "loading": "Loading...", + + "copy": "Copy to clipboard", + "copied": "Copied!", + + "typeahead": { + "emptyLabel": "No matches found.", + "paginationText": "Display additional results..." + }, + + "masterPassword": { + "dialogTitle": "Master password", + "passwordPlaceholder": "Master password", + "passwordConfirmPlaceholder": "Confirm master password", + "browseAsGuest": "Browse as guest", + "createPassword": "Create password", + "logIn": "Log in", + "forgotPassword": "Forgot password?", + "intro": "Enter a master password to encrypt your wallets, or browse KristWeb as a guest <1>.", + "intro2": "Enter a <1>master password to encrypt your wallet private keys. They will be saved in your browser's local storage, and you will be asked for the master password to decrypt them once per session.", + "dontForgetPassword": "Never forget this password. If you forget it, you will have to create a new password and add your wallets all over again.", + "loginIntro": "Enter a master password to access your wallets, or browse KristWeb as a guest.", + "learnMore": "learn more", + "errorPasswordRequired": "Password is required.", + "errorPasswordLength": "Must be at least 1 character.", + "errorPasswordUnset": "Master password has not been set up.", + "errorPasswordIncorrect": "Incorrect password.", + "errorPasswordInequal": "Passwords must match.", + "errorStorageCorrupt": "Wallet storage is corrupted.", + "errorNoPassword": "Master password is required.", + "errorUnknown": "Unknown error.", + "helpWalletStorageTitle": "Help: Wallet storage", + "helpWalletStorage": "When you add a wallet to KristWeb, the private key for the wallet is saved to your browser's local storage and encrypted with your master password.\nEvery wallet you save is encrypted using the same master password, and you will need to enter it every time you open KristWeb. Your actual Krist wallet is not modified in any way.\nWhen browsing KristWeb as a guest, you do not need to enter a master password, but it also means that you will not be able to add or use any wallets. You will still be able to explore the Krist network.", + "popoverTitle": "Decrypt wallets", + "popoverTitleEncrypt": "Encrypt wallets", + "popoverAuthoriseButton": "Authorise", + "popoverDescription": "Enter your master password to decrypt your wallets.", + "popoverDescriptionEncrypt": "Enter your master password to encrypt and decrypt your wallets.", + "forcedAuthWarning": "You were automatically logged in by an insecure debug setting." + }, + + "myWallets": { + "title": "Wallets", + "manageBackups": "Manage backups", + "createWallet": "Create wallet", + "addExistingWallet": "Add existing wallet", + "searchPlaceholder": "Search wallets...", + "categoryDropdownAll": "All categories", + "columnLabel": "Label", + "columnAddress": "Address", + "columnBalance": "Balance", + "columnNames": "Names", + "columnCategory": "Category", + "columnFirstSeen": "First Seen", + "nameCount": "{{count}} name", + "nameCount_plural": "{{count}} names", + "firstSeen": "First seen {{date}}" + }, + + "myTransactions": { + "title": "Transactions", + "searchPlaceholder": "Search transactions...", + "columnFrom": "From", + "columnTo": "To", + "columnValue": "Value", + "columnTime": "Time" + }, + + "addWallet": { + "dialogTitle": "Add wallet", + "dialogTitleCreate": "Create wallet", + "dialogOkAdd": "Add", + "dialogOkCreate": "Create", + + "walletLabel": "Wallet label", + "walletLabelPlaceholder": "Wallet label (optional)", + "walletLabelMaxLengthError": "No longer than 32 characters", + "walletLabelWhitespaceError": "Must not be only spaces", + + "walletCategory": "Wallet category", + "walletCategoryDropdownNone": "No category", + "walletCategoryDropdownNew": "New", + "walletCategoryDropdownNewPlaceholder": "Category name", + + "walletAddress": "Wallet address", + "walletUsername": "Wallet username", + "walletUsernamePlaceholder": "Wallet username", + "walletPassword": "Wallet password", + "walletPasswordPlaceholder": "Wallet password", + "walletPasswordWarning": "Make sure to save this somewhere <1>secure!", + "walletPasswordRegenerate": "Regenerate", + "walletPrivatekey": "Wallet private key", + "walletPrivatekeyPlaceholder": "Wallet private key", + + "advancedOptions": "Advanced options", + + "walletFormat": "Wallet format", + "walletFormatKristWallet": "KristWallet, KWallet (recommended)", + "walletFormatKristWalletUsernameAppendhashes": "KW-Username (appendhashes)", + "walletFormatKristWalletUsername": "KW-Username (pre-appendhashes)", + "walletFormatJwalelset": "jwalelset", + "walletFormatApi": "Raw/API (advanced users)", + + "walletSave": "Save this wallet in KristWeb", + + "messageSuccessAdd": "Added wallet successfully!", + "messageSuccessCreate": "Created wallet successfully!", + + "errorUnexpectedTitle": "Unexpected error", + "errorUnexpectedDescription": "There was an error while adding the wallet. See console for details.", + "errorDuplicateWalletTitle": "Wallet already exists", + "errorDuplicateWalletDescription": "You already have a wallet for that address." + }, + + "credits": { + "title": "Credits", + "madeBy": "Made by <1>{{authorName}}", + "supportersTitle": "Supporters", + "supportersDescription": "This project was made possible by the following amazing supporters:", + "supportButton": "Support KristWeb", + "translatorsTitle": "Translators", + "translatorsDescription": "This project was translated by the following amazing contributors:", + "translateButton": "Translate KristWeb", + "tmpim": "Created by tmpim" + }, + + "settings": { + "siteTitle": "Settings", + "title": "Settings", + + "menuLanguage": "Language", + "subMenuDebug": "Debug settings", + "advancedWalletFormats": "Advanced wallet formats", + "menuTranslations": "Translations", + + "subTitleTranslations": "Translations", + + "translations": { + "errorMissingLanguages": "The languages.json file seems to be missing. Was KristWeb compiled correctly?", + "errorNoKeys": "No translation keys", + + "columnLanguageCode": "Code", + "columnLanguage": "Language", + "columnKeys": "Keys", + "columnMissingKeys": "Missing keys", + "columnProgress": "Progress", + + "tableUntranslatedKeys": "Untranslated keys", + "columnKey": "Key", + "columnEnglishString": "English string", + + "exportCSV": "Export CSV" + } + }, + + "breadcrumb": { + "dashboard": "Dashboard", + "wallets": "Wallets", + + "settings": "Settings", + "settingsDebug": "Debug", + "settingsTranslations": "Translations" + } +} diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json deleted file mode 100644 index c70202e..0000000 --- a/public/locales/en/translation.json +++ /dev/null @@ -1,212 +0,0 @@ -{ - "app": { - "name": "KristWeb" - }, - - "nav": { - "connection": { - "online": "Online", - "offline": "Offline", - "connecting": "Connecting" - }, - - "search": "Search the Krist network", - - "send": "Send", - "request": "Request", - - "settings": "Settings" - }, - - "sidebar": { - "totalBalance": "Total Balance", - "guestIndicator": "Browsing as guest", - "dashboard": "Dashboard", - "myWallets": "My Wallets", - "addressBook": "Address Book", - "transactions": "Transactions", - "names": "Names", - "mining": "Mining", - "network": "Network", - "blocks": "Blocks", - "statistics": "Statistics", - "madeBy": "Made by <1>{{authorName}}", - "hostedBy": "Hosted by <1>{{host}}", - "github": "GitHub", - "credits": "Credits" - }, - - "dialog": { - "close": "Close", - "ok": "OK", - "cancel": "Cancel" - }, - - "pagination": { - "justPage": "Page {{page}}", - "pageWithTotal": "Page {{page}} of {{total}}" - }, - - "error": "Error", - "loading": "Loading...", - - "copy": "Copy to clipboard", - "copied": "Copied!", - - "typeahead": { - "emptyLabel": "No matches found.", - "paginationText": "Display additional results..." - }, - - "masterPassword": { - "dialogTitle": "Master password", - "passwordPlaceholder": "Master password", - "passwordConfirmPlaceholder": "Confirm master password", - "browseAsGuest": "Browse as guest", - "createPassword": "Create password", - "logIn": "Log in", - "forgotPassword": "Forgot password?", - "intro": "Enter a master password to encrypt your wallets, or browse KristWeb as a guest <1>.", - "intro2": "Enter a <1>master password to encrypt your wallet private keys. They will be saved in your browser's local storage, and you will be asked for the master password to decrypt them once per session.", - "dontForgetPassword": "Never forget this password. If you forget it, you will have to create a new password and add your wallets all over again.", - "loginIntro": "Enter a master password to access your wallets, or browse KristWeb as a guest.", - "learnMore": "learn more", - "errorPasswordRequired": "Password is required.", - "errorPasswordLength": "Must be at least 1 character.", - "errorPasswordUnset": "Master password has not been set up.", - "errorPasswordIncorrect": "Incorrect password.", - "errorPasswordInequal": "Passwords must match.", - "errorStorageCorrupt": "Wallet storage is corrupted.", - "errorNoPassword": "Master password is required.", - "errorUnknown": "Unknown error.", - "helpWalletStorageTitle": "Help: Wallet storage", - "helpWalletStorage": "When you add a wallet to KristWeb, the private key for the wallet is saved to your browser's local storage and encrypted with your master password.\nEvery wallet you save is encrypted using the same master password, and you will need to enter it every time you open KristWeb. Your actual Krist wallet is not modified in any way.\nWhen browsing KristWeb as a guest, you do not need to enter a master password, but it also means that you will not be able to add or use any wallets. You will still be able to explore the Krist network.", - "popoverTitle": "Decrypt wallets", - "popoverTitleEncrypt": "Encrypt wallets", - "popoverAuthoriseButton": "Authorise", - "popoverDescription": "Enter your master password to decrypt your wallets.", - "popoverDescriptionEncrypt": "Enter your master password to encrypt and decrypt your wallets.", - "forcedAuthWarning": "You were automatically logged in by an insecure debug setting." - }, - - "myWallets": { - "title": "Wallets", - "manageBackups": "Manage backups", - "createWallet": "Create wallet", - "addExistingWallet": "Add existing wallet", - "searchPlaceholder": "Search wallets...", - "categoryDropdownAll": "All categories", - "columnLabel": "Label", - "columnAddress": "Address", - "columnBalance": "Balance", - "columnNames": "Names", - "columnCategory": "Category", - "columnFirstSeen": "First Seen", - "nameCount": "{{count}} name", - "nameCount_plural": "{{count}} names", - "firstSeen": "First seen {{date}}" - }, - - "myTransactions": { - "title": "Transactions", - "searchPlaceholder": "Search transactions...", - "columnFrom": "From", - "columnTo": "To", - "columnValue": "Value", - "columnTime": "Time" - }, - - "addWallet": { - "dialogTitle": "Add wallet", - "dialogTitleCreate": "Create wallet", - "dialogOkAdd": "Add", - "dialogOkCreate": "Create", - - "walletLabel": "Wallet label", - "walletLabelPlaceholder": "Wallet label (optional)", - "walletLabelMaxLengthError": "No longer than 32 characters", - "walletLabelWhitespaceError": "Must not be only spaces", - - "walletCategory": "Wallet category", - "walletCategoryDropdownNone": "No category", - "walletCategoryDropdownNew": "New", - "walletCategoryDropdownNewPlaceholder": "Category name", - - "walletAddress": "Wallet address", - "walletUsername": "Wallet username", - "walletUsernamePlaceholder": "Wallet username", - "walletPassword": "Wallet password", - "walletPasswordPlaceholder": "Wallet password", - "walletPasswordWarning": "Make sure to save this somewhere <1>secure!", - "walletPasswordRegenerate": "Regenerate", - "walletPrivatekey": "Wallet private key", - "walletPrivatekeyPlaceholder": "Wallet private key", - - "advancedOptions": "Advanced options", - - "walletFormat": "Wallet format", - "walletFormatKristWallet": "KristWallet, KWallet (recommended)", - "walletFormatKristWalletUsernameAppendhashes": "KW-Username (appendhashes)", - "walletFormatKristWalletUsername": "KW-Username (pre-appendhashes)", - "walletFormatJwalelset": "jwalelset", - "walletFormatApi": "Raw/API (advanced users)", - - "walletSave": "Save this wallet in KristWeb", - - "messageSuccessAdd": "Added wallet successfully!", - "messageSuccessCreate": "Created wallet successfully!", - - "errorUnexpectedTitle": "Unexpected error", - "errorUnexpectedDescription": "There was an error while adding the wallet. See console for details." - }, - - "credits": { - "title": "Credits", - "madeBy": "Made by <1>{{authorName}}", - "supportersTitle": "Supporters", - "supportersDescription": "This project was made possible by the following amazing supporters:", - "supportButton": "Support KristWeb", - "translatorsTitle": "Translators", - "translatorsDescription": "This project was translated by the following amazing contributors:", - "translateButton": "Translate KristWeb", - "tmpim": "Created by tmpim" - }, - - "settings": { - "siteTitle": "Settings", - "title": "Settings", - - "menuLanguage": "Language", - "subMenuDebug": "Debug settings", - "advancedWalletFormats": "Advanced wallet formats", - "menuTranslations": "Translations", - - "subTitleTranslations": "Translations", - - "translations": { - "errorMissingLanguages": "The languages.json file seems to be missing. Was KristWeb compiled correctly?", - "errorNoKeys": "No translation keys", - - "columnLanguageCode": "Code", - "columnLanguage": "Language", - "columnKeys": "Keys", - "columnMissingKeys": "Missing keys", - "columnProgress": "Progress", - - "tableUntranslatedKeys": "Untranslated keys", - "columnKey": "Key", - "columnEnglishString": "English string", - - "exportCSV": "Export CSV" - } - }, - - "breadcrumb": { - "dashboard": "Dashboard", - "wallets": "Wallets", - - "settings": "Settings", - "settingsDebug": "Debug", - "settingsTranslations": "Translations" - } -} diff --git a/public/locales/fr.json b/public/locales/fr.json new file mode 100644 index 0000000..d6acffc --- /dev/null +++ b/public/locales/fr.json @@ -0,0 +1,109 @@ +{ + "app": { + "name": "KristWeb" + }, + + "nav": { + "connection": { + "online": "En ligne", + "offline": "Hors ligne", + "connecting": "En Connexion" + }, + + "search": "Rechercher sur le réseau Krist", + + "send": "Envoyer", + "request": "Requêter" + }, + + "sidebar": { + "totalBalance": "Solde Total", + "guestIndicator": "Navigation en tant qu'invité", + "dashboard": "Tableau de bord", + "myWallets": "Mes portefeuilles", + "addressBook": "Carnet d'adresses", + "transactions": "Transactions", + "names": "Noms", + "mining": "Minage", + "network": "Réseau", + "blocks": "Blocs", + "statistics": "Statistiques", + "madeBy": "Fait par <1>{{authorName}}", + "hostedBy": "Hébergé par <1>{{host}}", + "github": "GitHub", + "credits": "Crédits" + }, + + "dialog": { + "close": "Fermer" + }, + + "pagination": { + "justPage": "Page {{page}}", + "pageWithTotal": "Page {{page}} de {{total}}" + }, + + "loading": "Chargement...", + + "masterPassword": { + "dialogTitle": "Mot de passe maître", + "passwordPlaceholder": "Mot de passe maître", + "browseAsGuest": "Naviguer en tant qu'invité", + "createPassword": "Créer un mot de passe", + "logIn": "Connexion", + "forgotPassword": "Mot de passe oublié?", + "intro": "Entrez un mot de passe maître pour crypter vos portefeuilles ou parcourez KristWeb en tant qu'invité <1>.", + "dontForgetPassword": "N'oubliez jamais ce mot de passe. Si vous l'oubliez, vous devrez créer un nouveau mot de passe et ajouter à nouveau vos portefeuilles.", + "loginIntro": "Entrez un mot de passe maître pour accéder à vos portefeuilles ou parcourez KristWeb en tant qu'invité.", + "learnMore": "en savoir plus", + "errorPasswordRequired": "Mot de passe requis.", + "errorPasswordUnset": "Le mot de passe maître n'a pas été configuré.", + "errorPasswordIncorrect": "Mot de passe incorrect.", + "errorStorageCorrupt": "Le stockage du portefeuille est corrompu.", + "errorUnknown": "Erreur inconnue.", + "helpWalletStorageTitle": "Aide: stockage de portefeuille", + "helpWalletStorage": "Lorsque vous ajoutez un portefeuille à KristWeb, la clé privée du portefeuille est enregistrée dans le stockage local de votre navigateur et chiffrée avec votre mot de passe maître.\nChaque portefeuille que vous enregistrez est chiffré à l'aide du même mot de passe maître et vous devrez le saisir à chaque fois vous ouvrez KristWeb. Votre portefeuille Krist n'est en aucun cas modifié.\nLorsque vous naviguez sur KristWeb en tant qu'invité, vous n'avez pas besoin de saisir votre mot de passe maître, mais cela signifie également que vous ne pourrez pas ajouter ni utiliser vos portefeuilles. Vous pourrez toutefois explorer le réseau Krist." + }, + + "myWallets": { + "title": "Portefeuilles", + "manageBackups": "Gérer les sauvegardes", + "createWallet": "Créer un portefeuille", + "addExistingWallet": "Ajouter un portefeuille existant", + "searchPlaceholder": "Chercher un portefeuille...", + "categoryDropdownAll": "Toutes catégories", + "columnLabel": "Étiquette", + "columnAddress": "Addresse", + "columnBalance": "Solde", + "columnNames": "Noms", + "columnCategory": "Catégorie", + "columnFirstSeen": "Vu la première fois", + "nameCount": "{{count}} nom", + "nameCount_plural": "{{count}} noms", + "firstSeen": "Vu la première fois le {{date}}" + }, + + "myTransactions": { + "title": "Transactions", + "searchPlaceholder": "Chercher une transaction...", + "columnFrom": "De", + "columnTo": "À", + "columnValue": "Montant", + "columnTime": "Temps" + }, + + "addWallet": { + "dialogTitle": "Ajouter un portefeuille", + "dialogTitleCreate": "Créer un portefeuille" + }, + + "credits": { + "madeBy": "Fait par <1>{{authorName}}", + "supportersTitle": "Supporteurs", + "supportersDescription": "Ce projet a été rendu possible par les formidables supporters suivants:", + "supportButton": "Supporter KristWeb", + "translatorsTitle": "Traducteurs", + "translatorsDescription": "Ce projet a été traduit par les formidables contributeurs suivants:", + "translateButton": "Traduire KristWeb" + } +} diff --git a/public/locales/fr/translation.json b/public/locales/fr/translation.json deleted file mode 100644 index d6acffc..0000000 --- a/public/locales/fr/translation.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "app": { - "name": "KristWeb" - }, - - "nav": { - "connection": { - "online": "En ligne", - "offline": "Hors ligne", - "connecting": "En Connexion" - }, - - "search": "Rechercher sur le réseau Krist", - - "send": "Envoyer", - "request": "Requêter" - }, - - "sidebar": { - "totalBalance": "Solde Total", - "guestIndicator": "Navigation en tant qu'invité", - "dashboard": "Tableau de bord", - "myWallets": "Mes portefeuilles", - "addressBook": "Carnet d'adresses", - "transactions": "Transactions", - "names": "Noms", - "mining": "Minage", - "network": "Réseau", - "blocks": "Blocs", - "statistics": "Statistiques", - "madeBy": "Fait par <1>{{authorName}}", - "hostedBy": "Hébergé par <1>{{host}}", - "github": "GitHub", - "credits": "Crédits" - }, - - "dialog": { - "close": "Fermer" - }, - - "pagination": { - "justPage": "Page {{page}}", - "pageWithTotal": "Page {{page}} de {{total}}" - }, - - "loading": "Chargement...", - - "masterPassword": { - "dialogTitle": "Mot de passe maître", - "passwordPlaceholder": "Mot de passe maître", - "browseAsGuest": "Naviguer en tant qu'invité", - "createPassword": "Créer un mot de passe", - "logIn": "Connexion", - "forgotPassword": "Mot de passe oublié?", - "intro": "Entrez un mot de passe maître pour crypter vos portefeuilles ou parcourez KristWeb en tant qu'invité <1>.", - "dontForgetPassword": "N'oubliez jamais ce mot de passe. Si vous l'oubliez, vous devrez créer un nouveau mot de passe et ajouter à nouveau vos portefeuilles.", - "loginIntro": "Entrez un mot de passe maître pour accéder à vos portefeuilles ou parcourez KristWeb en tant qu'invité.", - "learnMore": "en savoir plus", - "errorPasswordRequired": "Mot de passe requis.", - "errorPasswordUnset": "Le mot de passe maître n'a pas été configuré.", - "errorPasswordIncorrect": "Mot de passe incorrect.", - "errorStorageCorrupt": "Le stockage du portefeuille est corrompu.", - "errorUnknown": "Erreur inconnue.", - "helpWalletStorageTitle": "Aide: stockage de portefeuille", - "helpWalletStorage": "Lorsque vous ajoutez un portefeuille à KristWeb, la clé privée du portefeuille est enregistrée dans le stockage local de votre navigateur et chiffrée avec votre mot de passe maître.\nChaque portefeuille que vous enregistrez est chiffré à l'aide du même mot de passe maître et vous devrez le saisir à chaque fois vous ouvrez KristWeb. Votre portefeuille Krist n'est en aucun cas modifié.\nLorsque vous naviguez sur KristWeb en tant qu'invité, vous n'avez pas besoin de saisir votre mot de passe maître, mais cela signifie également que vous ne pourrez pas ajouter ni utiliser vos portefeuilles. Vous pourrez toutefois explorer le réseau Krist." - }, - - "myWallets": { - "title": "Portefeuilles", - "manageBackups": "Gérer les sauvegardes", - "createWallet": "Créer un portefeuille", - "addExistingWallet": "Ajouter un portefeuille existant", - "searchPlaceholder": "Chercher un portefeuille...", - "categoryDropdownAll": "Toutes catégories", - "columnLabel": "Étiquette", - "columnAddress": "Addresse", - "columnBalance": "Solde", - "columnNames": "Noms", - "columnCategory": "Catégorie", - "columnFirstSeen": "Vu la première fois", - "nameCount": "{{count}} nom", - "nameCount_plural": "{{count}} noms", - "firstSeen": "Vu la première fois le {{date}}" - }, - - "myTransactions": { - "title": "Transactions", - "searchPlaceholder": "Chercher une transaction...", - "columnFrom": "De", - "columnTo": "À", - "columnValue": "Montant", - "columnTime": "Temps" - }, - - "addWallet": { - "dialogTitle": "Ajouter un portefeuille", - "dialogTitleCreate": "Créer un portefeuille" - }, - - "credits": { - "madeBy": "Fait par <1>{{authorName}}", - "supportersTitle": "Supporteurs", - "supportersDescription": "Ce projet a été rendu possible par les formidables supporters suivants:", - "supportButton": "Supporter KristWeb", - "translatorsTitle": "Traducteurs", - "translatorsDescription": "Ce projet a été traduit par les formidables contributeurs suivants:", - "translateButton": "Traduire KristWeb" - } -} diff --git a/public/locales/ja.json b/public/locales/ja.json new file mode 100644 index 0000000..e714ab1 --- /dev/null +++ b/public/locales/ja.json @@ -0,0 +1,61 @@ +{ + "nav": { + "connection": { + "online": "オンライン", + "offline": "オフライン", + "connecting": "接続中" + }, + + "search": "Kristネットワークを検索", + + "send": "送る", + "request": "受け取る" + }, + + "sidebar": { + "totalBalance": "合計残高", + "guestIndicator": "ゲストです", + "dashboard": "ダッシュボード", + "myWallets": "私の財布", + "addressBook": "住所録", + "transactions": "トランザクション", + "names": "名前", + "mining": "マイニング", + "network": "ネットワーク", + "blocks": "ブロック", + "statistics": "統計", + "madeBy": "作成者:<1>{{authorName}}", + "hostedBy": "ホスト:<1>{{host}}", + "credits": "謝辞" + }, + + "dialog": { + "close": "終了" + }, + + "pagination": { + "justPage": "Page {{page}}", + "pageWithTotal": "Page {{page}} of {{total}}" + }, + + "loading": "読み込み中...", + + "masterPassword": { + "dialogTitle": "マスターパスワード", + "passwordPlaceholder": "マスターパスワード", + "browseAsGuest": "ゲストとして閲覧", + "createPassword": "パスワードの作成", + "logIn": "ログイン", + "forgotPassword": "パスワードをお忘れの場合", + "intro": "新しいマスターパスワードを入力して財布を暗号化する、かゲストとしてKristWebを閲覧することができます<1>。", + "dontForgetPassword": "このパスワードは絶対に忘れないようにしましょう。忘れてしまった場合は、新しいパスワードを作成して、再度すべての財布を追加する必要があります。", + "loginIntro": "マスターパスワードを入力して財布を開けする、かゲストとしてKristWebを閲覧することができます。", + "learnMore": "詳細はこちら", + "errorPasswordRequired": "パスワードが必要です。", + "errorPasswordUnset": "マスターパスワードが設定されていません。", + "errorPasswordIncorrect": "パスワードが間違っています。", + "errorUnknown": "不明なエラーです。", + "helpWalletStorageTitle": "助けて:財布の保管", + "helpWalletStorage": "KristWebにウォレットを追加すると、ウォレットの秘密鍵はブラウザのローカルストレージに保存され、マスターパスワードで暗号化されます。\n保存されたすべてのウォレットは同じマスターパスワードで暗号化され、KristWebを開くたびに入力する必要があります。あなたの実際のウォレットは一切変更されません。\nゲストとしてKristWebを閲覧する場合、マスターパスワードを入力する必要はありませんが、ウォレットを追加したり使用したりすることはできません。あなたはまだKristネットワークを探索することができます。" + } +} diff --git a/public/locales/ja/translation.json b/public/locales/ja/translation.json deleted file mode 100644 index e714ab1..0000000 --- a/public/locales/ja/translation.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "nav": { - "connection": { - "online": "オンライン", - "offline": "オフライン", - "connecting": "接続中" - }, - - "search": "Kristネットワークを検索", - - "send": "送る", - "request": "受け取る" - }, - - "sidebar": { - "totalBalance": "合計残高", - "guestIndicator": "ゲストです", - "dashboard": "ダッシュボード", - "myWallets": "私の財布", - "addressBook": "住所録", - "transactions": "トランザクション", - "names": "名前", - "mining": "マイニング", - "network": "ネットワーク", - "blocks": "ブロック", - "statistics": "統計", - "madeBy": "作成者:<1>{{authorName}}", - "hostedBy": "ホスト:<1>{{host}}", - "credits": "謝辞" - }, - - "dialog": { - "close": "終了" - }, - - "pagination": { - "justPage": "Page {{page}}", - "pageWithTotal": "Page {{page}} of {{total}}" - }, - - "loading": "読み込み中...", - - "masterPassword": { - "dialogTitle": "マスターパスワード", - "passwordPlaceholder": "マスターパスワード", - "browseAsGuest": "ゲストとして閲覧", - "createPassword": "パスワードの作成", - "logIn": "ログイン", - "forgotPassword": "パスワードをお忘れの場合", - "intro": "新しいマスターパスワードを入力して財布を暗号化する、かゲストとしてKristWebを閲覧することができます<1>。", - "dontForgetPassword": "このパスワードは絶対に忘れないようにしましょう。忘れてしまった場合は、新しいパスワードを作成して、再度すべての財布を追加する必要があります。", - "loginIntro": "マスターパスワードを入力して財布を開けする、かゲストとしてKristWebを閲覧することができます。", - "learnMore": "詳細はこちら", - "errorPasswordRequired": "パスワードが必要です。", - "errorPasswordUnset": "マスターパスワードが設定されていません。", - "errorPasswordIncorrect": "パスワードが間違っています。", - "errorUnknown": "不明なエラーです。", - "helpWalletStorageTitle": "助けて:財布の保管", - "helpWalletStorage": "KristWebにウォレットを追加すると、ウォレットの秘密鍵はブラウザのローカルストレージに保存され、マスターパスワードで暗号化されます。\n保存されたすべてのウォレットは同じマスターパスワードで暗号化され、KristWebを開くたびに入力する必要があります。あなたの実際のウォレットは一切変更されません。\nゲストとしてKristWebを閲覧する場合、マスターパスワードを入力する必要はありませんが、ウォレットを追加したり使用したりすることはできません。あなたはまだKristネットワークを探索することができます。" - } -} diff --git a/public/locales/vi.json b/public/locales/vi.json new file mode 100644 index 0000000..70718be --- /dev/null +++ b/public/locales/vi.json @@ -0,0 +1,109 @@ +{ + "app": { + "name": "KristWeb" + }, + + "nav": { + "connection": { + "online": "Trực tuyến", + "offline": "Ngoại tuyến", + "connecting": "Đang kết nối" + }, + + "search": "Tiềm kiếm mạng Krist", + + "send": "Gửi", + "request": "Yêu cầu" + }, + + "sidebar": { + "totalBalance": "Tổng số dư", + "guestIndicator": "Đang duyệt với tư cách khách", + "dashboard": "Tổng quat", + "myWallets": "Những ví của bạn", + "addressBook": "Sổ địa chỉ", + "transactions": "Giao dịch", + "names": "Tên miền", + "mining": "Khai thác", + "network": "Mạng lưới", + "blocks": "Khối", + "statistics": "Thống kê", + "madeBy": "Thực hiện bởi <1>{{authorName}}", + "hostedBy": "Được lưu trữ bởi <1>{{host}}", + "github": "GitHub", + "credits": "Ghi công" + }, + + "dialog": { + "close": "Đóng" + }, + + "pagination": { + "justPage": "Trang {{page}}", + "pageWithTotal": "Trang {{page}} trên {{total}}" + }, + + "loading": "Đang tải...", + + "masterPassword": { + "dialogTitle": "Mật khẩu chính", + "passwordPlaceholder": "Mật khẩu chính", + "browseAsGuest": "Duyệt với tư cách khách", + "createPassword": "Tạo mật khẩu", + "logIn": "Đăng nhập", + "forgotPassword": "Bạn quên mật khẩu?", + "intro": "Nhập mật khẩu chính để mã hóa ví của bạn hoặc sử dụng KristWeb với tư cách khách. <1>.", + "dontForgetPassword": "Không bao giờ quên mật khẩu này. Nếu bạn quên nó, bạn sẽ phải tạo một mật khẩu chánh mới và thêm ví của bạn trên một lần nữa.", + "loginIntro": "Nhập mật khẩu chính để truy cập ví của bạn hoặc duyệt KristWeb với tư cách khách.", + "learnMore": "Tìm hiểu thêm", + "errorPasswordRequired": "Mật khẩu là bắc buộc.", + "errorPasswordUnset": "Mậc khẩu chính chưa đuộc thiết lạp.", + "errorPasswordIncorrect": "Mật khẩu không đúng.", + "errorStorageCorrupt": "Chỗ lưu trữ ví bị hỏng.", + "errorUnknown": "Lỗi không xác định.", + "helpWalletStorageTitle": "Trợ giúp: Lưu trữ ví", + "helpWalletStorage": "Khi bạn thêm ví vào KristWeb, mã khóa bí mật của ví sẽ được lưu vào bộ nhớ cục bộ của trình duyệt và được mã hóa bằng mật khẩu chính của bạn.\nMọi ví của bạn lưu đều được mã hóa bằng cùng một mật khẩu chính và bạn sẽ cần nhập mật khẩu đó mỗi khi mở KristWeb. Ví Krist thực tế của bạn không được sửa đổi theo bất kỳ cách nào.\nKhi duyệt KristWeb với tư cách khách, bạn không cần phải nhập mật khẩu chính, nhưng điều đó cũng có nghĩa là bạn sẽ không thể thêm hoặc sử dụng bất cứ ví nào. Bạn vẫn có thể khám phá mạng Krist." + }, + + "myWallets": { + "title": "Ví", + "manageBackups": "Quản lý các bản sao lưu", + "createWallet": "Tạo ví", + "addExistingWallet": "Thêm ví hiện có", + "searchPlaceholder": "Tìm ví...", + "categoryDropdownAll": "Tất cả danh mục", + "columnLabel": "Nhãn", + "columnAddress": "Địa chỉ", + "columnBalance": "Só dư", + "columnNames": "Tên miền", + "columnCategory": "Danh mục", + "columnFirstSeen": "Lần đầu tiên nhìn thấy", + "nameCount": "{{count}} tên miền", + "nameCount_plural": "{{count}} tên miền", + "firstSeen": "Lần đầu tiên nhìn thấy {{date}}" + }, + + "myTransactions": { + "title": "Giao dịch", + "searchPlaceholder": "Tìm kiếm các giao dịch...", + "columnFrom": "Từ", + "columnTo": "Đến", + "columnValue": "Giá trị", + "columnTime": "Giờ" + }, + + "addWallet": { + "dialogTitle": "Thêm ví", + "dialogTitleCreate": "Tạo ví" + }, + + "credits": { + "madeBy": "Thực hiện bởi <1>{{authorName}}", + "supportersTitle": "Người ủng hộ", + "supportersDescription": "Dự án này đã được thực hiện bởi những người ủng hộ tuyệt vời sau đây:", + "supportButton": "Ủng hộ KristWeb", + "translatorsTitle": "Người dịch", + "translatorsDescription": "Dự án này đã được dịch bởi những người đóng góp tuyệt vời sau đây:", + "translateButton": "Dịch KristWeb" + } +} diff --git a/public/locales/vi/translation.json b/public/locales/vi/translation.json deleted file mode 100644 index 70718be..0000000 --- a/public/locales/vi/translation.json +++ /dev/null @@ -1,109 +0,0 @@ -{ - "app": { - "name": "KristWeb" - }, - - "nav": { - "connection": { - "online": "Trực tuyến", - "offline": "Ngoại tuyến", - "connecting": "Đang kết nối" - }, - - "search": "Tiềm kiếm mạng Krist", - - "send": "Gửi", - "request": "Yêu cầu" - }, - - "sidebar": { - "totalBalance": "Tổng số dư", - "guestIndicator": "Đang duyệt với tư cách khách", - "dashboard": "Tổng quat", - "myWallets": "Những ví của bạn", - "addressBook": "Sổ địa chỉ", - "transactions": "Giao dịch", - "names": "Tên miền", - "mining": "Khai thác", - "network": "Mạng lưới", - "blocks": "Khối", - "statistics": "Thống kê", - "madeBy": "Thực hiện bởi <1>{{authorName}}", - "hostedBy": "Được lưu trữ bởi <1>{{host}}", - "github": "GitHub", - "credits": "Ghi công" - }, - - "dialog": { - "close": "Đóng" - }, - - "pagination": { - "justPage": "Trang {{page}}", - "pageWithTotal": "Trang {{page}} trên {{total}}" - }, - - "loading": "Đang tải...", - - "masterPassword": { - "dialogTitle": "Mật khẩu chính", - "passwordPlaceholder": "Mật khẩu chính", - "browseAsGuest": "Duyệt với tư cách khách", - "createPassword": "Tạo mật khẩu", - "logIn": "Đăng nhập", - "forgotPassword": "Bạn quên mật khẩu?", - "intro": "Nhập mật khẩu chính để mã hóa ví của bạn hoặc sử dụng KristWeb với tư cách khách. <1>.", - "dontForgetPassword": "Không bao giờ quên mật khẩu này. Nếu bạn quên nó, bạn sẽ phải tạo một mật khẩu chánh mới và thêm ví của bạn trên một lần nữa.", - "loginIntro": "Nhập mật khẩu chính để truy cập ví của bạn hoặc duyệt KristWeb với tư cách khách.", - "learnMore": "Tìm hiểu thêm", - "errorPasswordRequired": "Mật khẩu là bắc buộc.", - "errorPasswordUnset": "Mậc khẩu chính chưa đuộc thiết lạp.", - "errorPasswordIncorrect": "Mật khẩu không đúng.", - "errorStorageCorrupt": "Chỗ lưu trữ ví bị hỏng.", - "errorUnknown": "Lỗi không xác định.", - "helpWalletStorageTitle": "Trợ giúp: Lưu trữ ví", - "helpWalletStorage": "Khi bạn thêm ví vào KristWeb, mã khóa bí mật của ví sẽ được lưu vào bộ nhớ cục bộ của trình duyệt và được mã hóa bằng mật khẩu chính của bạn.\nMọi ví của bạn lưu đều được mã hóa bằng cùng một mật khẩu chính và bạn sẽ cần nhập mật khẩu đó mỗi khi mở KristWeb. Ví Krist thực tế của bạn không được sửa đổi theo bất kỳ cách nào.\nKhi duyệt KristWeb với tư cách khách, bạn không cần phải nhập mật khẩu chính, nhưng điều đó cũng có nghĩa là bạn sẽ không thể thêm hoặc sử dụng bất cứ ví nào. Bạn vẫn có thể khám phá mạng Krist." - }, - - "myWallets": { - "title": "Ví", - "manageBackups": "Quản lý các bản sao lưu", - "createWallet": "Tạo ví", - "addExistingWallet": "Thêm ví hiện có", - "searchPlaceholder": "Tìm ví...", - "categoryDropdownAll": "Tất cả danh mục", - "columnLabel": "Nhãn", - "columnAddress": "Địa chỉ", - "columnBalance": "Só dư", - "columnNames": "Tên miền", - "columnCategory": "Danh mục", - "columnFirstSeen": "Lần đầu tiên nhìn thấy", - "nameCount": "{{count}} tên miền", - "nameCount_plural": "{{count}} tên miền", - "firstSeen": "Lần đầu tiên nhìn thấy {{date}}" - }, - - "myTransactions": { - "title": "Giao dịch", - "searchPlaceholder": "Tìm kiếm các giao dịch...", - "columnFrom": "Từ", - "columnTo": "Đến", - "columnValue": "Giá trị", - "columnTime": "Giờ" - }, - - "addWallet": { - "dialogTitle": "Thêm ví", - "dialogTitleCreate": "Tạo ví" - }, - - "credits": { - "madeBy": "Thực hiện bởi <1>{{authorName}}", - "supportersTitle": "Người ủng hộ", - "supportersDescription": "Dự án này đã được thực hiện bởi những người ủng hộ tuyệt vời sau đây:", - "supportButton": "Ủng hộ KristWeb", - "translatorsTitle": "Người dịch", - "translatorsDescription": "Dự án này đã được dịch bởi những người đóng góp tuyệt vời sau đây:", - "translateButton": "Dịch KristWeb" - } -} diff --git a/src/App.tsx b/src/App.tsx index 485f100..7352243 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -7,6 +7,7 @@ import rootReducer from "./store/reducers/RootReducer"; import { getInitialWalletManagerState } from "./store/reducers/WalletManagerReducer"; +import { getInitialWalletsState } from "./store/reducers/WalletsReducer"; import { getInitialSettingsState } from "./store/reducers/SettingsReducer"; // Set up localisation @@ -14,12 +15,14 @@ import "./App.less"; import { AppLayout } from "./layout/AppLayout"; +import { SyncWallets } from "./krist/wallets/SyncWallets"; import { ForcedAuth } from "./components/auth/ForcedAuth"; export const store = createStore( rootReducer, { walletManager: getInitialWalletManagerState(), + wallets: getInitialWalletsState(), settings: getInitialSettingsState() }, devToolsEnhancer({}) @@ -31,6 +34,7 @@ + diff --git a/src/krist/api/lookup.ts b/src/krist/api/lookup.ts new file mode 100644 index 0000000..0e7a4a2 --- /dev/null +++ b/src/krist/api/lookup.ts @@ -0,0 +1,39 @@ +import { APIResponse, KristAddress } from "./types"; + +import packageJson from "../../../package.json"; + +interface LookupAddressesResponse { + found: number; + notFound: number; + addresses: Record; +} + +interface KristAddressWithNames extends KristAddress { + names?: number; +} + +export async function lookupAddresses(addresses: string[], fetchNames?: boolean): Promise> { + if (!addresses || addresses.length === 0) return {}; + + const syncNode = packageJson.defaultSyncNode; // TODO: support alt nodes + + try { + const res = await fetch( + syncNode + + "/lookup/addresses/" + + encodeURIComponent(addresses.join(",")) + + (fetchNames ? "?fetchNames" : "") + ); + if (!res.ok || res.status !== 200) throw new Error(res.statusText); + + const data: APIResponse = await res.json(); + if (!data.ok || data.error) throw new Error(data.error); + + return data.addresses; + } catch (err) { + // TODO: proper error handling function for API requests + console.error(err); + } + + return {}; +} diff --git a/src/krist/api/types.ts b/src/krist/api/types.ts new file mode 100644 index 0000000..1d6b4dc --- /dev/null +++ b/src/krist/api/types.ts @@ -0,0 +1,14 @@ +export interface KristAddress { + address: string; + + balance: number; + totalin?: number; + totalout?: number; + + first_seen: string; +} + +export type APIResponse> = T & { + ok: boolean; + error?: string; +} diff --git a/src/krist/wallets/SyncWallets.tsx b/src/krist/wallets/SyncWallets.tsx new file mode 100644 index 0000000..8ce8d30 --- /dev/null +++ b/src/krist/wallets/SyncWallets.tsx @@ -0,0 +1,24 @@ +import { useState, useEffect } from "react"; + +import { useDispatch, useSelector, shallowEqual } from "react-redux"; +import { RootState } from "../../store"; + +import { syncWallets } from "./Wallet"; + +/** Sync the wallets with the Krist node on startup. */ +export function SyncWallets(): JSX.Element | null { + const { wallets } = useSelector((s: RootState) => s.wallets, shallowEqual); + const dispatch = useDispatch(); + + const [synced, setSynced] = useState(false); + + useEffect(() => { + if (synced) return; + setSynced(true); + + // TODO: show errors to the user? + syncWallets(dispatch, wallets).catch(console.error); + }, [synced]); + + return null; +} diff --git a/src/krist/wallets/Wallet.ts b/src/krist/wallets/Wallet.ts index 51a9d7f..95cfbc5 100644 --- a/src/krist/wallets/Wallet.ts +++ b/src/krist/wallets/Wallet.ts @@ -5,6 +5,8 @@ import { aesGcmEncrypt } from "../../utils/crypto"; +import { lookupAddresses } from "../api/lookup"; + import { AppDispatch } from "../../App"; import * as actions from "../../store/actions/WalletsActions"; import { WalletMap } from "../../store/reducers/WalletsReducer"; @@ -27,12 +29,14 @@ address: string; balance?: number; names?: number; - firstSeen?: Date; - lastSynced?: Date; + firstSeen?: string; + lastSynced?: string; + + dontSave?: boolean; // Used to avoid saving when syncing } /** Properties of Wallet that are required to create a new wallet. */ -export type WalletNewKeys = "label" | "category" | "username" | "format"; +export type WalletNewKeys = "label" | "category" | "username" | "format" | "dontSave"; export type WalletNew = Pick; /** Properties of Wallet that are allowed to be updated. */ @@ -76,9 +80,8 @@ } } -/** Loads all available wallets from local storage and dispatches them to the - * Redux store. */ -export async function loadWallets(dispatch: AppDispatch): Promise { +/** Loads all available wallets from local storage. */ +export function loadWallets(): WalletMap { // Find all `wallet2` keys from local storage const keysToLoad = Object.keys(localStorage) .map(extractWalletKey) @@ -89,7 +92,41 @@ // Convert to map with wallet IDs const walletMap: WalletMap = wallets.reduce((obj, w) => ({ ...obj, [w.id]: w }), {}); - dispatch(actions.loadWallets(walletMap)); + return walletMap; +} + +/** Saves a wallet to local storage, unless it has `dontSave` set. */ +export function saveWallet(wallet: Wallet): void { + if (wallet.dontSave) return; + + const key = getWalletKey(wallet); + const serialised = JSON.stringify(wallet); + localStorage.setItem(key, serialised); +} + +/** Sync the data for all the wallets from the sync node, save it to local + * storage, and dispatch the changes to the Redux store. */ +export async function syncWallets(dispatch: AppDispatch, wallets: WalletMap): Promise { + const syncTime = new Date(); + + const addresses = Object.values(wallets).map(w => w.address); + const lookupResults = await lookupAddresses(addresses, true); + const updatedWallets = Object.entries(wallets).map(([_, wallet]) => { + const address = lookupResults[wallet.address]; + if (!address) return wallet; // Skip unsyncable wallets + + return { + ...wallet, + balance: address.balance, + names: address.names, + firstSeen: address.first_seen, + lastSynced: syncTime + }; + }).reduce((o, wallet) => ({ ...o, [wallet.id]: wallet }), {}); + + Object.values(updatedWallets).forEach(w => saveWallet(w as Wallet)); + + dispatch(actions.syncWallets(updatedWallets)); } /** Adds a new wallet, encrypting its privatekey and password, saving it to @@ -121,17 +158,21 @@ const encPrivatekey = await aesGcmEncrypt(privatekey, masterPassword); const newWallet = { - ...wallet, id, address, - encPassword, encPrivatekey + + label: wallet.label, + category: wallet.category, + + username: wallet.username, + encPassword, + encPrivatekey, + format: wallet.format, + + ...(save ? {} : { dontSave: true }) }; // Save the wallet to local storage if wanted - if (save) { - const key = getWalletKey(newWallet); - const serialised = JSON.stringify(newWallet); - localStorage.setItem(key, serialised); - } + if (save) saveWallet(newWallet); // Dispatch the changes to the redux store dispatch(actions.addWallet(newWallet)); diff --git a/src/layout/sidebar/Sidebar.tsx b/src/layout/sidebar/Sidebar.tsx index fdbcc69..4bc5506 100644 --- a/src/layout/sidebar/Sidebar.tsx +++ b/src/layout/sidebar/Sidebar.tsx @@ -57,7 +57,7 @@ width={240} className={"site-sidebar " + (collapsed ? "collapsed" : "")} > - + {getSidebarItems(t)} diff --git a/src/layout/sidebar/SidebarTotalBalance.tsx b/src/layout/sidebar/SidebarTotalBalance.tsx index 79ef446..68ffdf4 100644 --- a/src/layout/sidebar/SidebarTotalBalance.tsx +++ b/src/layout/sidebar/SidebarTotalBalance.tsx @@ -1,11 +1,19 @@ import React from "react"; import { useTranslation } from "react-i18next"; +import { useSelector, shallowEqual } from "react-redux"; +import { RootState } from "../../store"; + import { KristValue } from "../../components/KristValue"; -export function SidebarTotalBalance({ balance }: { balance: number }): JSX.Element { +export function SidebarTotalBalance(): JSX.Element { const { t } = useTranslation(); + const { wallets } = useSelector((s: RootState) => s.wallets, shallowEqual); + const balance = Object.values(wallets) + .filter(w => w.balance !== undefined) + .reduce((acc, w) => acc + w.balance!, 0); + return (
{t("sidebar.totalBalance")}
diff --git a/src/pages/DashboardPage.tsx b/src/pages/DashboardPage.tsx index 06670b1..c6dfa7e 100644 --- a/src/pages/DashboardPage.tsx +++ b/src/pages/DashboardPage.tsx @@ -1,20 +1,28 @@ import React from "react"; import { Button, message } from "antd"; -import { useSelector, shallowEqual } from "react-redux"; +import { useDispatch, useSelector, shallowEqual } from "react-redux"; import { RootState } from "../store"; +import { syncWallets } from "../krist/wallets/Wallet"; + import { PageLayout } from "../layout/PageLayout"; import { AuthorisedAction } from "../components/auth/AuthorisedAction"; export function DashboardPage(): JSX.Element { const { isAuthed, hasMasterPassword } = useSelector((s: RootState) => s.walletManager, shallowEqual); + const { wallets } = useSelector((s: RootState) => s.wallets, shallowEqual); + const dispatch = useDispatch(); return {/* TODO */}

Is authed: {isAuthed ? "yes" : "no"}

Has master password: {hasMasterPassword ? "yes" : "no"}

+ + +

+