From 83f92f1ef3e024e4547ddf4d4e646c4e82379b09 Mon Sep 17 00:00:00 2001 From: teridax Date: Sun, 16 Jul 2023 23:15:26 +0200 Subject: [PATCH] added translation capabilities --- src/me/teridax/jcash/Main.java | 10 +- src/me/teridax/jcash/gui/Loader.java | 3 +- .../jcash/gui/account/AccountView.java | 27 ++-- .../jcash/gui/deposit/DepositView.java | 12 +- .../jcash/gui/login/LoginController.java | 7 +- src/me/teridax/jcash/gui/login/LoginView.java | 12 +- .../jcash/gui/takeoff/TakeoffDialog.java | 3 +- .../jcash/gui/takeoff/TakeoffView.java | 13 +- .../jcash/gui/transfer/TransferDialog.java | 4 +- .../jcash/gui/transfer/TransferView.java | 17 +-- src/me/teridax/jcash/lang/Locales.java | 56 ++++++++ src/me/teridax/jcash/lang/Translator.java | 122 ++++++++++++++++++ src/me/teridax/jcash/lang/languages.csv | 48 +++++++ 13 files changed, 288 insertions(+), 46 deletions(-) create mode 100644 src/me/teridax/jcash/lang/Locales.java create mode 100644 src/me/teridax/jcash/lang/Translator.java create mode 100644 src/me/teridax/jcash/lang/languages.csv diff --git a/src/me/teridax/jcash/Main.java b/src/me/teridax/jcash/Main.java index d6e3cfc..d438122 100644 --- a/src/me/teridax/jcash/Main.java +++ b/src/me/teridax/jcash/Main.java @@ -3,6 +3,7 @@ package me.teridax.jcash; import me.teridax.jcash.gui.Loader; import me.teridax.jcash.gui.account.AccountController; import me.teridax.jcash.gui.login.LoginController; +import me.teridax.jcash.lang.Locales; import javax.swing.*; import java.util.Objects; @@ -12,6 +13,7 @@ import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.showMessageDialog; import static me.teridax.jcash.Logging.LOGGER; import static me.teridax.jcash.Logging.initializeSystemLogger; +import static me.teridax.jcash.lang.Translator.translate; public final class Main { @@ -28,7 +30,7 @@ public final class Main { private Main() { // create main window and set defaults this.window = new JFrame(); - this.window.setTitle("Bankautomat"); + this.window.setTitle(translate("Cashmachine")); this.window.setLocationByPlatform(true); this.window.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } @@ -36,6 +38,8 @@ public final class Main { public static void main(String[] args) { initializeSystemLogger(Level.FINE); + Locales.autodetectDefaultLocale(); + // create main instance and show the login screen instance(); getInstance().showLoginScreen(); @@ -97,7 +101,7 @@ public final class Main { } catch (IllegalStateException e) { LOGGER.fine("Unable to show login mask: " + e.getMessage()); - showMessageDialog(null, e.getMessage(), "Closing JCash", ERROR_MESSAGE); + showMessageDialog(null, e.getMessage(), translate("Closing JCash"), ERROR_MESSAGE); System.exit(0); } }); @@ -107,7 +111,7 @@ public final class Main { * Logs the user out of the database, hiding the main window. */ public void logout() { - window.setContentPane(new JLabel("you're logged out")); + window.setContentPane(new JLabel(translate("you're logged out"))); window.setVisible(false); } } \ No newline at end of file diff --git a/src/me/teridax/jcash/gui/Loader.java b/src/me/teridax/jcash/gui/Loader.java index fe0ec6b..a9fce6a 100644 --- a/src/me/teridax/jcash/gui/Loader.java +++ b/src/me/teridax/jcash/gui/Loader.java @@ -2,6 +2,7 @@ package me.teridax.jcash.gui; import me.teridax.jcash.Logging; import me.teridax.jcash.banking.management.BankingManagementSystem; +import me.teridax.jcash.lang.Translator; import javax.swing.*; import javax.swing.filechooser.FileNameExtensionFilter; @@ -34,7 +35,7 @@ public class Loader { fileChooser.setDialogType(JFileChooser.OPEN_DIALOG); fileChooser.setAcceptAllFileFilterUsed(false); - if (fileChooser.showDialog(null, "Load database") == APPROVE_OPTION) { + if (fileChooser.showDialog(null, Translator.translate("Load database")) == APPROVE_OPTION) { // parse file content try { return BankingManagementSystem.loadFromCsv(fileChooser.getSelectedFile().toPath()); diff --git a/src/me/teridax/jcash/gui/account/AccountView.java b/src/me/teridax/jcash/gui/account/AccountView.java index 2f29caf..6381130 100644 --- a/src/me/teridax/jcash/gui/account/AccountView.java +++ b/src/me/teridax/jcash/gui/account/AccountView.java @@ -10,6 +10,7 @@ import javax.swing.*; import java.awt.*; import static javax.swing.SwingConstants.RIGHT; +import static me.teridax.jcash.lang.Translator.translate; public class AccountView extends JPanel { @@ -52,10 +53,10 @@ public class AccountView extends JPanel { this.type.setText(account.getClass().getSimpleName()); if (account instanceof CurrentAccount) {; - this.typeSpecialLabel.setText("Overdraft"); + this.typeSpecialLabel.setText(translate("Overdraft")); this.typeSpecialProperty.setText( StringDecoder.LOCAL_NUMBER_FORMAT.format(((CurrentAccount) account).getOverdraft()) + " €"); } else if (account instanceof SavingsAccount) { - this.typeSpecialLabel.setText("Interest rate"); + this.typeSpecialLabel.setText(translate("Interest rate")); this.typeSpecialProperty.setText( ((SavingsAccount) account).getInterest() + " %" ); } else { Logging.LOGGER.severe("Type of new primary account cannot be determined: " + account.getClass().getName()); @@ -84,14 +85,14 @@ public class AccountView extends JPanel { accountSelectionPanel.add(iban, BorderLayout.CENTER); accountSelectionPanel.add(accountSelection, BorderLayout.EAST); - addInputRow(constraints, content, accountSelectionPanel, 1, new JLabel("IBAN", RIGHT)); - addInputRow(constraints, content, name, 2, new JLabel("Name/Family-name", RIGHT)); - addInputRow(constraints, content, address, 3, new JLabel("Address", RIGHT)); - addInputRow(constraints, content, bankName, 4, new JLabel("Bank", RIGHT)); - addInputRow(constraints, content, blz, 5, new JLabel("BLZ", RIGHT)); - addInputRow(constraints, content, type, 6, new JLabel("Account", RIGHT)); + addInputRow(constraints, content, accountSelectionPanel, 1, new JLabel(translate("IBAN"), RIGHT)); + addInputRow(constraints, content, name, 2, new JLabel(translate("Name/Family-name"), RIGHT)); + addInputRow(constraints, content, address, 3, new JLabel(translate("Address"), RIGHT)); + addInputRow(constraints, content, bankName, 4, new JLabel(translate("Bank"), RIGHT)); + addInputRow(constraints, content, blz, 5, new JLabel(translate("BLZ"), RIGHT)); + addInputRow(constraints, content, type, 6, new JLabel(translate("Account"), RIGHT)); addInputRow(constraints, content, typeSpecialProperty, 7, typeSpecialLabel); - addInputRow(constraints, content, balance, 8, new JLabel("Balance", RIGHT)); + addInputRow(constraints, content, balance, 8, new JLabel(translate("Balance"), RIGHT)); var buttonPanel = Box.createHorizontalBox(); buttonPanel.add(Box.createHorizontalStrut(4)); @@ -129,10 +130,10 @@ public class AccountView extends JPanel { this.accountSelection = new JComboBox<>(); - this.logout = new JButton("Logout"); - this.transfer = new JButton("Transfer"); - this.deposit = new JButton("Deposit"); - this.takeoff = new JButton("Takeoff"); + this.logout = new JButton(translate("Logout")); + this.transfer = new JButton(translate("Transfer")); + this.deposit = new JButton(translate("Deposit")); + this.takeoff = new JButton(translate("Takeoff")); } /** diff --git a/src/me/teridax/jcash/gui/deposit/DepositView.java b/src/me/teridax/jcash/gui/deposit/DepositView.java index 5c24469..b496001 100644 --- a/src/me/teridax/jcash/gui/deposit/DepositView.java +++ b/src/me/teridax/jcash/gui/deposit/DepositView.java @@ -8,6 +8,8 @@ import java.awt.*; import java.text.NumberFormat; import java.text.ParseException; +import static me.teridax.jcash.lang.Translator.translate; + public class DepositView { private JDialog dialog; @@ -22,7 +24,7 @@ public class DepositView { public void showDialog() { dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); - dialog.setTitle("Deposit money"); + dialog.setTitle(translate("Deposit money")); dialog.pack(); dialog.setResizable(false); dialog.setLocationRelativeTo(null); @@ -43,7 +45,7 @@ public class DepositView { c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.CENTER; c.insets = new Insets(4, 4, 4, 4); - dialog.getContentPane().add(new JLabel("Deposit money"), c); + dialog.getContentPane().add(new JLabel(translate("Deposit money")), c); c.gridx = 0; c.gridy = 1; @@ -51,7 +53,7 @@ public class DepositView { c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; - dialog.getContentPane().add(new JLabel("Value", SwingConstants.RIGHT), c); + dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c); c.gridx = 1; c.gridy = 1; @@ -83,8 +85,8 @@ public class DepositView { private void createComponents() { this.dialog = new JDialog(); - this.cancel = new JButton("Cancel"); - this.deposit = new JButton("Deposit"); + this.cancel = new JButton(translate("Cancel")); + this.deposit = new JButton(translate("Deposit")); this.value = new JFormattedTextField(StringDecoder.LOCAL_NUMBER_FORMAT); this.dialog.setContentPane(new JPanel(new GridBagLayout())); diff --git a/src/me/teridax/jcash/gui/login/LoginController.java b/src/me/teridax/jcash/gui/login/LoginController.java index 3b16452..bda542b 100644 --- a/src/me/teridax/jcash/gui/login/LoginController.java +++ b/src/me/teridax/jcash/gui/login/LoginController.java @@ -8,6 +8,7 @@ import java.util.Optional; import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.showMessageDialog; +import static me.teridax.jcash.lang.Translator.translate; public class LoginController { @@ -52,14 +53,14 @@ public class LoginController { var iban = this.getIban(); if (iban.isEmpty()) { Logging.LOGGER.severe("IBAN is invalid: " + iban); - showMessageDialog(null, "invalid IBAN", "Faulty login attempt", ERROR_MESSAGE); + showMessageDialog(null, translate("Invalid IBAN"), translate("Faulty login attempt"), ERROR_MESSAGE); return; } var pin = this.getPin(); if (pin.isEmpty()) { Logging.LOGGER.severe("PIN is invalid: " + pin); - showMessageDialog(null, "invalid pin", "Faulty login attempt", ERROR_MESSAGE); + showMessageDialog(null, translate("Invalid pin"), translate("Faulty login attempt"), ERROR_MESSAGE); return; } @@ -68,7 +69,7 @@ public class LoginController { this.listener.onAccountSelected(account.get()); } else { Logging.LOGGER.severe("invalid login credentials: " + iban + " / " + pin); - showMessageDialog(null, "invalid login credentials", "Faulty login attempt", ERROR_MESSAGE); + showMessageDialog(null, translate("Invalid login credentials"), translate("Faulty login attempt"), ERROR_MESSAGE); } } diff --git a/src/me/teridax/jcash/gui/login/LoginView.java b/src/me/teridax/jcash/gui/login/LoginView.java index 05f19fa..a5e0c4e 100644 --- a/src/me/teridax/jcash/gui/login/LoginView.java +++ b/src/me/teridax/jcash/gui/login/LoginView.java @@ -5,6 +5,8 @@ import javax.swing.text.NumberFormatter; import java.awt.*; import java.text.NumberFormat; +import static me.teridax.jcash.lang.Translator.translate; + public class LoginView extends JPanel { private JFormattedTextField blz; @@ -23,16 +25,16 @@ public class LoginView extends JPanel { this.setBorder(BorderFactory.createEmptyBorder(8,8,8,8)); this.setLayout(new BorderLayout(16, 16)); this.add(new JScrollPane(content), BorderLayout.CENTER); - this.add(new JLabel("Bankautomat"), BorderLayout.NORTH); + this.add(new JLabel(translate("Cashmachine")), BorderLayout.NORTH); var constraints = new GridBagConstraints(); constraints.gridwidth = 4; constraints.insets = new Insets(12,12,12,12); - addInputRow(constraints, content, blz, 1, "BLZ"); - addInputRow(constraints, content, iban, 2, "IBAN"); - addInputRow(constraints, content, pin, 3, "PIN"); + addInputRow(constraints, content, blz, 1, translate("BLZ")); + addInputRow(constraints, content, iban, 2, translate("IBAN")); + addInputRow(constraints, content, pin, 3, translate("PIN")); constraints.gridy = 4; constraints.anchor = GridBagConstraints.PAGE_END; @@ -46,7 +48,7 @@ public class LoginView extends JPanel { this.blz = new JFormattedTextField(); this.iban = new JFormattedTextField(getNumberFormat()); this.pin = new JPasswordField(); - this.login = new JButton("Login"); + this.login = new JButton(translate("Login")); } private NumberFormatter getNumberFormat() { diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java b/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java index 547710e..bfff13b 100644 --- a/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java +++ b/src/me/teridax/jcash/gui/takeoff/TakeoffDialog.java @@ -5,6 +5,7 @@ import me.teridax.jcash.banking.accounts.Account; import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.showMessageDialog; +import static me.teridax.jcash.lang.Translator.translate; public class TakeoffDialog { @@ -30,7 +31,7 @@ public class TakeoffDialog { view.dispose(); } catch (IllegalArgumentException ex) { Logging.LOGGER.severe("Could not take off money: " + ex.getMessage()); - showMessageDialog(null, "Reason: " + ex.getMessage(), "Could not take off money", ERROR_MESSAGE); + showMessageDialog(null, "Reason: " + ex.getMessage(), translate("Could not take off money"), ERROR_MESSAGE); } } } diff --git a/src/me/teridax/jcash/gui/takeoff/TakeoffView.java b/src/me/teridax/jcash/gui/takeoff/TakeoffView.java index 33a9c20..8fa6f5e 100644 --- a/src/me/teridax/jcash/gui/takeoff/TakeoffView.java +++ b/src/me/teridax/jcash/gui/takeoff/TakeoffView.java @@ -9,6 +9,7 @@ import java.text.ParseException; import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.showMessageDialog; +import static me.teridax.jcash.lang.Translator.translate; public class TakeoffView { @@ -24,7 +25,7 @@ public class TakeoffView { public void showDialog() { dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); - dialog.setTitle("Takeoff money"); + dialog.setTitle(translate("Takeoff money")); dialog.pack(); dialog.setResizable(false); dialog.setLocationRelativeTo(null); @@ -43,7 +44,7 @@ public class TakeoffView { c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.CENTER; c.insets = new Insets(4, 4, 4, 4); - dialog.getContentPane().add(new JLabel("Takeoff money"), c); + dialog.getContentPane().add(new JLabel(translate("Takeoff money")), c); c.gridx = 0; c.gridy = 1; @@ -51,7 +52,7 @@ public class TakeoffView { c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; - dialog.getContentPane().add(new JLabel("Value", SwingConstants.RIGHT), c); + dialog.getContentPane().add(new JLabel(translate("Value"), SwingConstants.RIGHT), c); c.gridx = 1; c.gridy = 1; @@ -83,8 +84,8 @@ public class TakeoffView { private void createComponents() { this.dialog = new JDialog(); - this.cancel = new JButton("Cancel"); - this.takeoff = new JButton("Takeoff"); + this.cancel = new JButton(translate("Cancel")); + this.takeoff = new JButton(translate("Takeoff")); this.value = new JFormattedTextField(StringDecoder.LOCAL_NUMBER_FORMAT); this.dialog.setContentPane(new JPanel(new GridBagLayout())); @@ -92,7 +93,7 @@ public class TakeoffView { public double getAmount() { if (value.getText().isBlank()) { - showMessageDialog(null, "invalid amount", "currency must not be blank", ERROR_MESSAGE); + showMessageDialog(null, translate("Invalid amount"), translate("Currency must not be blank"), ERROR_MESSAGE); return 0; } diff --git a/src/me/teridax/jcash/gui/transfer/TransferDialog.java b/src/me/teridax/jcash/gui/transfer/TransferDialog.java index b774d4e..e916490 100644 --- a/src/me/teridax/jcash/gui/transfer/TransferDialog.java +++ b/src/me/teridax/jcash/gui/transfer/TransferDialog.java @@ -3,9 +3,11 @@ package me.teridax.jcash.gui.transfer; import me.teridax.jcash.Logging; import me.teridax.jcash.banking.accounts.Account; import me.teridax.jcash.banking.management.BankingManagementSystem; +import me.teridax.jcash.lang.Translator; import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.showMessageDialog; +import static me.teridax.jcash.lang.Translator.translate; public class TransferDialog { @@ -35,7 +37,7 @@ public class TransferDialog { transferView.dispose(); } catch (IllegalArgumentException ex) { Logging.LOGGER.severe("Could not transfer: " + ex.getMessage()); - showMessageDialog(null, "invalid account", "Could not transfer", ERROR_MESSAGE); + showMessageDialog(null, translate("Invalid account"), translate("Could not transfer"), ERROR_MESSAGE); } } } diff --git a/src/me/teridax/jcash/gui/transfer/TransferView.java b/src/me/teridax/jcash/gui/transfer/TransferView.java index 484cf1c..82e9cc9 100644 --- a/src/me/teridax/jcash/gui/transfer/TransferView.java +++ b/src/me/teridax/jcash/gui/transfer/TransferView.java @@ -8,6 +8,7 @@ import java.awt.*; import static javax.swing.JOptionPane.ERROR_MESSAGE; import static javax.swing.JOptionPane.showMessageDialog; +import static me.teridax.jcash.lang.Translator.translate; public class TransferView { @@ -25,7 +26,7 @@ public class TransferView { public void showDialog() { dialog.setModalityType(Dialog.ModalityType.APPLICATION_MODAL); - dialog.setTitle("Transfer money"); + dialog.setTitle(translate("Transfer money")); dialog.pack(); dialog.setSize(dialog.getWidth() * 2, dialog.getHeight()); dialog.setResizable(false); @@ -45,7 +46,7 @@ public class TransferView { c.fill = GridBagConstraints.HORIZONTAL; c.anchor = GridBagConstraints.CENTER; c.insets = new Insets(4, 4, 4, 4); - dialog.getContentPane().add(new JLabel("Transfer money"), c); + dialog.getContentPane().add(new JLabel(translate("Transfer money")), c); c.gridx = 0; c.gridy = 1; @@ -53,7 +54,7 @@ public class TransferView { c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; - dialog.getContentPane().add(new JLabel("BLZ", SwingConstants.RIGHT), c); + dialog.getContentPane().add(new JLabel(translate("BLZ"), SwingConstants.RIGHT), c); c.gridx = 1; c.gridy = 1; @@ -67,7 +68,7 @@ public class TransferView { c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; - dialog.getContentPane().add(new JLabel("IBAN", SwingConstants.RIGHT), c); + dialog.getContentPane().add(new JLabel(translate("IBAN"), SwingConstants.RIGHT), c); c.gridx = 3; c.gridy = 1; @@ -81,7 +82,7 @@ public class TransferView { c.fill = GridBagConstraints.NONE; c.anchor = GridBagConstraints.LAST_LINE_END; c.weightx = 0; - dialog.getContentPane().add(new JLabel("Betrag", SwingConstants.RIGHT), c); + dialog.getContentPane().add(new JLabel(translate("Betrag"), SwingConstants.RIGHT), c); c.gridx = 1; c.gridy = 2; @@ -106,8 +107,8 @@ public class TransferView { private void createComponents() { this.dialog = new JDialog(); - this.cancel = new JButton("Cancel"); - this.transfer = new JButton("Transfer"); + this.cancel = new JButton(translate("Cancel")); + this.transfer = new JButton(translate("Transfer")); this.value = new JFormattedTextField(StringDecoder.LOCAL_NUMBER_FORMAT); this.iban = new JFormattedTextField(); this.blz = new JFormattedTextField(); @@ -118,7 +119,7 @@ public class TransferView { public double getAmount() { if (value.getText().isBlank()) { Logging.LOGGER.severe("Amount is empty"); - showMessageDialog(null, "invalid amount", "currency must not be blank", ERROR_MESSAGE); + showMessageDialog(null, translate("invalid amount"), translate("currency must not be blank"), ERROR_MESSAGE); return 0; } diff --git a/src/me/teridax/jcash/lang/Locales.java b/src/me/teridax/jcash/lang/Locales.java new file mode 100644 index 0000000..7739f6e --- /dev/null +++ b/src/me/teridax/jcash/lang/Locales.java @@ -0,0 +1,56 @@ +package me.teridax.jcash.lang; + +import java.util.Locale; + +/** + * Class for storing static information about the locale used by the application instance at runtime. + */ +@SuppressWarnings("unused") +public class Locales { + + /** + * Locale string used to identify a certain locale + */ + private static String defaultLocale = "en_EN"; + + /** + * Sets the default locale to use for the application instance. + * This will instruct the translator to use the default locale as well as + * Java swing components. + * @param language the locale to use for language + * @param country the locale to use for country + */ + public static void setDefaultLocale(String language, String country) { + var locale = language + "_" + country; + + if (Translator.setTranslationLocale(locale)) { + // apply locale to JVM + Locale.setDefault(new Locale(language, country)); + defaultLocale = locale; + } + } + + public static String getDefaultLocale() { + return defaultLocale; + } + + /** + * Tries to automatically detect the default locale. + * This will prefer the users locale over the systems locale. + * If both fail, the JVMs default locale will be used. + */ + public static void autodetectDefaultLocale() { + var country = System.getProperty("user.country"); + var language = System.getProperty("user.language"); + + var jvmLocale = Locale.getDefault(); + + if (null == country) + country = jvmLocale.getCountry(); + + if (null == language) + language = jvmLocale.getLanguage(); + + setDefaultLocale(language, country); + } +} diff --git a/src/me/teridax/jcash/lang/Translator.java b/src/me/teridax/jcash/lang/Translator.java new file mode 100644 index 0000000..61b2a4f --- /dev/null +++ b/src/me/teridax/jcash/lang/Translator.java @@ -0,0 +1,122 @@ +package me.teridax.jcash.lang; + +import me.teridax.jcash.Logging; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Collectors; + +public final class Translator { + + /** + * Precomputed index of the locale used to statically translate a phrase + */ + private static int localeTranslationIndex = 0; + + /** + * List of all supported locales in the format: language_country. Examples: en_EN, fr_FR + * The index inside the list is directly related to the index of the translation array inside the list translations. + */ + private static final List languages = new ArrayList<>(); + /** + * Mapping of a default english phrase of the code en_EN which is associated with a list of possible translations. + * Index 0 of the translation is equivalent to the key itself since locale 0 is always en_EN. + */ + private static final Map translations = new HashMap<>(); + + static { + // read language file and parse + try (var stream = Objects.requireNonNull(Translator.class.getResourceAsStream("languages.csv"))) { + var text = new String(stream.readAllBytes(), StandardCharsets.UTF_8); + + // parse each line + text.lines().forEach(line -> { + // read header i.e. the locales identifier + if (languages.isEmpty()) { + // split string into columns by comma and trim each column + languages.addAll(Arrays.stream(line.split(",")).map(String::trim).collect(Collectors.toList())); + + // check if default locale is present + if (!languages.contains("en_EN")) { + Logging.LOGGER.severe("Missing default en_EN locale"); + throw new IllegalArgumentException("Missing en_EN locale"); + } + + Logging.LOGGER.info("Read locales: " + Arrays.deepToString(languages.toArray())); + return; + } + + var translation = Arrays.stream(line.split(",")).map(String::trim).toArray(String[]::new); + + // check if all translations are present + // it may happen at a locale does not provide a translation + if (translation.length != languages.size()) + Logging.LOGGER.warning("invalid translations: " + translation.length + " " + languages.size()); + + translations.put(translation[0], translation); + }); + + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Translates the given phrase into the corresponding phrase of the selected locale of the translator. + * If no translation is found or no locale is defined for the translator this function will return the given phrase. + * @param phrase the text to translate + * @return the translated phrase, or the phrase itself in case no translation can be found + */ + public static String translate(String phrase) { + try { + return translations.get(phrase)[localeTranslationIndex]; + + } catch (ArrayIndexOutOfBoundsException e) { + Logging.LOGGER.severe("Locale does not exist with index: " + localeTranslationIndex); + } catch (NullPointerException e) { + Logging.LOGGER.severe("No translation found for phrase: " + phrase); + } + return phrase; + } + + /** + * Returns an array of all available locales for this translator + * @return an array of all available locales for this translator + */ + public static String[] availableLocales() { + return languages.toArray(String[]::new); + } + + /** + * Map the given locale string to an index indicating which array location to choose when fetching a result from + * the translation map. + * @param locale the locale string + * @return a matching index of the locale + * @throws IllegalArgumentException if the given locale is not part of the available locales + */ + private static int mapLocaleToIndex(String locale) throws IllegalArgumentException { + for (int i = 0; i < languages.size(); i++) { + if (languages.get(i).equals(locale)) { + return i; + } + } + + throw new IllegalArgumentException("Locale does not exist: " + locale); + } + + /** + * Sets the default locale to use when translating. + * The locale must have the format language_COUNTRY like en_EN + * @param locale the locale to use when translating + * @return if the specified locale can be used by the translator + */ + public static boolean setTranslationLocale(String locale) { + try { + localeTranslationIndex = Translator.mapLocaleToIndex(locale); + return true; + } catch (IllegalArgumentException ex) { + Logging.LOGGER.severe("unable to set locale for translation: " + locale + " because: " + ex.getMessage()); + return false; + } + } +} diff --git a/src/me/teridax/jcash/lang/languages.csv b/src/me/teridax/jcash/lang/languages.csv new file mode 100644 index 0000000..84ce604 --- /dev/null +++ b/src/me/teridax/jcash/lang/languages.csv @@ -0,0 +1,48 @@ +en_EN,de_DE,es_ES,fr_FR,zh_Hans +Bank,Bank,Banco,Banque,银行 +BLZ,BLZ,BLZ,CODE BANCAIRE,分类代码 +PIN,PIN,PIN,CODE PIN,密码 +Balance,Kontostand,Saldo,Solde du compte,账户余额 +Account type,Kontoart,Tipo de cuenta,Type de compte,账户类型 +Interest,Zins,Interés,Intérêt,利息 +Overdraft,Überziehungsbetrag,Importe del descubierto,Montant du découvert,透支金额 +Customer number,Kundennummer,Número de cliente,Numéro de client,客户编号 +Name,Name,Nombre,Nom,客户姓名 +Name,Vorname,Nombre,Prénom,姓名 +Street,Straße,Calle,Rue,街道 +PLZ,PLZ,PLZ,NPA,邮政编码 +City,Ort,Ubicación,Ville,城市 +Password,Passwort,contraseña,Mot de passe,密码 +Login,Anmelden,Inicio de sesión,S'inscrire,登录 +Current account,Girokonto,Cuenta corriente,Compte courant,活期账户 +Savings account,Sparkonto,Cuenta de ahorro,Compte d'épargne,储蓄账户 +Address,Adresse,Dirección,Adresse,地址 +Logout,Abmelden,desconectarse,Se désinscrire,退出登录 +Transfer,Überweisen,transferencia,Virement bancaire,转账 +Deposit,Einzahlen,depósito,Dépôt,存款 +Take off,Abheben,despegar,Retrait,取款 +Value,Betrag,Importe,Montant,金额 +Cancel,Abbrechen,Cancelar,Annuler,取消 +Load database,Datenbank auswählen,Seleccionar base de datos,Sélectionner la base de données,选择数据库 +Invalid account,Ungültiges Benutzerkonto,Cuenta de usuario no válida,Compte utilisateur non valide,用户账户无效 +Could not transfer,Überweiung fehlgeschlagen,Transferencia fallida,Échec du transfert,转账失败 +Transfer,Überweisen,Transferencia,Transfert,转帐 +invalid amount,Ungültiger Betrag,Importe no válido,Montant non valide,金额无效 +currency must not be blank,Betrag darf nicht leer sein,El importe no debe estar vacío,Le montant ne doit pas être vide,金额不能为空 +Transfer money,Geld überweisen,Transferencia de dinero,Transférer de l'argent,汇款 +Could not take off money,Geld konnte nicht abgehoben werden,No se ha podido retirar dinero,L'argent n'a pas pu être retiré,无法取款 +Takeoff money,Geld abheben,Retirar dinero,Retirer de l'argent,取款 +Takeoff,Abheben,Retirar,Retrait,取款 +Currency must not be blank,Betrag darf nicht leer sein,El importe no debe estar vacío,Le montant ne doit pas être vide,金额不能为空 +Cashmachine,Bankautomat,CAJERO,Distributeur automatique de billets,ATM +Invalid IBAN,Ungültige IBAN,IBAN no válido,IBAN non valide,无效的IBAN +Faulty login attempt,Ungültiger Authentifizierungsverzuch,Solicitud de autenticación no válida,Demande d'authentification non valide,验证请求无效 +Invalid PIN,Ungültiger PIN,PIN no válido,Code PIN non valide,密码无效 +Invalid login credentials,Ungültiges Passwort oder Nutzername,Contraseña o nombre de usuario no válidos,Mot de passe ou nom d'utilisateur non valide,密码或用户名无效 +Deposit money,Geld einzahlen,Depositar dinero,Dépôt d'argent,存款金额 +Interest rate,Zinsbetrag,Importe de los intereses,Montant des intérêts,利息金额 +Name/Family-name,Vorname/Name,Nombre y apellidos,Prénom/nom,名/姓 +Address,Adresse,Dirección,Adresse,地址 +Account,Konto,Cuenta,Compte,账户 +Closing JCash,JCash wird geschlossen,JCash está cerrado,JCash est fermé,JCash 已关闭 +you're logged out,Du bist abgemeldet,Ha cerrado la sesión,Tu es déconnecté,您已注销