Translation implementation and translations for WelcomeDialog and Transactions Tab

This commit is contained in:
Anurag Lint 2024-09-16 20:25:35 +02:00
parent 31f287125f
commit f5c79e33cd
21 changed files with 413 additions and 78 deletions

View file

@ -18,6 +18,8 @@ import com.sparrowwallet.hummingbird.registry.CryptoPSBT;
import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; 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.*;
import com.sparrowwallet.sparrow.io.bbqr.BBQR; import com.sparrowwallet.sparrow.io.bbqr.BBQR;
import com.sparrowwallet.sparrow.io.bbqr.BBQRType; import com.sparrowwallet.sparrow.io.bbqr.BBQRType;
@ -138,6 +140,9 @@ public class AppController implements Initializable {
@FXML @FXML
private ToggleGroup theme; private ToggleGroup theme;
@FXML
private ToggleGroup language;
@FXML @FXML
private CheckMenuItem openWalletsInNewWindows; private CheckMenuItem openWalletsInNewWindows;
private static final BooleanProperty openWalletsInNewWindowsProperty = new SimpleBooleanProperty(); private static final BooleanProperty openWalletsInNewWindowsProperty = new SimpleBooleanProperty();
@ -373,6 +378,15 @@ public class AppController implements Initializable {
selectedThemeToggle.ifPresent(toggle -> theme.selectToggle(toggle)); selectedThemeToggle.ifPresent(toggle -> theme.selectToggle(toggle));
setTheme(null); setTheme(null);
Language configLanguage = Config.get().getLanguage();
if(configLanguage == null) {
configLanguage = LanguagesManager.DEFAULT_LANGUAGE;
Config.get().setLanguage(configLanguage);
}
final Language selectedLanguage = configLanguage;
Optional<Toggle> selectedLanguageToggle = language.getToggles().stream().filter(toggle -> selectedLanguage.equals(toggle.getUserData())).findFirst();
selectedLanguageToggle.ifPresent(toggle -> language.selectToggle(toggle));
openWalletsInNewWindowsProperty.set(Config.get().isOpenWalletsInNewWindows()); openWalletsInNewWindowsProperty.set(Config.get().isOpenWalletsInNewWindows());
openWalletsInNewWindows.selectedProperty().bindBidirectional(openWalletsInNewWindowsProperty); openWalletsInNewWindows.selectedProperty().bindBidirectional(openWalletsInNewWindowsProperty);
hideEmptyUsedAddressesProperty.set(Config.get().isHideEmptyUsedAddresses()); hideEmptyUsedAddressesProperty.set(Config.get().isHideEmptyUsedAddresses());
@ -488,7 +502,7 @@ public class AppController implements Initializable {
} }
public void showIntroduction(ActionEvent event) { public void showIntroduction(ActionEvent event) {
WelcomeDialog welcomeDialog = new WelcomeDialog(); WelcomeDialog welcomeDialog = new WelcomeDialog(false);
welcomeDialog.initOwner(rootStack.getScene().getWindow()); welcomeDialog.initOwner(rootStack.getScene().getWindow());
Optional<Mode> optionalMode = welcomeDialog.showAndWait(); Optional<Mode> optionalMode = welcomeDialog.showAndWait();
if(optionalMode.isPresent() && optionalMode.get().equals(Mode.ONLINE)) { if(optionalMode.isPresent() && optionalMode.get().equals(Mode.ONLINE)) {
@ -996,13 +1010,19 @@ public class AppController implements Initializable {
} }
} }
public void restart(ActionEvent event) {
restart(event, (Network) null);
}
public void restart(ActionEvent event, Network network) { public void restart(ActionEvent event, Network network) {
if(System.getProperty(JPACKAGE_APP_PATH) == null) { if(System.getProperty(JPACKAGE_APP_PATH) == null) {
throw new IllegalStateException("Property " + JPACKAGE_APP_PATH + " is not present"); throw new IllegalStateException("Property " + JPACKAGE_APP_PATH + " is not present");
} }
Args args = getRestartArgs(); Args args = getRestartArgs();
if(network != null) {
args.network = network; args.network = network;
}
restart(event, args); restart(event, args);
} }
@ -1760,7 +1780,7 @@ public class AppController implements Initializable {
subTabLabel.setTooltip(new Tooltip(label)); subTabLabel.setTooltip(new Tooltip(label));
} }
subTab.setGraphic(subTabLabel); 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()); subTab.setContent(walletLoader.load());
WalletController controller = walletLoader.getController(); WalletController controller = walletLoader.getController();
@ -2337,6 +2357,15 @@ public class AppController implements Initializable {
EventManager.get().post(new ThemeChangedEvent(selectedTheme)); 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() { private void serverToggleStartAnimation() {
Node thumbArea = serverToggle.lookup(".thumb-area"); Node thumbArea = serverToggle.lookup(".thumb-area");
if(thumbArea != null) { if(thumbArea != null) {

View file

@ -14,6 +14,7 @@ import com.sparrowwallet.drongo.policy.PolicyType;
import com.sparrowwallet.drongo.wallet.*; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.sparrow.control.WalletPasswordDialog; import com.sparrowwallet.sparrow.control.WalletPasswordDialog;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.i18n.LanguagesManager;
import com.sparrowwallet.sparrow.net.Auth47; import com.sparrowwallet.sparrow.net.Auth47;
import com.sparrowwallet.drongo.protocol.BlockHeader; import com.sparrowwallet.drongo.protocol.BlockHeader;
import com.sparrowwallet.drongo.protocol.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
@ -551,7 +552,7 @@ public class AppServices {
public static AppController newAppWindow(Stage stage) { public static AppController newAppWindow(Stage stage) {
try { 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(); Parent root = appLoader.load();
AppController appController = appLoader.getController(); AppController appController = appLoader.getController();

View file

@ -5,6 +5,8 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.control.WalletIcon; import com.sparrowwallet.sparrow.control.WalletIcon;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5; import com.sparrowwallet.sparrow.glyphfont.FontAwesome5;
import com.sparrowwallet.sparrow.glyphfont.FontAwesome5Brands; 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.Config;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.net.PublicElectrumServer; import com.sparrowwallet.sparrow.net.PublicElectrumServer;
@ -53,7 +55,7 @@ public class SparrowDesktop extends Application {
boolean createNewWallet = false; boolean createNewWallet = false;
Mode mode = Config.get().getMode(); Mode mode = Config.get().getMode();
if(mode == null) { if(mode == null) {
WelcomeDialog welcomeDialog = new WelcomeDialog(); WelcomeDialog welcomeDialog = new WelcomeDialog(true);
Optional<Mode> optionalMode = welcomeDialog.showAndWait(); Optional<Mode> optionalMode = welcomeDialog.showAndWait();
if(optionalMode.isPresent()) { if(optionalMode.isPresent()) {
mode = optionalMode.get(); mode = optionalMode.get();
@ -87,6 +89,13 @@ public class SparrowDesktop extends Application {
mainStage.setHeight(Config.get().getAppHeight()); 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); AppController appController = AppServices.newAppWindow(stage);
final boolean showNewWallet = createNewWallet; final boolean showNewWallet = createNewWallet;

View file

@ -1,22 +1,30 @@
package com.sparrowwallet.sparrow; package com.sparrowwallet.sparrow;
import com.google.common.eventbus.Subscribe;
import com.sparrowwallet.sparrow.control.UnlabeledToggleSwitch; 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.animation.PauseTransition;
import javafx.application.Platform; import javafx.collections.FXCollections;
import javafx.beans.value.ChangeListener; import javafx.collections.ObservableList;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.Event; import javafx.event.Event;
import javafx.event.EventHandler;
import javafx.event.EventType;
import javafx.fxml.FXML; 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.control.Tooltip;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent; import javafx.scene.input.MouseEvent;
import javafx.scene.layout.VBox; import javafx.scene.layout.VBox;
import javafx.util.Duration; import javafx.util.Duration;
import javafx.util.StringConverter;
import org.controlsfx.control.StatusBar; import org.controlsfx.control.StatusBar;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
public class WelcomeController { public class WelcomeController {
@FXML @FXML
private VBox welcomeBox; private VBox welcomeBox;
@ -36,10 +44,13 @@ public class WelcomeController {
@FXML @FXML
private StatusBar serverStatus; private StatusBar serverStatus;
@FXML
private ComboBox<Language> languages;
@FXML @FXML
private UnlabeledToggleSwitch serverToggle; private UnlabeledToggleSwitch serverToggle;
public void initializeView() { public void initializeView(boolean isFirstExecution) {
step1.managedProperty().bind(step1.visibleProperty()); step1.managedProperty().bind(step1.visibleProperty());
step2.managedProperty().bind(step2.visibleProperty()); step2.managedProperty().bind(step2.visibleProperty());
step3.managedProperty().bind(step3.visibleProperty()); step3.managedProperty().bind(step3.visibleProperty());
@ -50,14 +61,51 @@ public class WelcomeController {
step4.setVisible(false); step4.setVisible(false);
welcomeBox.getStyleClass().add("offline"); welcomeBox.getStyleClass().add("offline");
serverStatus.setText("Offline"); serverStatus.setText(LanguagesManager.getMessage("welcome.offline"));
serverToggle.addEventFilter(MouseEvent.MOUSE_RELEASED, Event::consume); 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); tooltip.setShowDelay(Duration.ZERO);
serverToggle.setTooltip(tooltip); serverToggle.setTooltip(tooltip);
serverToggle.selectedProperty().addListener((observable, oldValue, newValue) -> { 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() { public boolean next() {
@ -69,7 +117,7 @@ public class WelcomeController {
PauseTransition wait = new PauseTransition(Duration.millis(200)); PauseTransition wait = new PauseTransition(Duration.millis(200));
wait.setOnFinished((e) -> { wait.setOnFinished((e) -> {
serverToggle.setSelected(true); serverToggle.setSelected(true);
serverStatus.setText("Connected to a Public Server (demonstration only)"); serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.public-server"));
}); });
wait.play(); wait.play();
return true; return true;
@ -81,7 +129,7 @@ public class WelcomeController {
welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().clear();
welcomeBox.getStyleClass().add("bitcoin-core"); welcomeBox.getStyleClass().add("bitcoin-core");
serverToggle.setSelected(true); serverToggle.setSelected(true);
serverStatus.setText("Connected to Bitcoin Core (demonstration only)"); serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.bitcoin-core"));
return true; return true;
} }
@ -91,7 +139,7 @@ public class WelcomeController {
welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().clear();
welcomeBox.getStyleClass().add("private-electrum"); welcomeBox.getStyleClass().add("private-electrum");
serverToggle.setSelected(true); 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; return false;
@ -106,7 +154,7 @@ public class WelcomeController {
PauseTransition wait = new PauseTransition(Duration.millis(200)); PauseTransition wait = new PauseTransition(Duration.millis(200));
wait.setOnFinished((e) -> { wait.setOnFinished((e) -> {
serverToggle.setSelected(false); serverToggle.setSelected(false);
serverStatus.setText("Offline"); serverStatus.setText(LanguagesManager.getMessage("welcome.offline"));
}); });
wait.play(); wait.play();
return false; return false;
@ -118,7 +166,7 @@ public class WelcomeController {
welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().clear();
welcomeBox.getStyleClass().add("public-electrum"); welcomeBox.getStyleClass().add("public-electrum");
serverToggle.setSelected(true); serverToggle.setSelected(true);
serverStatus.setText("Connected to a Public Server (demonstration only)"); serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.public-server"));
return true; return true;
} }
@ -128,10 +176,15 @@ public class WelcomeController {
welcomeBox.getStyleClass().clear(); welcomeBox.getStyleClass().clear();
welcomeBox.getStyleClass().add("bitcoin-core"); welcomeBox.getStyleClass().add("bitcoin-core");
serverToggle.setSelected(true); serverToggle.setSelected(true);
serverStatus.setText("Connected to Bitcoin Core (demonstration only)"); serverStatus.setText(LanguagesManager.getMessage("welcome.server-status.online.bitcoin-core"));
return true; return true;
} }
return false; return false;
} }
private ObservableList<Language> getLanguagesList() {
List<Language> languages = Arrays.asList(Language.values());
return FXCollections.observableList(languages);
}
} }

View file

@ -1,5 +1,8 @@
package com.sparrowwallet.sparrow; 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.event.ActionEvent;
import javafx.fxml.FXMLLoader; import javafx.fxml.FXMLLoader;
import javafx.scene.control.*; import javafx.scene.control.*;
@ -9,16 +12,22 @@ import javafx.scene.image.ImageView;
import java.io.IOException; import java.io.IOException;
public class WelcomeDialog extends Dialog<Mode> { public class WelcomeDialog extends Dialog<Mode> {
public WelcomeDialog() {
private WelcomeController welcomeController;
public WelcomeDialog(boolean isFirstExecution) {
super();
final DialogPane dialogPane = getDialogPane(); final DialogPane dialogPane = getDialogPane();
AppServices.setStageIcon(dialogPane.getScene().getWindow()); AppServices.setStageIcon(dialogPane.getScene().getWindow());
AppServices.onEscapePressed(dialogPane.getScene(), this::close); AppServices.onEscapePressed(dialogPane.getScene(), this::close);
EventManager.get().register(this);
try { 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()); dialogPane.setContent(welcomeLoader.load());
WelcomeController welcomeController = welcomeLoader.getController(); welcomeController = welcomeLoader.getController();
welcomeController.initializeView(); welcomeController.initializeView(isFirstExecution);
dialogPane.setPrefWidth(600); dialogPane.setPrefWidth(600);
dialogPane.setPrefHeight(520); dialogPane.setPrefHeight(520);
@ -27,10 +36,10 @@ public class WelcomeDialog extends Dialog<Mode> {
dialogPane.getStylesheets().add(AppServices.class.getResource("welcome.css").toExternalForm()); dialogPane.getStylesheets().add(AppServices.class.getResource("welcome.css").toExternalForm());
final ButtonType nextButtonType = new javafx.scene.control.ButtonType("Next", ButtonBar.ButtonData.OK_DONE); final ButtonType nextButtonType = new javafx.scene.control.ButtonType(LanguagesManager.getMessage("welcome.next"), ButtonBar.ButtonData.OK_DONE);
final ButtonType backButtonType = new javafx.scene.control.ButtonType("Back", ButtonBar.ButtonData.LEFT); final ButtonType backButtonType = new javafx.scene.control.ButtonType(LanguagesManager.getMessage("welcome.back"), ButtonBar.ButtonData.LEFT);
final ButtonType onlineButtonType = new javafx.scene.control.ButtonType("Configure Server", ButtonBar.ButtonData.APPLY); 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() ? "Done" : "Later or Offline Mode", ButtonBar.ButtonData.CANCEL_CLOSE); 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); dialogPane.getButtonTypes().addAll(nextButtonType, backButtonType, onlineButtonType, offlineButtonType);
Button nextButton = (Button)dialogPane.lookupButton(nextButtonType); Button nextButton = (Button)dialogPane.lookupButton(nextButtonType);
@ -69,4 +78,18 @@ public class WelcomeDialog extends Dialog<Mode> {
throw new RuntimeException(e); 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);
}
}
} }

View file

@ -9,6 +9,7 @@ import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent; import com.sparrowwallet.sparrow.event.WalletAddressesChangedEvent;
import com.sparrowwallet.sparrow.event.WalletDataChangedEvent; import com.sparrowwallet.sparrow.event.WalletDataChangedEvent;
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
import com.sparrowwallet.sparrow.i18n.LanguagesManager;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
import com.sparrowwallet.sparrow.net.ServerType; import com.sparrowwallet.sparrow.net.ServerType;
@ -86,12 +87,12 @@ public class CoinTreeTable extends TreeTableView<Entry> {
if(entry != null && event.getWallet() != null && entry.getWallet() == event.getWallet()) { if(entry != null && event.getWallet() != null && entry.getWallet() == event.getWallet()) {
Platform.runLater(() -> { Platform.runLater(() -> {
if(event.getErrorMessage() != null) { 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()) { } else if(event.isLoading()) {
if(event.getStatusMessage() != null) { if(event.getStatusMessage() != null) {
setPlaceholder(new Label(event.getStatusMessage() + "...")); setPlaceholder(new Label(event.getStatusMessage() + "..."));
} else { } else {
setPlaceholder(new Label("Loading transactions...")); setPlaceholder(new Label(LanguagesManager.getMessage("wallet.transactions.table.loading")));
} }
} else { } else {
setPlaceholder(getDefaultPlaceholder(event.getWallet())); setPlaceholder(getDefaultPlaceholder(event.getWallet()));
@ -103,7 +104,7 @@ public class CoinTreeTable extends TreeTableView<Entry> {
protected Node getDefaultPlaceholder(Wallet wallet) { protected Node getDefaultPlaceholder(Wallet wallet) {
StackPane stackPane = new StackPane(); 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()) { if(Config.get().getServerType() == ServerType.BITCOIN_CORE && !AppServices.isConnecting()) {
Hyperlink hyperlink = new Hyperlink(); Hyperlink hyperlink = new Hyperlink();

View file

@ -1,5 +1,6 @@
package com.sparrowwallet.sparrow.control; package com.sparrowwallet.sparrow.control;
import com.sparrowwallet.sparrow.i18n.LanguagesManager;
import com.sparrowwallet.sparrow.wallet.Entry; import com.sparrowwallet.sparrow.wallet.Entry;
import com.sparrowwallet.sparrow.wallet.TransactionEntry; import com.sparrowwallet.sparrow.wallet.TransactionEntry;
import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry; import com.sparrowwallet.sparrow.wallet.WalletTransactionsEntry;
@ -15,7 +16,7 @@ public class TransactionsTreeTable extends CoinTreeTable {
updateAll(rootEntry); updateAll(rootEntry);
setShowRoot(false); setShowRoot(false);
TreeTableColumn<Entry, Entry> dateCol = new TreeTableColumn<>("Date"); TreeTableColumn<Entry, Entry> dateCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.date"));
dateCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> { dateCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Entry> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue()); return new ReadOnlyObjectWrapper<>(param.getValue().getValue());
}); });
@ -23,7 +24,7 @@ public class TransactionsTreeTable extends CoinTreeTable {
dateCol.setSortable(true); dateCol.setSortable(true);
getColumns().add(dateCol); getColumns().add(dateCol);
TreeTableColumn<Entry, String> labelCol = new TreeTableColumn<>("Label"); TreeTableColumn<Entry, String> labelCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.label"));
labelCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> { labelCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, String> param) -> {
return param.getValue().getValue().labelProperty(); return param.getValue().getValue().labelProperty();
}); });
@ -31,7 +32,7 @@ public class TransactionsTreeTable extends CoinTreeTable {
labelCol.setSortable(true); labelCol.setSortable(true);
getColumns().add(labelCol); getColumns().add(labelCol);
TreeTableColumn<Entry, Number> amountCol = new TreeTableColumn<>("Value"); TreeTableColumn<Entry, Number> amountCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.value"));
amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> { amountCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> {
return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue()); return new ReadOnlyObjectWrapper<>(param.getValue().getValue().getValue());
}); });
@ -39,7 +40,7 @@ public class TransactionsTreeTable extends CoinTreeTable {
amountCol.setSortable(true); amountCol.setSortable(true);
getColumns().add(amountCol); getColumns().add(amountCol);
TreeTableColumn<Entry, Number> balanceCol = new TreeTableColumn<>("Balance"); TreeTableColumn<Entry, Number> balanceCol = new TreeTableColumn<>(LanguagesManager.getMessage("wallet.transactions.table.balance"));
balanceCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> { balanceCol.setCellValueFactory((TreeTableColumn.CellDataFeatures<Entry, Number> param) -> {
return param.getValue().getValue() instanceof TransactionEntry ? ((TransactionEntry)param.getValue().getValue()).balanceProperty() : new ReadOnlyObjectWrapper<>(null); return param.getValue().getValue() instanceof TransactionEntry ? ((TransactionEntry)param.getValue().getValue()).balanceProperty() : new ReadOnlyObjectWrapper<>(null);
}); });

View file

@ -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;
}
}

View file

@ -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;
}
}

View file

@ -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); }
}

View file

@ -6,6 +6,7 @@ import com.sparrowwallet.sparrow.UnitFormat;
import com.sparrowwallet.sparrow.Mode; import com.sparrowwallet.sparrow.Mode;
import com.sparrowwallet.sparrow.Theme; import com.sparrowwallet.sparrow.Theme;
import com.sparrowwallet.sparrow.control.QRDensity; import com.sparrowwallet.sparrow.control.QRDensity;
import com.sparrowwallet.sparrow.i18n.Language;
import com.sparrowwallet.sparrow.net.*; import com.sparrowwallet.sparrow.net.*;
import com.sparrowwallet.sparrow.wallet.FeeRatesSelection; import com.sparrowwallet.sparrow.wallet.FeeRatesSelection;
import com.sparrowwallet.sparrow.wallet.OptimizationStrategy; import com.sparrowwallet.sparrow.wallet.OptimizationStrategy;
@ -43,6 +44,7 @@ public class Config {
private boolean notifyNewTransactions = true; private boolean notifyNewTransactions = true;
private boolean checkNewVersions = true; private boolean checkNewVersions = true;
private Theme theme; private Theme theme;
private Language language;
private boolean openWalletsInNewWindows = false; private boolean openWalletsInNewWindows = false;
private boolean hideEmptyUsedAddresses = false; private boolean hideEmptyUsedAddresses = false;
private boolean showTransactionHex = true; private boolean showTransactionHex = true;
@ -277,6 +279,15 @@ public class Config {
flush(); flush();
} }
public Language getLanguage() {
return language;
}
public void setLanguage(Language language) {
this.language = language;
flush();
}
public boolean isOpenWalletsInNewWindows() { public boolean isOpenWalletsInNewWindows() {
return openWalletsInNewWindows; return openWalletsInNewWindows;
} }

View file

@ -9,6 +9,7 @@ import com.sparrowwallet.drongo.protocol.Sha256Hash;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
import com.sparrowwallet.sparrow.i18n.LanguagesManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -82,7 +83,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Map<String, ScriptHashTx[]> getScriptHashHistory(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes, boolean failOnError) { public Map<String, ScriptHashTx[]> getScriptHashHistory(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes, boolean failOnError) {
PagedBatchRequestBuilder<String, ScriptHashTx[]> batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(String.class).returnType(ScriptHashTx[].class); PagedBatchRequestBuilder<String, ScriptHashTx[]> 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()) { for(String path : pathScriptHashes.keySet()) {
batchRequest.add(path, "blockchain.scripthash.get_history", pathScriptHashes.get(path)); batchRequest.add(path, "blockchain.scripthash.get_history", pathScriptHashes.get(path));
@ -137,7 +138,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Map<String, String> subscribeScriptHashes(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes) { public Map<String, String> subscribeScriptHashes(Transport transport, Wallet wallet, Map<String, String> pathScriptHashes) {
PagedBatchRequestBuilder<String, String> batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(String.class).returnType(String.class); PagedBatchRequestBuilder<String, String> 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()) { for(String path : pathScriptHashes.keySet()) {
batchRequest.add(path, "blockchain.scripthash.subscribe", pathScriptHashes.get(path)); batchRequest.add(path, "blockchain.scripthash.subscribe", pathScriptHashes.get(path));
@ -157,7 +158,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Map<Integer, String> getBlockHeaders(Transport transport, Wallet wallet, Set<Integer> blockHeights) { public Map<Integer, String> getBlockHeaders(Transport transport, Wallet wallet, Set<Integer> blockHeights) {
PagedBatchRequestBuilder<Integer, String> batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(Integer.class).returnType(String.class); PagedBatchRequestBuilder<Integer, String> 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) { for(Integer height : blockHeights) {
batchRequest.add(height, "blockchain.block.header", height); batchRequest.add(height, "blockchain.block.header", height);
@ -176,7 +177,7 @@ public class BatchedElectrumServerRpc implements ElectrumServerRpc {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public Map<String, String> getTransactions(Transport transport, Wallet wallet, Set<String> txids) { public Map<String, String> getTransactions(Transport transport, Wallet wallet, Set<String> txids) {
PagedBatchRequestBuilder<String, String> batchRequest = PagedBatchRequestBuilder.create(transport, idCounter).keysType(String.class).returnType(String.class); PagedBatchRequestBuilder<String, String> 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) { for(String txid : txids) {
batchRequest.add(txid, "blockchain.transaction.get", txid); batchRequest.add(txid, "blockchain.transaction.get", txid);

View file

@ -10,6 +10,7 @@ import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.sparrow.AppServices; import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent; import com.sparrowwallet.sparrow.event.WalletHistoryStatusEvent;
import com.sparrowwallet.sparrow.i18n.LanguagesManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
@ -76,7 +77,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map<String, ScriptHashTx[]> result = new LinkedHashMap<>(); Map<String, ScriptHashTx[]> result = new LinkedHashMap<>();
for(String path : pathScriptHashes.keySet()) { 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 { try {
ScriptHashTx[] scriptHashTxes = new RetryLogic<ScriptHashTx[]>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> ScriptHashTx[] scriptHashTxes = new RetryLogic<ScriptHashTx[]>(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()); client.createRequest().returnAs(ScriptHashTx[].class).method("blockchain.scripthash.get_history").id(idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).execute());
@ -121,7 +122,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map<String, String> result = new LinkedHashMap<>(); Map<String, String> result = new LinkedHashMap<>();
for(String path : pathScriptHashes.keySet()) { 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 { try {
String scriptHash = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> String scriptHash = new RetryLogic<String>(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()); client.createRequest().returnAs(String.class).method("blockchain.scripthash.subscribe").id(idCounter.incrementAndGet()).params(pathScriptHashes.get(path)).executeNullable());
@ -141,7 +142,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map<Integer, String> result = new LinkedHashMap<>(); Map<Integer, String> result = new LinkedHashMap<>();
for(Integer blockHeight : blockHeights) { 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 { try {
String blockHeader = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> String blockHeader = new RetryLogic<String>(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()); client.createRequest().returnAs(String.class).method("blockchain.block.header").id(idCounter.incrementAndGet()).params(blockHeight).execute());
@ -165,7 +166,7 @@ public class SimpleElectrumServerRpc implements ElectrumServerRpc {
Map<String, String> result = new LinkedHashMap<>(); Map<String, String> result = new LinkedHashMap<>();
for(String txid : txids) { 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 { try {
String rawTxHex = new RetryLogic<String>(MAX_RETRIES, RETRY_DELAY, List.of(IllegalStateException.class, IllegalArgumentException.class)).getResult(() -> String rawTxHex = new RetryLogic<String>(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()); client.createRequest().returnAs(String.class).method("blockchain.transaction.get").id(idCounter.incrementAndGet()).params(txid).execute());

View file

@ -5,6 +5,7 @@ import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.*; import com.sparrowwallet.sparrow.control.*;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.i18n.LanguagesManager;
import com.sparrowwallet.sparrow.io.Config; import com.sparrowwallet.sparrow.io.Config;
import com.sparrowwallet.sparrow.io.WalletTransactions; import com.sparrowwallet.sparrow.io.WalletTransactions;
import com.sparrowwallet.sparrow.net.ExchangeSource; import com.sparrowwallet.sparrow.net.ExchangeSource;
@ -96,7 +97,7 @@ public class TransactionsController extends WalletFormController implements Init
}); });
transactionsMasterDetail.setShowDetailNode(Config.get().isShowLoadingLog()); 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); loadingLog.setEditable(false);
} }
@ -109,7 +110,7 @@ public class TransactionsController extends WalletFormController implements Init
Stage window = new Stage(); Stage window = new Stage();
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
fileChooser.setTitle("Export Transactions as CSV"); fileChooser.setTitle(LanguagesManager.getMessage("wallet.transactions.csv-export"));
fileChooser.setInitialFileName(wallet.getFullName() + "-transactions.csv"); fileChooser.setInitialFileName(wallet.getFullName() + "-transactions.csv");
AppServices.moveToActiveWindowScreen(window, 800, 450); AppServices.moveToActiveWindowScreen(window, 800, 450);
File file = fileChooser.showSaveDialog(window); File file = fileChooser.showSaveDialog(window);
@ -118,7 +119,7 @@ public class TransactionsController extends WalletFormController implements Init
exportService.setOnFailed(failedEvent -> { exportService.setOnFailed(failedEvent -> {
Throwable e = failedEvent.getSource().getException(); Throwable e = failedEvent.getSource().getException();
log.error("Error exporting transactions as CSV", e); 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(); exportService.start();
} }
@ -212,7 +213,7 @@ public class TransactionsController extends WalletFormController implements Init
String logMessage = event.getStatusMessage(); String logMessage = event.getStatusMessage();
if(logMessage == null) { if(logMessage == null) {
if(event instanceof WalletHistoryFinishedEvent) { if(event instanceof WalletHistoryFinishedEvent) {
logMessage = "Finished loading."; logMessage = LanguagesManager.getMessage("wallet.transactions.loading-finish");
} else if(event instanceof WalletHistoryFailedEvent) { } else if(event instanceof WalletHistoryFailedEvent) {
logMessage = event.getErrorMessage(); logMessage = event.getErrorMessage();
} }

View file

@ -8,6 +8,7 @@ import com.sparrowwallet.sparrow.AppServices;
import com.sparrowwallet.sparrow.EventManager; import com.sparrowwallet.sparrow.EventManager;
import com.sparrowwallet.sparrow.control.ViewPasswordField; import com.sparrowwallet.sparrow.control.ViewPasswordField;
import com.sparrowwallet.sparrow.event.*; import com.sparrowwallet.sparrow.event.*;
import com.sparrowwallet.sparrow.i18n.LanguagesManager;
import com.sparrowwallet.sparrow.io.Storage; import com.sparrowwallet.sparrow.io.Storage;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.BooleanProperty; import javafx.beans.property.BooleanProperty;
@ -95,7 +96,7 @@ public class WalletController extends WalletFormController implements Initializa
throw new IllegalStateException("Cannot find wallet/" + function.toString().toLowerCase(Locale.ROOT) + ".fxml"); 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(); Node walletFunction = functionLoader.load();
walletFunction.setUserData(function); walletFunction.setUserData(function);
WalletFormController controller = functionLoader.getController(); WalletFormController controller = functionLoader.getController();

View file

@ -9,6 +9,7 @@
<?import com.sparrowwallet.drongo.BitcoinUnit?> <?import com.sparrowwallet.drongo.BitcoinUnit?>
<?import com.sparrowwallet.sparrow.UnitFormat?> <?import com.sparrowwallet.sparrow.UnitFormat?>
<?import com.sparrowwallet.sparrow.Theme?> <?import com.sparrowwallet.sparrow.Theme?>
<?import com.sparrowwallet.sparrow.i18n.Language?>
<?import impl.org.controlsfx.skin.DecorationPane?> <?import impl.org.controlsfx.skin.DecorationPane?>
<VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="200" minWidth="350" prefHeight="770.0" prefWidth="1070.0" fx:controller="com.sparrowwallet.sparrow.AppController" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1"> <VBox maxHeight="-Infinity" maxWidth="-Infinity" minHeight="200" minWidth="350" prefHeight="770.0" prefWidth="1070.0" fx:controller="com.sparrowwallet.sparrow.AppController" xmlns="http://javafx.com/javafx/10.0.2-internal" xmlns:fx="http://javafx.com/fxml/1">
@ -64,6 +65,9 @@
<fx:define> <fx:define>
<ToggleGroup fx:id="theme"/> <ToggleGroup fx:id="theme"/>
</fx:define> </fx:define>
<fx:define>
<ToggleGroup fx:id="language"/>
</fx:define>
<Menu fx:id="viewMenu" mnemonicParsing="false" text="View"> <Menu fx:id="viewMenu" mnemonicParsing="false" text="View">
<items> <items>
<Menu mnemonicParsing="false" text="Bitcoin Unit"> <Menu mnemonicParsing="false" text="Bitcoin Unit">
@ -113,6 +117,20 @@
</RadioMenuItem> </RadioMenuItem>
</items> </items>
</Menu> </Menu>
<Menu mnemonicParsing="false" text="%language">
<items>
<RadioMenuItem mnemonicParsing="false" text="%language.en" toggleGroup="$language" onAction="#setLanguage">
<userData>
<Language fx:constant="ENGLISH" />
</userData>
</RadioMenuItem>
<RadioMenuItem mnemonicParsing="false" text="%language.es" toggleGroup="$language" onAction="#setLanguage">
<userData>
<Language fx:constant="SPANISH" />
</userData>
</RadioMenuItem>
</items>
</Menu>
<SeparatorMenuItem /> <SeparatorMenuItem />
<CheckMenuItem fx:id="openWalletsInNewWindows" mnemonicParsing="false" text="Open Wallets In New Windows" onAction="#openWalletsInNewWindows"/> <CheckMenuItem fx:id="openWalletsInNewWindows" mnemonicParsing="false" text="Open Wallets In New Windows" onAction="#openWalletsInNewWindows"/>
<CheckMenuItem fx:id="hideEmptyUsedAddresses" mnemonicParsing="false" text="Hide Empty Used Addresses" onAction="#hideEmptyUsedAddresses"/> <CheckMenuItem fx:id="hideEmptyUsedAddresses" mnemonicParsing="false" text="Hide Empty Used Addresses" onAction="#hideEmptyUsedAddresses"/>

View file

@ -33,21 +33,21 @@
<RowConstraints /> <RowConstraints />
</rowConstraints> </rowConstraints>
<Form GridPane.columnIndex="0" GridPane.rowIndex="0"> <Form GridPane.columnIndex="0" GridPane.rowIndex="0">
<Fieldset inputGrow="SOMETIMES" text="Transactions" styleClass="header"> <Fieldset inputGrow="SOMETIMES" text="%wallet.transactions" styleClass="header">
<Field text="Balance:"> <Field text="%wallet.transactions.balance">
<CopyableCoinLabel fx:id="balance"/><Region HBox.hgrow="ALWAYS"/><FiatLabel fx:id="fiatBalance" minWidth="110" /> <CopyableCoinLabel fx:id="balance"/><Region HBox.hgrow="ALWAYS"/><FiatLabel fx:id="fiatBalance" minWidth="110" />
</Field> </Field>
<Field text="Mempool:"> <Field text="Mempool:">
<CopyableCoinLabel fx:id="mempoolBalance" /><Region HBox.hgrow="ALWAYS"/><FiatLabel fx:id="fiatMempoolBalance" minWidth="110" /> <CopyableCoinLabel fx:id="mempoolBalance" /><Region HBox.hgrow="ALWAYS"/><FiatLabel fx:id="fiatMempoolBalance" minWidth="110" />
</Field> </Field>
<Field text="Transactions:"> <Field text="%wallet.transactions.transactions">
<CopyableLabel fx:id="transactionCount" /> <CopyableLabel fx:id="transactionCount" />
<Button fx:id="exportCsv" maxHeight="25" onAction="#exportCSV" translateY="-1" styleClass="icon-button"> <Button fx:id="exportCsv" maxHeight="25" onAction="#exportCSV" translateY="-1" styleClass="icon-button">
<graphic> <graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" icon="ARROW_CIRCLE_DOWN" fontSize="12" /> <Glyph fontFamily="Font Awesome 5 Free Solid" icon="ARROW_CIRCLE_DOWN" fontSize="12" />
</graphic> </graphic>
<tooltip> <tooltip>
<Tooltip text="Export transactions as CSV" /> <Tooltip text="%wallet.transactions.csv-export" />
</tooltip> </tooltip>
</Button> </Button>
</Field> </Field>

View file

@ -11,7 +11,7 @@
<BorderPane stylesheets="@wallet.css, @../general.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.wallet.WalletController"> <BorderPane stylesheets="@wallet.css, @../general.css" xmlns="http://javafx.com/javafx" xmlns:fx="http://javafx.com/fxml" fx:controller="com.sparrowwallet.sparrow.wallet.WalletController">
<left> <left>
<VBox fx:id="walletMenuBox" styleClass="list-menu"> <VBox fx:id="walletMenuBox" styleClass="list-menu">
<ToggleButton VBox.vgrow="ALWAYS" text="Transactions" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity"> <ToggleButton VBox.vgrow="ALWAYS" text="%wallet.transactions" contentDisplay="TOP" styleClass="list-item" maxHeight="Infinity">
<toggleGroup> <toggleGroup>
<ToggleGroup fx:id="walletMenu" /> <ToggleGroup fx:id="walletMenu" />
</toggleGroup> </toggleGroup>

View file

@ -15,7 +15,7 @@
<VBox spacing="20"> <VBox spacing="20">
<HBox styleClass="title-area"> <HBox styleClass="title-area">
<HBox alignment="CENTER_LEFT"> <HBox alignment="CENTER_LEFT">
<Label fx:id="title" text="Welcome to Sparrow" styleClass="title-label" /> <Label fx:id="title" text="%welcome.title" styleClass="title-label" />
</HBox> </HBox>
<Region HBox.hgrow="ALWAYS"/> <Region HBox.hgrow="ALWAYS"/>
<ImageView AnchorPane.rightAnchor="0"> <ImageView AnchorPane.rightAnchor="0">
@ -24,44 +24,48 @@
</HBox> </HBox>
<VBox fx:id="welcomeBox" styleClass="content-area" spacing="20" prefHeight="370"> <VBox fx:id="welcomeBox" styleClass="content-area" spacing="20" prefHeight="370">
<VBox fx:id="step1" spacing="15"> <VBox fx:id="step1" spacing="15">
<Label text="Introduction" styleClass="title-text"> <HBox>
<Label text="%welcome.step1.introduction" styleClass="title-text">
<graphic> <graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_OFF" styleClass="title-icon" /> <Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_OFF" styleClass="title-icon" />
</graphic> </graphic>
</Label> </Label>
<Label text="Sparrow is a Bitcoin wallet with a focus on security and usability." wrapText="true" styleClass="content-text" /> <Region HBox.hgrow="ALWAYS" />
<Label text="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." wrapText="true" styleClass="content-text" /> <ComboBox fx:id="languages"/>
<Label text="The status bar at the bottom displays the connection status, as demonstrated below:" wrapText="true" styleClass="content-text" /> </HBox>
<Label text="%welcome.step1.label1" wrapText="true" styleClass="content-text" />
<Label text="%welcome.step1.label2" wrapText="true" styleClass="content-text" />
<Label text="%welcome.step1.label3" wrapText="true" styleClass="content-text" />
</VBox> </VBox>
<VBox fx:id="step2" spacing="15"> <VBox fx:id="step2" spacing="15">
<Label text="Connecting to a Public Server" styleClass="title-text"> <Label text="%welcome.step2.introduction" styleClass="title-text">
<graphic> <graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_ON" styleClass="title-icon" /> <Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_ON" styleClass="title-icon" />
</graphic> </graphic>
</Label> </Label>
<Label text="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. " wrapText="true" styleClass="content-text" /> <Label text="%welcome.step2.label1" wrapText="true" styleClass="content-text" />
<Label text="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." wrapText="true" styleClass="content-text" /> <Label text="%welcome.step2.label2" wrapText="true" styleClass="content-text" />
<Label text="A yellow toggle means you are connected to a public server." wrapText="true" styleClass="content-text" /> <Label text="%welcome.step2.label3" wrapText="true" styleClass="content-text" />
</VBox> </VBox>
<VBox fx:id="step3" spacing="15"> <VBox fx:id="step3" spacing="15">
<Label text="Connecting to a Bitcoin Core node" styleClass="title-text"> <Label text="%welcome.step3.introduction" styleClass="title-text">
<graphic> <graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_ON" styleClass="title-icon" /> <Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_ON" styleClass="title-icon" />
</graphic> </graphic>
</Label> </Label>
<Label text="If you are running your own Bitcoin Core node, you can configure Sparrow to connect to it directly. " wrapText="true" styleClass="content-text" /> <Label text="%welcome.step3.label1" wrapText="true" styleClass="content-text" />
<Label text="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." wrapText="true" styleClass="content-text" /> <Label text="%welcome.step3.label2" wrapText="true" styleClass="content-text" />
<Label text="A green toggle means you are connected to a Bitcoin Core node." wrapText="true" styleClass="content-text" /> <Label text="%welcome.step3.label3" wrapText="true" styleClass="content-text" />
</VBox> </VBox>
<VBox fx:id="step4" spacing="15"> <VBox fx:id="step4" spacing="15">
<Label text="Connecting to a Private Electrum Server" styleClass="title-text"> <Label text="%welcome.step4.introduction" styleClass="title-text">
<graphic> <graphic>
<Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_ON" styleClass="title-icon" /> <Glyph fontFamily="Font Awesome 5 Free Solid" fontSize="20" icon="TOGGLE_ON" styleClass="title-icon" />
</graphic> </graphic>
</Label> </Label>
<Label text="The most private way to connect is to your own Electrum server, which in turn connects to your Bitcoin Core node." wrapText="true" styleClass="content-text" /> <Label text="%welcome.step4.label1" wrapText="true" styleClass="content-text" />
<Label text="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." wrapText="true" styleClass="content-text" /> <Label text="%welcome.step4.label2" wrapText="true" styleClass="content-text" />
<Label text="A blue toggle means you are connected to a private Electrum server. You're now ready to configure a server and start using Sparrow!" wrapText="true" styleClass="content-text" /> <Label text="%welcome.step4.label3" wrapText="true" styleClass="content-text" />
</VBox> </VBox>
<Region VBox.vgrow="ALWAYS" /> <Region VBox.vgrow="ALWAYS" />
<StatusBar fx:id="serverStatus"> <StatusBar fx:id="serverStatus">

View file

@ -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

View file

@ -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