added translation capabilities

This commit is contained in:
Sven Vogel 2023-07-16 23:15:26 +02:00
parent ad279a6b15
commit 83f92f1ef3
13 changed files with 288 additions and 46 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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() {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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<String> 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<String, String[]> 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;
}
}
}

View File

@ -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é,您已注销
1 en_EN de_DE es_ES fr_FR zh_Hans
2 Bank Bank Banco Banque 银行
3 BLZ BLZ BLZ CODE BANCAIRE 分类代码
4 PIN PIN PIN CODE PIN 密码
5 Balance Kontostand Saldo Solde du compte 账户余额
6 Account type Kontoart Tipo de cuenta Type de compte 账户类型
7 Interest Zins Interés Intérêt 利息
8 Overdraft Überziehungsbetrag Importe del descubierto Montant du découvert 透支金额
9 Customer number Kundennummer Número de cliente Numéro de client 客户编号
10 Name Name Nombre Nom 客户姓名
11 Name Vorname Nombre Prénom 姓名
12 Street Straße Calle Rue 街道
13 PLZ PLZ PLZ NPA 邮政编码
14 City Ort Ubicación Ville 城市
15 Password Passwort contraseña Mot de passe 密码
16 Login Anmelden Inicio de sesión S'inscrire 登录
17 Current account Girokonto Cuenta corriente Compte courant 活期账户
18 Savings account Sparkonto Cuenta de ahorro Compte d'épargne 储蓄账户
19 Address Adresse Dirección Adresse 地址
20 Logout Abmelden desconectarse Se désinscrire 退出登录
21 Transfer Überweisen transferencia Virement bancaire 转账
22 Deposit Einzahlen depósito Dépôt 存款
23 Take off Abheben despegar Retrait 取款
24 Value Betrag Importe Montant 金额
25 Cancel Abbrechen Cancelar Annuler 取消
26 Load database Datenbank auswählen Seleccionar base de datos Sélectionner la base de données 选择数据库
27 Invalid account Ungültiges Benutzerkonto Cuenta de usuario no válida Compte utilisateur non valide 用户账户无效
28 Could not transfer Überweiung fehlgeschlagen Transferencia fallida Échec du transfert 转账失败
29 Transfer Überweisen Transferencia Transfert 转帐
30 invalid amount Ungültiger Betrag Importe no válido Montant non valide 金额无效
31 currency must not be blank Betrag darf nicht leer sein El importe no debe estar vacío Le montant ne doit pas être vide 金额不能为空
32 Transfer money Geld überweisen Transferencia de dinero Transférer de l'argent 汇款
33 Could not take off money Geld konnte nicht abgehoben werden No se ha podido retirar dinero L'argent n'a pas pu être retiré 无法取款
34 Takeoff money Geld abheben Retirar dinero Retirer de l'argent 取款
35 Takeoff Abheben Retirar Retrait 取款
36 Currency must not be blank Betrag darf nicht leer sein El importe no debe estar vacío Le montant ne doit pas être vide 金额不能为空
37 Cashmachine Bankautomat CAJERO Distributeur automatique de billets ATM
38 Invalid IBAN Ungültige IBAN IBAN no válido IBAN non valide 无效的IBAN
39 Faulty login attempt Ungültiger Authentifizierungsverzuch Solicitud de autenticación no válida Demande d'authentification non valide 验证请求无效
40 Invalid PIN Ungültiger PIN PIN no válido Code PIN non valide 密码无效
41 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 密码或用户名无效
42 Deposit money Geld einzahlen Depositar dinero Dépôt d'argent 存款金额
43 Interest rate Zinsbetrag Importe de los intereses Montant des intérêts 利息金额
44 Name/Family-name Vorname/Name Nombre y apellidos Prénom/nom 名/姓
45 Address Adresse Dirección Adresse 地址
46 Account Konto Cuenta Compte 账户
47 Closing JCash JCash wird geschlossen JCash está cerrado JCash est fermé JCash 已关闭
48 you're logged out Du bist abgemeldet Ha cerrado la sesión Tu es déconnecté 您已注销