From f5c79e33cd6b17c5f7293da7c786b933aaa5b82f Mon Sep 17 00:00:00 2001 From: Anurag Lint Date: Mon, 16 Sep 2024 20:25:35 +0200 Subject: [PATCH 1/4] Translation implementation and translations for WelcomeDialog and Transactions Tab --- .../sparrowwallet/sparrow/AppController.java | 37 +++++++- .../sparrowwallet/sparrow/AppServices.java | 3 +- .../sparrowwallet/sparrow/SparrowDesktop.java | 11 ++- .../sparrow/WelcomeController.java | 89 +++++++++++++++---- .../sparrowwallet/sparrow/WelcomeDialog.java | 39 ++++++-- .../sparrow/control/CoinTreeTable.java | 7 +- .../control/TransactionsTreeTable.java | 9 +- .../event/LanguageChangedInWelcomeEvent.java | 15 ++++ .../sparrowwallet/sparrow/i18n/Language.java | 25 ++++++ .../sparrow/i18n/LanguagesManager.java | 27 ++++++ .../com/sparrowwallet/sparrow/io/Config.java | 11 +++ .../sparrow/net/BatchedElectrumServerRpc.java | 9 +- .../sparrow/net/SimpleElectrumServerRpc.java | 9 +- .../wallet/TransactionsController.java | 9 +- .../sparrow/wallet/WalletController.java | 3 +- .../com/sparrowwallet/sparrow/app.fxml | 18 ++++ .../sparrow/wallet/transactions.fxml | 8 +- .../sparrowwallet/sparrow/wallet/wallet.fxml | 2 +- .../com/sparrowwallet/sparrow/welcome.fxml | 46 +++++----- .../resources/i18n/messages_en.properties | 57 ++++++++++++ .../resources/i18n/messages_es.properties | 57 ++++++++++++ 21 files changed, 413 insertions(+), 78 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/sparrow/event/LanguageChangedInWelcomeEvent.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/i18n/Language.java create mode 100644 src/main/java/com/sparrowwallet/sparrow/i18n/LanguagesManager.java create mode 100644 src/main/resources/i18n/messages_en.properties create mode 100644 src/main/resources/i18n/messages_es.properties diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index a95a3f485..fe271af02 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -18,6 +18,8 @@ import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; +import com.sparrowwallet.sparrow.i18n.Language; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.io.*; import com.sparrowwallet.sparrow.io.bbqr.BBQR; import com.sparrowwallet.sparrow.io.bbqr.BBQRType; @@ -138,6 +140,9 @@ public class AppController implements Initializable { @FXML private ToggleGroup theme; + @FXML + private ToggleGroup language; + @FXML private CheckMenuItem openWalletsInNewWindows; private static final BooleanProperty openWalletsInNewWindowsProperty = new SimpleBooleanProperty(); @@ -373,6 +378,15 @@ void initializeView() { selectedThemeToggle.ifPresent(toggle -> theme.selectToggle(toggle)); setTheme(null); + Language configLanguage = Config.get().getLanguage(); + if(configLanguage == null) { + configLanguage = LanguagesManager.DEFAULT_LANGUAGE; + Config.get().setLanguage(configLanguage); + } + final Language selectedLanguage = configLanguage; + Optional selectedLanguageToggle = language.getToggles().stream().filter(toggle -> selectedLanguage.equals(toggle.getUserData())).findFirst(); + selectedLanguageToggle.ifPresent(toggle -> language.selectToggle(toggle)); + openWalletsInNewWindowsProperty.set(Config.get().isOpenWalletsInNewWindows()); openWalletsInNewWindows.selectedProperty().bindBidirectional(openWalletsInNewWindowsProperty); hideEmptyUsedAddressesProperty.set(Config.get().isHideEmptyUsedAddresses()); @@ -488,7 +502,7 @@ private void setNetworkLabel() { } public void showIntroduction(ActionEvent event) { - WelcomeDialog welcomeDialog = new WelcomeDialog(); + WelcomeDialog welcomeDialog = new WelcomeDialog(false); welcomeDialog.initOwner(rootStack.getScene().getWindow()); Optional optionalMode = welcomeDialog.showAndWait(); if(optionalMode.isPresent() && optionalMode.get().equals(Mode.ONLINE)) { @@ -996,13 +1010,19 @@ public void restartInHome(ActionEvent event) { } } + public void restart(ActionEvent event) { + restart(event, (Network) null); + } + public void restart(ActionEvent event, Network network) { if(System.getProperty(JPACKAGE_APP_PATH) == null) { throw new IllegalStateException("Property " + JPACKAGE_APP_PATH + " is not present"); } Args args = getRestartArgs(); - args.network = network; + if(network != null) { + args.network = network; + } restart(event, args); } @@ -1760,7 +1780,7 @@ public WalletForm addWalletSubTab(TabPane subTabs, Storage storage, Wallet walle subTabLabel.setTooltip(new Tooltip(label)); } subTab.setGraphic(subTabLabel); - FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml")); + FXMLLoader walletLoader = new FXMLLoader(getClass().getResource("wallet/wallet.fxml"), LanguagesManager.getResourceBundle()); subTab.setContent(walletLoader.load()); WalletController controller = walletLoader.getController(); @@ -2337,6 +2357,15 @@ public void setTheme(ActionEvent event) { EventManager.get().post(new ThemeChangedEvent(selectedTheme)); } + public void setLanguage(ActionEvent event) { + Language selectedLanguage = (Language)language.getSelectedToggle().getUserData(); + if(Config.get().getLanguage() != selectedLanguage) { + Config.get().setLanguage(selectedLanguage); + } + + restart(event); + } + private void serverToggleStartAnimation() { Node thumbArea = serverToggle.lookup(".thumb-area"); if(thumbArea != null) { @@ -3135,4 +3164,4 @@ public void walletConfigChanged(WalletConfigChangedEvent event) { } } } -} +} \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/AppServices.java b/src/main/java/com/sparrowwallet/sparrow/AppServices.java index cd4418e31..fa75e3e11 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppServices.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppServices.java @@ -14,6 +14,7 @@ import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.sparrow.control.WalletPasswordDialog; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.net.Auth47; import com.sparrowwallet.drongo.protocol.BlockHeader; import com.sparrowwallet.drongo.protocol.ScriptType; @@ -551,7 +552,7 @@ public static HostAndPort getTorProxy() { public static AppController newAppWindow(Stage stage) { try { - FXMLLoader appLoader = new FXMLLoader(AppServices.class.getResource("app.fxml")); + FXMLLoader appLoader = new FXMLLoader(AppServices.class.getResource("app.fxml"), LanguagesManager.getResourceBundle()); Parent root = appLoader.load(); AppController appController = appLoader.getController(); diff --git a/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java b/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java index 8002db822..f2b7c7aa1 100644 --- a/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java +++ b/src/main/java/com/sparrowwallet/sparrow/SparrowDesktop.java @@ -5,6 +5,8 @@ import com.sparrowwallet.sparrow.control.WalletIcon; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands; +import com.sparrowwallet.sparrow.i18n.Language; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.net.PublicElectrumServer; @@ -53,7 +55,7 @@ public void start(Stage stage) throws Exception { boolean createNewWallet = false; Mode mode = Config.get().getMode(); if(mode == null) { - WelcomeDialog welcomeDialog = new WelcomeDialog(); + WelcomeDialog welcomeDialog = new WelcomeDialog(true); Optional optionalMode = welcomeDialog.showAndWait(); if(optionalMode.isPresent()) { mode = optionalMode.get(); @@ -87,6 +89,13 @@ public void start(Stage stage) throws Exception { mainStage.setHeight(Config.get().getAppHeight()); } + Language configLanguage = Config.get().getLanguage(); + if(configLanguage == null) { + configLanguage = LanguagesManager.DEFAULT_LANGUAGE; + Config.get().setLanguage(configLanguage); + } + LanguagesManager.loadLanguage(configLanguage.getCode()); + AppController appController = AppServices.newAppWindow(stage); final boolean showNewWallet = createNewWallet; diff --git a/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java b/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java index 1a00b2ad0..6b9967355 100644 --- a/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java +++ b/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java @@ -1,22 +1,30 @@ package com.sparrowwallet.sparrow; +import com.google.common.eventbus.Subscribe; import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch; +import com.sparrowwallet.sparrow.event.LanguageChangedInWelcomeEvent; +import com.sparrowwallet.sparrow.i18n.Language; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; +import com.sparrowwallet.sparrow.io.Config; import javafx.animation.PauseTransition; -import javafx.application.Platform; -import javafx.beans.value.ChangeListener; -import javafx.beans.value.ObservableValue; -import javafx.event.ActionEvent; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; import javafx.event.Event; -import javafx.event.EventHandler; -import javafx.event.EventType; import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.ComboBox; +import javafx.scene.control.DialogPane; import javafx.scene.control.Tooltip; -import javafx.scene.input.MouseButton; import javafx.scene.input.MouseEvent; import javafx.scene.layout.VBox; import javafx.util.Duration; +import javafx.util.StringConverter; import org.controlsfx.control.StatusBar; +import java.io.IOException; +import java.util.Arrays; +import java.util.List; + public class WelcomeController { @FXML private VBox welcomeBox; @@ -36,10 +44,13 @@ public class WelcomeController { @FXML private StatusBar serverStatus; + @FXML + private ComboBox languages; + @FXML private UnlabeledToggleSwitch serverToggle; - public void initializeView() { + public void initializeView(boolean isFirstExecution) { step1.managedProperty().bind(step1.visibleProperty()); step2.managedProperty().bind(step2.visibleProperty()); step3.managedProperty().bind(step3.visibleProperty()); @@ -50,14 +61,51 @@ public void initializeView() { step4.setVisible(false); welcomeBox.getStyleClass().add("offline"); - serverStatus.setText("Offline"); + serverStatus.setText(LanguagesManager.getMessage("welcome.offline")); serverToggle.addEventFilter(MouseEvent.MOUSE_RELEASED, Event::consume); - Tooltip tooltip = new Tooltip("Demonstration only - you are not connected!"); + Tooltip tooltip = new Tooltip(LanguagesManager.getMessage("welcome.offline.tooltip")); tooltip.setShowDelay(Duration.ZERO); serverToggle.setTooltip(tooltip); serverToggle.selectedProperty().addListener((observable, oldValue, newValue) -> { - serverStatus.setText(newValue ? "Connected (demonstration only)" : "Offline"); + serverStatus.setText(newValue ? LanguagesManager.getMessage("welcome.server-status.online") : LanguagesManager.getMessage("welcome.offline")); }); + + if(isFirstExecution) { + languages.setItems(getLanguagesList()); + languages.setConverter(new StringConverter<>() { + @Override + public String toString(Language language) { + if(language != null) { + return LanguagesManager.getMessage("language." + language.getCode()); + } else { + return null; + } + } + + @Override + public Language fromString(String code) { + return Language.getFromCode(code); + } + }); + + Language configuredLanguage = Config.get().getLanguage(); + if(configuredLanguage != null) { + languages.setValue(configuredLanguage); + } else { + languages.setValue(LanguagesManager.DEFAULT_LANGUAGE); + } + + languages.valueProperty().addListener((observable, oldValue, newValue) -> { + if(Config.get().getLanguage() != newValue) { + Config.get().setLanguage(newValue); + LanguagesManager.loadLanguage(newValue.getCode()); + + EventManager.get().post(new LanguageChangedInWelcomeEvent(newValue)); + } + }); + } else { + languages.setVisible(false); + } } public boolean next() { @@ -69,7 +117,7 @@ public boolean next() { PauseTransition wait = new PauseTransition(Duration.millis(200)); wait.setOnFinished((e) -> { serverToggle.setSelected(true); - serverStatus.setText("Connected to a Public Server (demonstration only)"); + serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.public-server")); }); wait.play(); return true; @@ -81,7 +129,7 @@ public boolean next() { welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().add("bitcoin-core"); serverToggle.setSelected(true); - serverStatus.setText("Connected to Bitcoin Core (demonstration only)"); + serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.bitcoin-core")); return true; } @@ -91,7 +139,7 @@ public boolean next() { welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().add("private-electrum"); serverToggle.setSelected(true); - serverStatus.setText("Connected to a Private Electrum Server (demonstration only)"); + serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.private-electrum")); } return false; @@ -106,7 +154,7 @@ public boolean back() { PauseTransition wait = new PauseTransition(Duration.millis(200)); wait.setOnFinished((e) -> { serverToggle.setSelected(false); - serverStatus.setText("Offline"); + serverStatus.setText(LanguagesManager.getMessage("welcome.offline")); }); wait.play(); return false; @@ -118,7 +166,7 @@ public boolean back() { welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().add("public-electrum"); serverToggle.setSelected(true); - serverStatus.setText("Connected to a Public Server (demonstration only)"); + serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.public-server")); return true; } @@ -128,10 +176,15 @@ public boolean back() { welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().add("bitcoin-core"); serverToggle.setSelected(true); - serverStatus.setText("Connected to Bitcoin Core (demonstration only)"); + serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.bitcoin-core")); return true; } return false; } -} + + private ObservableList getLanguagesList() { + List languages = Arrays.asList(Language.values()); + return FXCollections.observableList(languages); + } +} \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/WelcomeDialog.java b/src/main/java/com/sparrowwallet/sparrow/WelcomeDialog.java index 3b2702a0f..d653d5644 100644 --- a/src/main/java/com/sparrowwallet/sparrow/WelcomeDialog.java +++ b/src/main/java/com/sparrowwallet/sparrow/WelcomeDialog.java @@ -1,5 +1,8 @@ package com.sparrowwallet.sparrow; +import com.google.common.eventbus.Subscribe; +import com.sparrowwallet.sparrow.event.LanguageChangedInWelcomeEvent; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import javafx.event.ActionEvent; import javafx.fxml.FXMLLoader; import javafx.scene.control.*; @@ -9,16 +12,22 @@ import java.io.IOException; public class WelcomeDialog extends Dialog { - public WelcomeDialog() { + + private WelcomeController welcomeController; + + public WelcomeDialog(boolean isFirstExecution) { + super(); final DialogPane dialogPane = getDialogPane(); AppServices.setStageIcon(dialogPane.getScene().getWindow()); AppServices.onEscapePressed(dialogPane.getScene(), this::close); + EventManager.get().register(this); + try { - FXMLLoader welcomeLoader = new FXMLLoader(AppServices.class.getResource("welcome.fxml")); + FXMLLoader welcomeLoader = new FXMLLoader(AppServices.class.getResource("welcome.fxml"), LanguagesManager.getResourceBundle()); dialogPane.setContent(welcomeLoader.load()); - WelcomeController welcomeController = welcomeLoader.getController(); - welcomeController.initializeView(); + welcomeController = welcomeLoader.getController(); + welcomeController.initializeView(isFirstExecution); dialogPane.setPrefWidth(600); dialogPane.setPrefHeight(520); @@ -27,10 +36,10 @@ public WelcomeDialog() { dialogPane.getStylesheets().add(AppServices.class.getResource("welcome.css").toExternalForm()); - final ButtonType nextButtonType = new javafx.scene.control.ButtonType("Next", ButtonBar.ButtonData.OK_DONE); - final ButtonType backButtonType = new javafx.scene.control.ButtonType("Back", ButtonBar.ButtonData.LEFT); - final ButtonType onlineButtonType = new javafx.scene.control.ButtonType("Configure Server", ButtonBar.ButtonData.APPLY); - final ButtonType offlineButtonType = new javafx.scene.control.ButtonType(AppServices.isConnected() ? "Done" : "Later or Offline Mode", ButtonBar.ButtonData.CANCEL_CLOSE); + final ButtonType nextButtonType = new javafx.scene.control.ButtonType(LanguagesManager.getMessage("welcome.next"), ButtonBar.ButtonData.OK_DONE); + final ButtonType backButtonType = new javafx.scene.control.ButtonType(LanguagesManager.getMessage("welcome.back"), ButtonBar.ButtonData.LEFT); + final ButtonType onlineButtonType = new javafx.scene.control.ButtonType(LanguagesManager.getMessage("welcome.configure-server"), ButtonBar.ButtonData.APPLY); + final ButtonType offlineButtonType = new javafx.scene.control.ButtonType(AppServices.isConnected() ? LanguagesManager.getMessage("welcome.done") : LanguagesManager.getMessage("welcome.configure-later"), ButtonBar.ButtonData.CANCEL_CLOSE); dialogPane.getButtonTypes().addAll(nextButtonType, backButtonType, onlineButtonType, offlineButtonType); Button nextButton = (Button)dialogPane.lookupButton(nextButtonType); @@ -69,4 +78,18 @@ public WelcomeDialog() { throw new RuntimeException(e); } } + + @Subscribe + public void languageChanged(LanguageChangedInWelcomeEvent event) { + final DialogPane dialogPane = getDialogPane(); + + try { + FXMLLoader welcomeLoader = new FXMLLoader(AppServices.class.getResource("welcome.fxml"), LanguagesManager.getResourceBundle()); + dialogPane.setContent(welcomeLoader.load()); + welcomeController = welcomeLoader.getController(); + welcomeController.initializeView(true); + } catch(IOException e) { + throw new RuntimeException(e); + } + } } diff --git a/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java index 6f1ba858c..21ae185b3 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/CoinTreeTable.java @@ -9,6 +9,7 @@ import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent; import com.sparrowwallet.sparrow.event.WalletDataChangedEvent; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.net.ServerType; @@ -86,12 +87,12 @@ public void updateHistoryStatus(WalletHistoryStatusEvent event) { if(entry != null && event.getWallet() != null && entry.getWallet() == event.getWallet()) { Platform.runLater(() -> { if(event.getErrorMessage() != null) { - setPlaceholder(new Label("Error loading transactions: " + event.getErrorMessage())); + setPlaceholder(new Label(LanguagesManager.getMessage("wallet.transactions.table.loading.error")+ " " + event.getErrorMessage())); } else if(event.isLoading()) { if(event.getStatusMessage() != null) { setPlaceholder(new Label(event.getStatusMessage() + "...")); } else { - setPlaceholder(new Label("Loading transactions...")); + setPlaceholder(new Label(LanguagesManager.getMessage("wallet.transactions.table.loading"))); } } else { setPlaceholder(getDefaultPlaceholder(event.getWallet())); @@ -103,7 +104,7 @@ public void updateHistoryStatus(WalletHistoryStatusEvent event) { protected Node getDefaultPlaceholder(Wallet wallet) { StackPane stackPane = new StackPane(); - stackPane.getChildren().add(AppServices.isConnecting() ? new Label("Loading transactions...") : new Label("No transactions")); + stackPane.getChildren().add(AppServices.isConnecting() ? new Label(LanguagesManager.getMessage("wallet.transactions.table.loading")) : new Label(LanguagesManager.getMessage("wallet.transactions.table.no-transactions"))); if(Config.get().getServerType() == ServerType.BITCOIN_CORE && !AppServices.isConnecting()) { Hyperlink hyperlink = new Hyperlink(); diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java index dfc9fe711..a100aab22 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionsTreeTable.java @@ -1,5 +1,6 @@ package com.sparrowwallet.sparrow.control; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.wallet.Entry; import com.sparrowwallet.sparrow.wallet.TransactionEntry; import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry; @@ -15,7 +16,7 @@ public void initialize(WalletTransactionsEntry rootEntry) { updateAll(rootEntry); setShowRoot(false); - TreeTableColumn dateCol = new TreeTableColumn<>("Date"); + TreeTableColumn dateCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.date")); dateCol.setCellValueFactory((TreeTableColumn.CellDataFeatures param) -> { return new ReadOnlyObjectWrapper<>(param.getValue().getValue()); }); @@ -23,7 +24,7 @@ public void initialize(WalletTransactionsEntry rootEntry) { dateCol.setSortable(true); getColumns().add(dateCol); - TreeTableColumn labelCol = new TreeTableColumn<>("Label"); + TreeTableColumn labelCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.label")); labelCol.setCellValueFactory((TreeTableColumn.CellDataFeatures param) -> { return param.getValue().getValue().labelProperty(); }); @@ -31,7 +32,7 @@ public void initialize(WalletTransactionsEntry rootEntry) { labelCol.setSortable(true); getColumns().add(labelCol); - TreeTableColumn amountCol = new TreeTableColumn<>("Value"); + TreeTableColumn amountCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.value")); amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures param) -> { return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue()); }); @@ -39,7 +40,7 @@ public void initialize(WalletTransactionsEntry rootEntry) { amountCol.setSortable(true); getColumns().add(amountCol); - TreeTableColumn balanceCol = new TreeTableColumn<>("Balance"); + TreeTableColumn balanceCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.balance")); balanceCol.setCellValueFactory((TreeTableColumn.CellDataFeatures param) -> { return param.getValue().getValue() instanceof TransactionEntry ? ((TransactionEntry)param.getValue().getValue()).balanceProperty() : new ReadOnlyObjectWrapper<>(null); }); diff --git a/src/main/java/com/sparrowwallet/sparrow/event/LanguageChangedInWelcomeEvent.java b/src/main/java/com/sparrowwallet/sparrow/event/LanguageChangedInWelcomeEvent.java new file mode 100644 index 000000000..0d1019567 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/event/LanguageChangedInWelcomeEvent.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.sparrow.event; + +import com.sparrowwallet.sparrow.i18n.Language; + +public class LanguageChangedInWelcomeEvent { + private final Language language; + + public LanguageChangedInWelcomeEvent(Language language) { + this.language = language; + } + + public Language getLanguage() { + return language; + } +} diff --git a/src/main/java/com/sparrowwallet/sparrow/i18n/Language.java b/src/main/java/com/sparrowwallet/sparrow/i18n/Language.java new file mode 100644 index 000000000..64221982a --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/i18n/Language.java @@ -0,0 +1,25 @@ +package com.sparrowwallet.sparrow.i18n; + +public enum Language { + ENGLISH("en"), + SPANISH("es"); + + private final String code; + + Language(String code) { + this.code = code; + } + + public String getCode() { + return code; + } + + public static Language getFromCode(String code) { + for (Language l : Language.values()) { + if (l.code.equals(code)) { + return l; + } + } + return null; + } +} \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/i18n/LanguagesManager.java b/src/main/java/com/sparrowwallet/sparrow/i18n/LanguagesManager.java new file mode 100644 index 000000000..95c634205 --- /dev/null +++ b/src/main/java/com/sparrowwallet/sparrow/i18n/LanguagesManager.java @@ -0,0 +1,27 @@ +package com.sparrowwallet.sparrow.i18n; + +import java.util.*; + +public class LanguagesManager { + + public static final Language DEFAULT_LANGUAGE = Language.ENGLISH; + + private static ResourceBundle bundle; + + public static ResourceBundle getResourceBundle() { + if(bundle == null) { + loadLanguage(DEFAULT_LANGUAGE.getCode()); + } + return bundle; + } + + public static void loadLanguage(String language) { + Locale locale = new Locale(language); + + if(bundle == null || !locale.equals(bundle.getLocale())) { + bundle = ResourceBundle.getBundle("i18n/messages", locale); + } + } + + public static String getMessage(String key) { return bundle.getString(key); } +} \ No newline at end of file diff --git a/src/main/java/com/sparrowwallet/sparrow/io/Config.java b/src/main/java/com/sparrowwallet/sparrow/io/Config.java index ce5151224..c4fd19211 100644 --- a/src/main/java/com/sparrowwallet/sparrow/io/Config.java +++ b/src/main/java/com/sparrowwallet/sparrow/io/Config.java @@ -6,6 +6,7 @@ import com.sparrowwallet.sparrow.Mode; import com.sparrowwallet.sparrow.Theme; import com.sparrowwallet.sparrow.control.QRDensity; +import com.sparrowwallet.sparrow.i18n.Language; import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.wallet.FeeRatesSelection; import com.sparrowwallet.sparrow.wallet.OptimizationStrategy; @@ -43,6 +44,7 @@ public class Config { private boolean notifyNewTransactions = true; private boolean checkNewVersions = true; private Theme theme; + private Language language; private boolean openWalletsInNewWindows = false; private boolean hideEmptyUsedAddresses = false; private boolean showTransactionHex = true; @@ -277,6 +279,15 @@ public void setTheme(Theme theme) { flush(); } + public Language getLanguage() { + return language; + } + + public void setLanguage(Language language) { + this.language = language; + flush(); + } + public boolean isOpenWalletsInNewWindows() { return openWalletsInNewWindows; } diff --git a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java index c5a492721..6e3551486 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/BatchedElectrumServerRpc.java @@ -9,6 +9,7 @@ import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -82,7 +83,7 @@ public BlockHeaderTip subscribeBlockHeaders(Transport transport) { @SuppressWarnings("unchecked") public Map getScriptHashHistory(Transport transport, Wallet wallet, Map pathScriptHashes, boolean failOnError) { PagedBatchRequestBuilder batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(String.class).returnType(ScriptHashTx[].class); - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Loading transactions for " + nodeRangesToString(pathScriptHashes.keySet()))); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.loading-transactions") + " " + nodeRangesToString(pathScriptHashes.keySet()))); for(String path : pathScriptHashes.keySet()) { batchRequest.add(path, "blockchain.scripthash.get_history", pathScriptHashes.get(path)); @@ -137,7 +138,7 @@ public Map getScriptHashMempool(Transport transport, Wal @SuppressWarnings("unchecked") public Map subscribeScriptHashes(Transport transport, Wallet wallet, Map pathScriptHashes) { PagedBatchRequestBuilder batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(String.class).returnType(String.class); - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Finding transactions for " + nodeRangesToString(pathScriptHashes.keySet()))); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.finding-transactions") + " " + nodeRangesToString(pathScriptHashes.keySet()))); for(String path : pathScriptHashes.keySet()) { batchRequest.add(path, "blockchain.scripthash.subscribe", pathScriptHashes.get(path)); @@ -157,7 +158,7 @@ public Map subscribeScriptHashes(Transport transport, Wallet wal @SuppressWarnings("unchecked") public Map getBlockHeaders(Transport transport, Wallet wallet, Set blockHeights) { PagedBatchRequestBuilder batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(Integer.class).returnType(String.class); - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving " + blockHeights.size() + " block headers")); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.retrieving") + " " + blockHeights.size() + " " + LanguagesManager.getMessage("wallet.transactions.retrieving-headers"))); for(Integer height : blockHeights) { batchRequest.add(height, "blockchain.block.header", height); @@ -176,7 +177,7 @@ public Map getBlockHeaders(Transport transport, Wallet wallet, @SuppressWarnings("unchecked") public Map getTransactions(Transport transport, Wallet wallet, Set txids) { PagedBatchRequestBuilder batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(String.class).returnType(String.class); - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving " + txids.size() + " transactions")); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.retrieving") + " " + txids.size() + " " + LanguagesManager.getMessage("wallet.transactions.retrieving-transactions"))); for(String txid : txids) { batchRequest.add(txid, "blockchain.transaction.get", txid); diff --git a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java index f407ea5f6..ad33bd20f 100644 --- a/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java +++ b/src/main/java/com/sparrowwallet/sparrow/net/SimpleElectrumServerRpc.java @@ -10,6 +10,7 @@ import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -76,7 +77,7 @@ public Map getScriptHashHistory(Transport transport, Wal Map result = new LinkedHashMap<>(); for(String path : pathScriptHashes.keySet()) { - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Loading transactions for " + path)); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.loading-transactions") + " " + path)); try { ScriptHashTx[] scriptHashTxes = new RetryLogic(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> client.createRequest().returnAs(ScriptHashTx[].class).method("blockchain.scripthash.get_history").id(idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).execute()); @@ -121,7 +122,7 @@ public Map subscribeScriptHashes(Transport transport, Wallet wal Map result = new LinkedHashMap<>(); for(String path : pathScriptHashes.keySet()) { - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Finding transactions for " + path)); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.finding-transactions") + " " + path)); try { String scriptHash = new RetryLogic(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> client.createRequest().returnAs(String.class).method("blockchain.scripthash.subscribe").id(idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).executeNullable()); @@ -141,7 +142,7 @@ public Map getBlockHeaders(Transport transport, Wallet wallet, Map result = new LinkedHashMap<>(); for(Integer blockHeight : blockHeights) { - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving block at height " + blockHeight)); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.retrieving-block-height") + " " + blockHeight)); try { String blockHeader = new RetryLogic(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> client.createRequest().returnAs(String.class).method("blockchain.block.header").id(idCounter.incrementAndGet()).params(blockHeight).execute()); @@ -165,7 +166,7 @@ public Map getTransactions(Transport transport, Wallet wallet, S Map result = new LinkedHashMap<>(); for(String txid : txids) { - EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, "Retrieving transaction [" + txid.substring(0, 6) + "]")); + EventManager.get().post(new WalletHistoryStatusEvent(wallet, true, LanguagesManager.getMessage("wallet.transactions.retrieving-transaction") + " [" + txid.substring(0, 6) + "]")); try { String rawTxHex = new RetryLogic(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> client.createRequest().returnAs(String.class).method("blockchain.transaction.get").id(idCounter.incrementAndGet()).params(txid).execute()); diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java index 5c236c8a4..e191805c7 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/TransactionsController.java @@ -5,6 +5,7 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.*; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.WalletTransactions; import com.sparrowwallet.sparrow.net.ExchangeSource; @@ -96,7 +97,7 @@ public void initializeView() { }); transactionsMasterDetail.setShowDetailNode(Config.get().isShowLoadingLog()); - loadingLog.appendText("Wallet loading history for " + getWalletForm().getWallet().getFullDisplayName()); + loadingLog.appendText(LanguagesManager.getMessage("wallet.transactions.loading-history") + " " + getWalletForm().getWallet().getFullDisplayName()); loadingLog.setEditable(false); } @@ -109,7 +110,7 @@ public void exportCSV(ActionEvent event) { Stage window = new Stage(); FileChooser fileChooser = new FileChooser(); - fileChooser.setTitle("Export Transactions as CSV"); + fileChooser.setTitle(LanguagesManager.getMessage("wallet.transactions.csv-export")); fileChooser.setInitialFileName(wallet.getFullName() + "-transactions.csv"); AppServices.moveToActiveWindowScreen(window, 800, 450); File file = fileChooser.showSaveDialog(window); @@ -118,7 +119,7 @@ public void exportCSV(ActionEvent event) { exportService.setOnFailed(failedEvent -> { Throwable e = failedEvent.getSource().getException(); log.error("Error exporting transactions as CSV", e); - AppServices.showErrorDialog("Error exporting transactions as CSV", e.getMessage()); + AppServices.showErrorDialog(LanguagesManager.getMessage("wallet.transactions.csv-export.error"), e.getMessage()); }); exportService.start(); } @@ -212,7 +213,7 @@ public void walletHistoryStatus(WalletHistoryStatusEvent event) { String logMessage = event.getStatusMessage(); if(logMessage == null) { if(event instanceof WalletHistoryFinishedEvent) { - logMessage = "Finished loading."; + logMessage = LanguagesManager.getMessage("wallet.transactions.loading-finish"); } else if(event instanceof WalletHistoryFailedEvent) { logMessage = event.getErrorMessage(); } diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java index b8cc2149b..7e946c4cd 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/WalletController.java @@ -8,6 +8,7 @@ import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.control.ViewPasswordField; import com.sparrowwallet.sparrow.event.*; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.io.Storage; import javafx.application.Platform; import javafx.beans.property.BooleanProperty; @@ -95,7 +96,7 @@ public void initializeView() { throw new IllegalStateException("Cannot find wallet/" + function.toString().toLowerCase(Locale.ROOT) + ".fxml"); } - FXMLLoader functionLoader = new FXMLLoader(url); + FXMLLoader functionLoader = new FXMLLoader(url, LanguagesManager.getResourceBundle()); Node walletFunction = functionLoader.load(); walletFunction.setUserData(function); WalletFormController controller = functionLoader.getController(); diff --git a/src/main/resources/com/sparrowwallet/sparrow/app.fxml b/src/main/resources/com/sparrowwallet/sparrow/app.fxml index 68123ac5d..7bd511f52 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/app.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/app.fxml @@ -9,6 +9,7 @@ + @@ -64,6 +65,9 @@ + + + @@ -113,6 +117,20 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml index 27e46a7df..6d0a70f1a 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/transactions.fxml @@ -33,21 +33,21 @@
-
- +
+ - + diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml index 0cbdeeb2e..0be3a1dd6 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/wallet.fxml @@ -11,7 +11,7 @@ - + diff --git a/src/main/resources/com/sparrowwallet/sparrow/welcome.fxml b/src/main/resources/com/sparrowwallet/sparrow/welcome.fxml index efe2f22c4..8ea073279 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/welcome.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/welcome.fxml @@ -15,7 +15,7 @@ - @@ -24,44 +24,48 @@ - - - - - diff --git a/src/main/resources/i18n/messages_en.properties b/src/main/resources/i18n/messages_en.properties new file mode 100644 index 000000000..c5a632d70 --- /dev/null +++ b/src/main/resources/i18n/messages_en.properties @@ -0,0 +1,57 @@ +#welcome +welcome.title=Welcome to Sparrow +welcome.step1.introduction=Introduction +welcome.step1.label1=Sparrow is a Bitcoin wallet with a focus on security and usability. +welcome.step1.label2=Sparrow can operate in both an online and offline mode. In the online mode it connects to a server to display transaction history. In the offline mode it is useful as a transaction editor and as an airgapped multisig coordinator. +welcome.step1.label3=The status bar at the bottom displays the connection status, as demonstrated below: +welcome.step2.introduction=Connecting to a Public Server +welcome.step2.label1=If you are beginning your journey in self custody, or just storing a small amount, the easiest way to connect Sparrow to the Bitcoin blockchain is via one of the preconfigured public Electrum servers. +welcome.step2.label2=However, although Sparrow only connects to servers that have a record of respecting privacy, it is still not ideal as you are sharing your transaction history and balance with them. +welcome.step2.label3=A yellow toggle means you are connected to a public server. +welcome.step3.introduction=Connecting to a Bitcoin Core node +welcome.step3.label1=If you are running your own Bitcoin Core node, you can configure Sparrow to connect to it directly. +welcome.step3.label2=This means you are not sharing your transaction data, but be aware Bitcoin Core stores your balance, transactions and public keys unencrypted on that node, which is not ideal for true cold storage. +welcome.step3.label3=A green toggle means you are connected to a Bitcoin Core node. +welcome.step4.introduction=Connecting to a Private Electrum Server +welcome.step4.label1=The most private way to connect is to your own Electrum server, which in turn connects to your Bitcoin Core node. +welcome.step4.label2=Because these servers index all Bitcoin transactions equally, your wallet transactions are never stored on the server in an identifiable way, and your server forgets your requests immediately after serving them. +welcome.step4.label3=A blue toggle means you are connected to a private Electrum server. You're now ready to configure a server and start using Sparrow! +welcome.offline=Offline +welcome.offline.tooltip=Demonstration only - you are not connected! +welcome.server-status.online=Connected (demonstration only) +welcome.server-status.online.public-server=Connected to a Public Server (demonstration only) +welcome.server-status.online.bitcoin-core=Connected to Bitcoin Core (demonstration only) +welcome.server-status.online.private-electrum=Connected to a Private Electrum Server (demonstration only) +welcome.next=Next +welcome.back=Back +welcome.configure-server=Configure Server +welcome.done=Done +welcome.configure-later=Later or Offline Mode + +# wallet transactions +wallet.transactions=Transactions +wallet.transactions.balance=Balance: +wallet.transactions.transactions=Transactions: +wallet.transactions.csv-export=Export transactions as CSV +wallet.transactions.csv-export.error=Error exporting transactions as CSV +wallet.transactions.loading-history=Wallet loading history for +wallet.transactions.loading-finish=Finished loading. +wallet.transactions.loading-transactions=Loading transactions for +wallet.transactions.finding-transactions=Finding transactions for +wallet.transactions.retrieving=Retrieving +wallet.transactions.retrieving-transactions=transactions +wallet.transactions.retrieving-headers=block headers +wallet.transactions.retrieving-block-height=Retrieving block at height +wallet.transactions.retrieving-transaction=Retrieving transaction +wallet.transactions.table.date=Date +wallet.transactions.table.label=Label +wallet.transactions.table.value=Value +wallet.transactions.table.balance=Balance +wallet.transactions.table.loading=Loading transactions... +wallet.transactions.table.loading.error=Error loading transactions: +wallet.transactions.table.no-transactions=No transactions + +# settings +language=Language +language.en=English +language.es=Spanish \ No newline at end of file diff --git a/src/main/resources/i18n/messages_es.properties b/src/main/resources/i18n/messages_es.properties new file mode 100644 index 000000000..bfa87dec4 --- /dev/null +++ b/src/main/resources/i18n/messages_es.properties @@ -0,0 +1,57 @@ +#welcome +welcome.title=Bienvenido a Sparrow +welcome.step1.introduction=Introducción +welcome.step1.label1=Sparrow es una billetera de Bitcoin con un enfoque en la seguridad y la facilidad de uso. +welcome.step1.label2=Sparrow puede operar tanto en modo en línea como fuera de línea. En el modo en línea, se conecta a un servidor para mostrar el historial de transacciones. En el modo fuera de línea, es útil como editor de transacciones y como coordinador multisig desconectado. +welcome.step1.label3=La barra de estado en la parte inferior muestra el estado de la conexión, como se muestra a continuación: +welcome.step2.introduction=Conexión a un servidor público +welcome.step2.label1=Si estás comenzando tu camino en la auto custodia o solo almacenando una pequeña cantidad, la forma más fácil de conectar Sparrow a la blockchain de Bitcoin es a través de uno de los servidores públicos de Electrum preconfigurados. +welcome.step2.label2=Sin embargo, aunque Sparrow solo se conecta a servidores que tienen un historial de respetar la privacidad, aún no es lo ideal ya que estás compartiendo tu historial de transacciones y tu saldo con ellos. +welcome.step2.label3=Un interruptor amarillo significa que estás conectado a un servidor público. +welcome.step3.introduction=Conexión a un nodo de Bitcoin Core +welcome.step3.label1=Si estás ejecutando tu propio nodo de Bitcoin Core, puedes configurar Sparrow para que se conecte directamente a él. +welcome.step3.label2=Esto significa que no estás compartiendo tus datos de transacciones, pero ten en cuenta que Bitcoin Core almacena tu saldo, transacciones y claves públicas sin cifrar en ese nodo, lo cual no es ideal para un almacenamiento en frío verdadero. +welcome.step3.label3=Un interruptor verde significa que estás conectado a un nodo de Bitcoin Core. +welcome.step4.introduction=Conexión a un servidor privado de Electrum +welcome.step4.label1=La forma más privada de conectarse es a tu propio servidor de Electrum, que a su vez se conecta a tu nodo de Bitcoin Core. +welcome.step4.label2=Dado que estos servidores indexan todas las transacciones de Bitcoin por igual, las transacciones de tu billetera nunca se almacenan en el servidor de manera identificable, y tu servidor olvida tus solicitudes inmediatamente después de servirlas. +welcome.step4.label3=Un interruptor azul significa que estás conectado a un servidor privado de Electrum. ¡Ahora estás listo para configurar un servidor y empezar a usar Sparrow! +welcome.offline=Desconectado +welcome.offline.tooltip=Sólo demostración - ¡no estás conectado! +welcome.server-status.online=Conectado (sólo demostración) +welcome.server-status.online.public-server=Conectado a un Servidor Público (sólo demostración) +welcome.server-status.online.bitcoin-core=Conectado a Bitcoin Core (sólo demostración) +welcome.server-status.online.private-electrum=Conectado a un Servidor Privado Electrum (sólo demostración) +welcome.next=Siguiente +welcome.back=Anterior +welcome.configure-server=Configurar Servidor +welcome.done=Hecho +welcome.configure-later=Después o Modo Sin Conexción + +# wallet transactions +wallet.transactions=Transacciones +wallet.transactions.balance=Saldo: +wallet.transactions.transactions=Transacciones: +wallet.transactions.csv-export=Exportar transacciones en CSV +wallet.transactions.csv-export.error=Error exportando transacciones a CSV +wallet.transactions.loading-history=Cargando historial de carga para la billetera +wallet.transactions.loading-finish=Carga terminada. +wallet.transactions.loading-transactions=Cargando transacciones para +wallet.transactions.finding-transactions=Encontrando transacciones para +wallet.transactions.retrieving=Recuperando +wallet.transactions.retrieving-transactions=transacciones +wallet.transactions.retrieving-headers=cabeceras de bloque +wallet.transactions.retrieving-block-height=Recuperando bloque en altura +wallet.transactions.retrieving-transaction=Recuperando transacción +wallet.transactions.table.date=Fecha +wallet.transactions.table.label=Etiqueta +wallet.transactions.table.value=Valor +wallet.transactions.table.balance=Saldo +wallet.transactions.table.loading=Cargando transacciones... +wallet.transactions.table.loading.error=Error cargando transacciones: +wallet.transactions.table.no-transactions=Sin transacciones + +# settings +language=Idioma +language.en=Inglés +language.es=Español \ No newline at end of file From b33459f5d120aad54192d6c26ce164efaf52383c Mon Sep 17 00:00:00 2001 From: Anurag Lint Date: Mon, 16 Sep 2024 21:24:18 +0200 Subject: [PATCH 2/4] Dynamic menu language list --- .../sparrowwallet/sparrow/AppController.java | 37 ++++++++++++++----- .../com/sparrowwallet/sparrow/app.fxml | 17 +-------- 2 files changed, 29 insertions(+), 25 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/AppController.java b/src/main/java/com/sparrowwallet/sparrow/AppController.java index fe271af02..f373a56a2 100644 --- a/src/main/java/com/sparrowwallet/sparrow/AppController.java +++ b/src/main/java/com/sparrowwallet/sparrow/AppController.java @@ -141,7 +141,10 @@ public class AppController implements Initializable { private ToggleGroup theme; @FXML - private ToggleGroup language; + private ToggleGroup languages; + + @FXML + private Menu languagesMenu; @FXML private CheckMenuItem openWalletsInNewWindows; @@ -378,14 +381,7 @@ void initializeView() { selectedThemeToggle.ifPresent(toggle -> theme.selectToggle(toggle)); setTheme(null); - Language configLanguage = Config.get().getLanguage(); - if(configLanguage == null) { - configLanguage = LanguagesManager.DEFAULT_LANGUAGE; - Config.get().setLanguage(configLanguage); - } - final Language selectedLanguage = configLanguage; - Optional selectedLanguageToggle = language.getToggles().stream().filter(toggle -> selectedLanguage.equals(toggle.getUserData())).findFirst(); - selectedLanguageToggle.ifPresent(toggle -> language.selectToggle(toggle)); + prepareLanguages(); openWalletsInNewWindowsProperty.set(Config.get().isOpenWalletsInNewWindows()); openWalletsInNewWindows.selectedProperty().bindBidirectional(openWalletsInNewWindowsProperty); @@ -444,6 +440,27 @@ void initializeView() { setNetworkLabel(); } + private void prepareLanguages() { + Language[] languagesList = Language.values(); + for(Language language : languagesList) { + RadioMenuItem languageItem = new RadioMenuItem(LanguagesManager.getMessage("language." + language.getCode())); + languageItem.setMnemonicParsing(false); + languageItem.setToggleGroup(languages); + languageItem.setOnAction(this::setLanguage); + languageItem.setUserData(language); + languagesMenu.getItems().add(languageItem); + } + + Language configLanguage = Config.get().getLanguage(); + if(configLanguage == null) { + configLanguage = LanguagesManager.DEFAULT_LANGUAGE; + Config.get().setLanguage(configLanguage); + } + final Language selectedLanguage = configLanguage; + Optional selectedLanguageToggle = languages.getToggles().stream().filter(toggle -> selectedLanguage.equals(toggle.getUserData())).findFirst(); + selectedLanguageToggle.ifPresent(toggle -> languages.selectToggle(toggle)); + } + private void registerShortcuts() { org.controlsfx.tools.Platform platform = org.controlsfx.tools.Platform.getCurrent(); if(platform == org.controlsfx.tools.Platform.OSX) { @@ -2358,7 +2375,7 @@ public void setTheme(ActionEvent event) { } public void setLanguage(ActionEvent event) { - Language selectedLanguage = (Language)language.getSelectedToggle().getUserData(); + Language selectedLanguage = (Language)languages.getSelectedToggle().getUserData(); if(Config.get().getLanguage() != selectedLanguage) { Config.get().setLanguage(selectedLanguage); } diff --git a/src/main/resources/com/sparrowwallet/sparrow/app.fxml b/src/main/resources/com/sparrowwallet/sparrow/app.fxml index 7bd511f52..06f45c2a6 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/app.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/app.fxml @@ -66,7 +66,7 @@ - + @@ -117,20 +117,7 @@ - - - - - - - - - - - - - - + From 87f2fbcbb39311e316481a6086145f9602a781b7 Mon Sep 17 00:00:00 2001 From: Anurag Lint Date: Tue, 17 Sep 2024 07:52:47 +0200 Subject: [PATCH 3/4] Delete unused imports --- .../java/com/sparrowwallet/sparrow/WelcomeController.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java b/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java index 6b9967355..a8029ae66 100644 --- a/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java +++ b/src/main/java/com/sparrowwallet/sparrow/WelcomeController.java @@ -1,6 +1,5 @@ package com.sparrowwallet.sparrow; -import com.google.common.eventbus.Subscribe; import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch; import com.sparrowwallet.sparrow.event.LanguageChangedInWelcomeEvent; import com.sparrowwallet.sparrow.i18n.Language; @@ -11,9 +10,7 @@ import javafx.collections.ObservableList; import javafx.event.Event; import javafx.fxml.FXML; -import javafx.fxml.FXMLLoader; import javafx.scene.control.ComboBox; -import javafx.scene.control.DialogPane; import javafx.scene.control.Tooltip; import javafx.scene.input.MouseEvent; import javafx.scene.layout.VBox; @@ -21,7 +18,6 @@ import javafx.util.StringConverter; import org.controlsfx.control.StatusBar; -import java.io.IOException; import java.util.Arrays; import java.util.List; From bcffa0a68136b198484e4cc88dc3efe7d2849dbc Mon Sep 17 00:00:00 2001 From: Anurag Lint Date: Sat, 12 Oct 2024 10:41:49 +0200 Subject: [PATCH 4/4] Transaltions for Send tab --- .../sparrow/control/TransactionDiagram.java | 3 +- .../sparrow/wallet/SendController.java | 53 +++++++-------- .../sparrowwallet/sparrow/wallet/payment.fxml | 22 +++---- .../sparrowwallet/sparrow/wallet/send.fxml | 40 ++++++------ .../sparrowwallet/sparrow/wallet/wallet.fxml | 2 +- .../resources/i18n/messages_en.properties | 65 +++++++++++++++++++ .../resources/i18n/messages_es.properties | 65 +++++++++++++++++++ 7 files changed, 191 insertions(+), 59 deletions(-) diff --git a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java index 72c5b239d..cb175953d 100644 --- a/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java +++ b/src/main/java/com/sparrowwallet/sparrow/control/TransactionDiagram.java @@ -14,6 +14,7 @@ import com.sparrowwallet.sparrow.event.ReplaceChangeAddressEvent; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.GlyphUtils; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.wallet.OptimizationStrategy; import javafx.beans.property.BooleanProperty; @@ -806,7 +807,7 @@ private Pane getTransactionPane() { txPane.setAlignment(Pos.CENTER); txPane.getChildren().add(createSpacer()); - String txDesc = "Transaction"; + String txDesc = LanguagesManager.getMessage("diagram.transaction"); Label txLabel = new Label(txDesc); boolean isFinalized = walletTx.getTransaction().hasScriptSigs() || walletTx.getTransaction().hasWitnesses(); Tooltip tooltip = new Tooltip(walletTx.getTransaction().getLength() + " bytes\n" diff --git a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java index 1cba5000d..90998f02e 100644 --- a/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java +++ b/src/main/java/com/sparrowwallet/sparrow/wallet/SendController.java @@ -20,6 +20,7 @@ import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; +import com.sparrowwallet.sparrow.i18n.LanguagesManager; import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.net.*; @@ -356,7 +357,7 @@ public Double fromString(String string) { }); utxoLabelSelectionProperty.addListener((observable, oldValue, newValue) -> { - clearButton.setText("Clear" + newValue); + clearButton.setText(LanguagesManager.getMessage("wallet.send.clear") + newValue); }); utxoSelectorProperty.addListener((observable, oldValue, utxoSelector) -> { @@ -475,7 +476,7 @@ public Tab getPaymentTab() { Tab tab = new Tab(" " + (highestTabNo.isPresent() ? highestTabNo.getAsInt() + 1 : 1) + " "); try { - FXMLLoader paymentLoader = new FXMLLoader(AppServices.class.getResource("wallet/payment.fxml")); + FXMLLoader paymentLoader = new FXMLLoader(AppServices.class.getResource("wallet/payment.fxml"), LanguagesManager.getResourceBundle()); tab.setContent(paymentLoader.load()); PaymentController controller = paymentLoader.getController(); controller.setSendController(this); @@ -896,8 +897,8 @@ private void setFeeRatePriority(Double feeRateAmt) { if(targetBlocksFeeRates.get(Integer.MAX_VALUE) != null) { Double minFeeRate = targetBlocksFeeRates.get(Integer.MAX_VALUE); if(minFeeRate > 1.0 && feeRateAmt < minFeeRate) { - feeRatePriority.setText("Below Minimum"); - feeRatePriority.setTooltip(new Tooltip("Transactions at this fee rate are currently being purged from the default sized mempool")); + feeRatePriority.setText(LanguagesManager.getMessage("wallet.send.fee.rate.below-minimum")); + feeRatePriority.setTooltip(new Tooltip(LanguagesManager.getMessage("wallet.send.fee.rate.below-minimum.tooltip"))); feeRatePriorityGlyph.setStyle("-fx-text-fill: #a0a1a7cc"); feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.EXCLAMATION_CIRCLE); return; @@ -905,8 +906,8 @@ private void setFeeRatePriority(Double feeRateAmt) { Double lowestBlocksRate = targetBlocksFeeRates.get(TARGET_BLOCKS_RANGE.get(TARGET_BLOCKS_RANGE.size() - 1)); if(lowestBlocksRate >= minFeeRate && feeRateAmt < (minFeeRate + ((lowestBlocksRate - minFeeRate) / 2)) && !isPayjoinTx()) { - feeRatePriority.setText("Try Then Replace"); - feeRatePriority.setTooltip(new Tooltip("Send a transaction, verify it appears in the destination wallet, then RBF to get it confirmed or sent to another address")); + feeRatePriority.setText(LanguagesManager.getMessage("wallet.send.fee.rate.replace")); + feeRatePriority.setTooltip(new Tooltip(LanguagesManager.getMessage("wallet.send.fee.rate.replace.tooltip"))); feeRatePriorityGlyph.setStyle("-fx-text-fill: #7eb7c9cc"); feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.PLUS_CIRCLE); return; @@ -918,24 +919,24 @@ private void setFeeRatePriority(Double feeRateAmt) { Double maxFeeRate = FEE_RATES_RANGE.get(FEE_RATES_RANGE.size() - 1).doubleValue(); Double highestBlocksRate = targetBlocksFeeRates.get(TARGET_BLOCKS_RANGE.get(0)); if(highestBlocksRate < maxFeeRate && feeRateAmt > (highestBlocksRate + ((maxFeeRate - highestBlocksRate) / 10))) { - feeRatePriority.setText("Overpaid"); - feeRatePriority.setTooltip(new Tooltip("Transaction fees at this rate are likely higher than necessary")); + feeRatePriority.setText(LanguagesManager.getMessage("wallet.send.fee.rate.overpaid")); + feeRatePriority.setTooltip(new Tooltip(LanguagesManager.getMessage("wallet.send.fee.rate.overpaid.tooltip"))); feeRatePriorityGlyph.setStyle("-fx-text-fill: #c8416499"); feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.EXCLAMATION_CIRCLE); } else { - feeRatePriority.setText("High Priority"); - feeRatePriority.setTooltip(new Tooltip("Typically confirms within minutes")); + feeRatePriority.setText(LanguagesManager.getMessage("wallet.send.fee.rate.high-priority")); + feeRatePriority.setTooltip(new Tooltip(LanguagesManager.getMessage("wallet.send.fee.rate.high-priority.tooltip"))); feeRatePriorityGlyph.setStyle("-fx-text-fill: #c8416499"); feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.CIRCLE); } } else if(targetBlocks < FeeRatesSource.BLOCKS_IN_HOUR) { - feeRatePriority.setText("Medium Priority"); - feeRatePriority.setTooltip(new Tooltip("Typically confirms within an hour or two")); + feeRatePriority.setText(LanguagesManager.getMessage("wallet.send.fee.rate.medium-priority")); + feeRatePriority.setTooltip(new Tooltip(LanguagesManager.getMessage("wallet.send.fee.rate.medium-priority.tooltip"))); feeRatePriorityGlyph.setStyle("-fx-text-fill: #fba71b99"); feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.CIRCLE); } else { - feeRatePriority.setText("Low Priority"); - feeRatePriority.setTooltip(new Tooltip("Typically confirms in a day or longer")); + feeRatePriority.setText(LanguagesManager.getMessage("wallet.send.fee.rate.low-priority")); + feeRatePriority.setTooltip(new Tooltip(LanguagesManager.getMessage("wallet.send.fee.rate.low-priority.tooltip"))); feeRatePriorityGlyph.setStyle("-fx-text-fill: #41a9c999"); feeRatePriorityGlyph.setIcon(FontAwesome5.Glyph.CIRCLE); } @@ -963,11 +964,11 @@ private void setFiatFeeAmount(CurrencyRate currencyRate, Long amount) { private void updateMaxClearButtons(UtxoSelector utxoSelector, TxoFilter txoFilter) { if(utxoSelector instanceof PresetUtxoSelector presetUtxoSelector) { int num = presetUtxoSelector.getPresetUtxos().size(); - String selection = " (" + num + " UTXO" + (num != 1 ? "s" : "") + " selected)"; + String selection = " (" + num + " UTXO" + (num != 1 ? "s" : "") + " " + LanguagesManager.getMessage("common.selected") + ")"; utxoLabelSelectionProperty.set(selection); } else if(txoFilter instanceof ExcludeTxoFilter excludeTxoFilter) { int num = excludeTxoFilter.getExcludedTxos().size(); - String exclusion = " (" + num + " UTXO" + (num != 1 ? "s" : "") + " excluded)"; + String exclusion = " (" + num + " UTXO" + (num != 1 ? "s" : "") + " " + LanguagesManager.getMessage("common.excluded") + ")"; utxoLabelSelectionProperty.set(exclusion); } else { utxoLabelSelectionProperty.set(""); @@ -1579,38 +1580,38 @@ public PrivacyAnalysisTooltip(WalletTransaction walletTransaction) { if(optimizationStrategy == OptimizationStrategy.PRIVACY) { if(fakeMixPresent) { - addLabel("Appears as a two person coinjoin", getPlusGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.coinjoin"), getPlusGlyph()); } else { if(mixedAddressTypes) { - addLabel("Cannot fake coinjoin due to mixed address types", getInfoGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.coinjoin.mixes-address-types"), getInfoGlyph()); } else if(userPayments.size() > 1) { - addLabel("Cannot fake coinjoin due to multiple payments", getInfoGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.coinjoin.multiple-payments"), getInfoGlyph()); } else if(payjoinPresent) { - addLabel("Cannot fake coinjoin due to payjoin", getInfoGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.coinjoin.payjoin"), getInfoGlyph()); } else { if(utxoSelectorProperty().get() != null) { - addLabel("Cannot fake coinjoin due to coin control", getInfoGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.coinjoin.coincontrol"), getInfoGlyph()); } else { - addLabel("Cannot fake coinjoin due to insufficient funds", getInfoGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.coinjoin.insufficient-funds"), getInfoGlyph()); } } } } if(mixedAddressTypes) { - addLabel("Address types different to the wallet indicate external payments", getMinusGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.mixes-address-types"), getMinusGlyph()); } if(roundPaymentAmounts && !fakeMixPresent) { - addLabel("Rounded payment amounts indicate external payments", getMinusGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.external-payments"), getMinusGlyph()); } if(addressReuse) { - addLabel("Address reuse detected", getMinusGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.reuse"), getMinusGlyph()); } if(!fakeMixPresent && !mixedAddressTypes && !roundPaymentAmounts) { - addLabel("Appears as a possible self transfer", getPlusGlyph()); + addLabel(LanguagesManager.getMessage("wallet.send.optimize.self-transfer"), getPlusGlyph()); } analysisLabels.sort(Comparator.comparingInt(o -> (Integer)o.getGraphic().getUserData())); diff --git a/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml b/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml index e0d6ac5fe..a3cf8d9a5 100644 --- a/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml +++ b/src/main/resources/com/sparrowwallet/sparrow/wallet/payment.fxml @@ -30,24 +30,24 @@
- + - + - - + + - + - + @@ -60,17 +60,17 @@