Wer ein Programm entwickelt macht so etwas meist in seiner Heimatsprache. Erst später kommt dann der Wunsch hinzu, die fertige Lösung auch in anderen Sprachen anzubieten, was sich dann aber schwierig gestalten kann. Java im Zusammenspiel mit intelliJ IDEA macht es uns Programmierern aber recht leicht, die grafische Benutzeroberfläche („GUI“) mit verschiedenen Sprachen anzubieten. In der zumeist englischsprachigen Literatur wird Internationalisierung vielfach mit „I18n“ abgekürzt.
Was Euch bei der Betrachtung der Quellcodes nicht erschrecken sollte, ist die Tatsache, dass wir neben der bekannten Klasse „TextPane.java“ nebst „TextPane.form“ nun auch eine „Main.java“ als Startumgebung nutzen, diese ist notwendig da wir nur über diesen Umweg das Menü in das Programm hineinbekommen (Einzelheiten dazu im Artikel „TextPane Menue„, welcher noch erstellt wird.
Schauen wir uns als Erstes die Oberfläche des Programms an:
Nach dem Programmstart ist das Programm in englischer Sprache, nach dem Klick auf German wechselt die Oberfläche auf die deutsche Sprache:
Zusätzlich werden 5 Zeilen oben hinzugefügt, welche Beispiele für eine Lokalisierung darstellen (später mehr dazu).
Nun kommt die spannende Frage nach der programmtechnischen Umsetzung und die Antwort darauf lautet mittels „Ressource Bundle“. Die verschiedenen Sprachen sind in eigenen Dateien abgelegt, welche mit einem speziellen Editor sehr einfach erstellt und abgeändert werden können. Beim Programmstart und späterem Wechsel der Sprache über das Menü wird die gewünschte Sprachdatei geladen und jedes Menüelement erhält eine neue Beschriftung.
Fangen wir also mit der ersten Sprache (englisch) an: Wir erzeugen im Projektexplorer ein neues Unterverzeichnis und nennen es „resources“. Dann klicken wir mit der rechten Maustaste auf das Verzeichnis, wählen „New“ und im nächsten Menü ganz unten „Resource Bundle“:
Wir vergeben den Namen „Bundle“, gefolgt von „_en_US“ für die gewünschte englische Sprache und machen mit einem Klick auf „OK“ weiter:
Jetzt erzeugen wir die Datei für die deutsche Sprache: „Bundle_de_DE“ und finden beide Dateien mit der Dateiendung „.properties“ im Projektexplorer:
Die Kombinationen „en_US“ oder „de_DE“ sind nicht zufällig, sondern Ihr solltet Euch an die Tabelle zur Java-Dokumentation halten: https://www.oracle.com/technetwork/java/javase/java8locales-2095355.html. Nun nutzen wir den Resource Bundle-Editor, den wir mit der F4-Taste starten, nachdem wir den Eintrag „Resource Bundle ‚Bundle'“ markiert haben:
Da wir noch keine Ressourcen angelegt haben, ist der Editor aber nun noch komplett leer. Mit einem Klick auf das Plus-Zeichen in der Mitte (rot markiert) können wir unsere Einträge anlegen. Wir starten mit dem Namen unseres Programms, welcher später in der Titelzeile erscheinen soll – unser Eintrag soll „application.title“ lauten:
Nach dem Klick auf „OK“ könnt Ihr nun die gewünschten Titel in deutsch und englisch in die Felder eingeben:
So vergebt Ihr nun für jedes Element Eurer GUI ein „Sprachenpaar“. Ich habe mich dafür entschieden, schon bei der Benennung darauf zu achten, für welches Element später dieser Eintrag bestimmt ist (Beispiele sind „menuItem“ oder „mainButtonLoad“):
Nun noch ein ganz wichtiger Klick – sonst erhaltet Ihr später böse Fehlermeldungen. Ihr klickt im Projektexplorer mit der rechten Maustaste auf den Eintrag „resources“ und wählt aus dem Kontextmenü „Mark Directory as“ und dann „Resources Root“ aus. Damit wird intelliJ bei der Übersetzung des Programms und späteren Erzeugung einer JAR-Datei diese beiden Dateien direkt mit dem Programm verbinden:
Nun fehlt noch die Einbindung des „Resource Bundle“ in das Programm (hier in der Klasse „Main.java“). Da ich alle GUI-Elemente bei der Programmierung in englischer Sprache beschriftet habe, werden alle Elemente auch so angezeigt. Erst ein Klick auf den Menüeintrag „Language“ / „German“ ruft das deutschsprachige Bundle auf den Plan: er lädt alle Benennungen und gibt den GUI-Elementen den neuen Text. Bei den Elementen, welche sich in der Klasse „TextPane.java“ befinden benutze ich in der Klasse jeweils einen „Setter“, welchen ich von der „Main.java“ aufrufe:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
menuItemDe.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Locale locale = Locale.GERMANY; resourceBundle = ResourceBundle.getBundle("Bundle", locale); frame.setTitle(resourceBundle.getString("application.title")); form.setButtonLoad(resourceBundle.getString("mainButtonLoad")); menuItemLoad.setText(resourceBundle.getString("mainButtonLoad")); form.setButtonSave(resourceBundle.getString("mainButtonSave")); menuItemSave.setText(resourceBundle.getString("mainButtonSave")); form.setButtonBackgroundColorChooser(resourceBundle.getString("mainButtonBackgroundColorChooser")); menuFile.setText(resourceBundle.getString("menuFile")); menuItemExit.setText(resourceBundle.getString("menuItemExit")); menuLanguage.setText(resourceBundle.getString("menuLanguage")); menuItemEn.setText(resourceBundle.getString("menuItemEnglish")); menuItemDe.setText(resourceBundle.getString("menuItemGerman")); String line = resourceBundle.getString("mainOutputData") + "\n"; try { line = line + createLocaleLine(locale); form.textPaneNewLine(line); } catch (BadLocationException ex) { ex.printStackTrace(); } } }); |
Sollte ich dabei ein GUI-Element vergessen passiert exakt nichts – es verbleibt in der englischen Sprache. Selbstverständlich gibt es für jeden „Sprachen-Eintrag“ im Menü einen eigenen Action Listener.
Die Ressourcen-Dateien sind im übrigen einfache Textdateien; bei Bedarf könnt Ihr aber auch das XML-Format als Basis der Sprachdateien nutzen („Use XML-based properties files“ bei der Erzeugung des Resource Bundles).
Neben der Internationalisierung ist Lokalisierung ein wichtiger Programmpunkt, denn die Schreibweise von Datumsangaben, Währungen oder Zahlen differiert je nach Land. In der Methode „createLocaleLine“ (Klasse Main.java) findet Ihr dazu ein paar Beispiele und deren programmtechnische Umsetzung. Die Lokalisierung wird in der Literatur meistens mit „L18n“ bezeichnet.
Ein letzter Punkt betrifft die Sprachanzeige innerhalb von fertigen Dialogen, hier gezeigt am „Color Chooser“. Beim Start des Programms gebe ich mit der Programmzeile
1 |
Locale.setDefault(new Locale("es")); |
bewusst die spanische Sprache vor und diese wird im Color Chooser dann auch benutzt:
Leider gibt es derzeit keine Möglichkeit, die Sprache während eines laufenden Programms abzuändern. Die einzige Option hierfür ist die Speicherung der neu gewählten Sprache (z.B. in einer Ini-Datei) und ein Neustart des Programms mit Änderung der Locale-Vorgabe z.B. auf deutsch:
1 |
Locale.setDefault(new Locale("de")); |
Hier zeige ich Euch alle Sourcecodes und die beiden Sprachdateien, wie immer benötigt Ihr intelliJ zum Nachbau:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 |
/* * Herkunft/Origin: http://java-crypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: frei verwendbares Programm (Public Domain) * Copyright: This is free and unencumbered software released into the public domain. * Lizenttext/Licence: <http://unlicense.org> * getestet mit/tested with: Java Runtime Environment 8 Update 191 x64 * getestet mit/tested with: Java Runtime Environment 11.0.4 x64 * Datum/Date (dd.mm.jjjj): 11.02.2020 * Funktion: TextPane Internationalisierung * Function: TextPane Internationalisation * * Sicherheitshinweis/Security notice * Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, * insbesondere mit Blick auf die Sicherheit ! * Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird. * The program routines just show the function but please be aware of the security part - * check yourself before using in the real world ! */ import javax.swing.*; import javax.swing.text.BadLocationException; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.text.DateFormat; import java.text.NumberFormat; import java.util.Currency; import java.util.Date; import java.util.Locale; import java.util.ResourceBundle; public class Main { public static ResourceBundle resourceBundle; public static void main(String[] args) { // als vorgabe: spanisch // as default spanic Locale.setDefault(new Locale("es")); JMenuBar menuBar; JMenu menuFile, menuLanguage; JMenuItem menuItemExit, menuItemLoad, menuItemSave, menuItemEn, menuItemDe; menuBar = new JMenuBar(); menuFile = new JMenu("File"); menuBar.add(menuFile); menuItemSave = new JMenuItem("Save"); menuFile.add(menuItemSave); menuItemLoad = new JMenuItem("Load"); menuFile.add(menuItemLoad); menuItemExit = new JMenuItem("Exit"); menuFile.add(menuItemExit); menuLanguage = new JMenu("Language"); menuBar.add(menuLanguage); menuItemEn = new JMenuItem("English"); menuLanguage.add(menuItemEn); menuItemDe = new JMenuItem("German"); menuLanguage.add(menuItemDe); TextPane form = new TextPane(); JFrame frame = new JFrame("TextPane Internationalization and Localisation"); frame.setContentPane(form.getPanel()); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setJMenuBar(menuBar); frame.pack(); frame.setSize(500, 350); frame.setVisible(true); menuItemEn.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Locale locale = Locale.US; // vorgabe //Locale.setDefault(new Locale("es")); Locale.setDefault(new Locale("en")); UIManager.getDefaults().setDefaultLocale(locale); resourceBundle = ResourceBundle.getBundle("Bundle", locale); frame.setTitle(resourceBundle.getString("application.title")); form.setButtonLoad(resourceBundle.getString("mainButtonLoad")); menuItemLoad.setText(resourceBundle.getString("mainButtonLoad")); form.setButtonSave(resourceBundle.getString("mainButtonSave")); menuItemSave.setText(resourceBundle.getString("mainButtonSave")); form.setButtonBackgroundColorChooser(resourceBundle.getString("mainButtonBackgroundColorChooser")); menuFile.setText(resourceBundle.getString("menuFile")); menuItemExit.setText(resourceBundle.getString("menuItemExit")); menuLanguage.setText(resourceBundle.getString("menuLanguage")); menuItemEn.setText(resourceBundle.getString("menuItemEnglish")); menuItemDe.setText(resourceBundle.getString("menuItemGerman")); String line = resourceBundle.getString("mainOutputData") + "\n"; try { line = line + createLocaleLine(locale); form.textPaneNewLine(line); } catch (BadLocationException ex) { ex.printStackTrace(); } } }); menuItemDe.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { Locale locale = Locale.GERMANY; resourceBundle = ResourceBundle.getBundle("Bundle", locale); frame.setTitle(resourceBundle.getString("application.title")); form.setButtonLoad(resourceBundle.getString("mainButtonLoad")); menuItemLoad.setText(resourceBundle.getString("mainButtonLoad")); form.setButtonSave(resourceBundle.getString("mainButtonSave")); menuItemSave.setText(resourceBundle.getString("mainButtonSave")); form.setButtonBackgroundColorChooser(resourceBundle.getString("mainButtonBackgroundColorChooser")); menuFile.setText(resourceBundle.getString("menuFile")); menuItemExit.setText(resourceBundle.getString("menuItemExit")); menuLanguage.setText(resourceBundle.getString("menuLanguage")); menuItemEn.setText(resourceBundle.getString("menuItemEnglish")); menuItemDe.setText(resourceBundle.getString("menuItemGerman")); String line = resourceBundle.getString("mainOutputData") + "\n"; try { line = line + createLocaleLine(locale); form.textPaneNewLine(line); } catch (BadLocationException ex) { ex.printStackTrace(); } } }); menuItemExit.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { System.exit(0); } }); menuItemLoad.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent e) { form.loadRtf("textpane_rtf.rtf"); } }); } public static String createLocaleLine(Locale locale) { String line = ""; // date DateFormat dateFormat = DateFormat.getDateInstance( DateFormat.DEFAULT, locale); String date = dateFormat.format(new Date()); line = line + date + "\n"; // time DateFormat timeFormat = DateFormat.getTimeInstance( DateFormat.DEFAULT, locale); String time = timeFormat.format(new Date()); line = line + time + "\n"; // currency Double currency = 52560010d; Currency currentCurrency = Currency.getInstance(locale); NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(locale); line = line + currentCurrency.getDisplayName() + ": " + currencyFormatter.format(currency) + "\n"; // numbers Double num = 525949.2; NumberFormat numberFormatter; String numOut; numberFormatter = NumberFormat.getNumberInstance(locale); numOut = numberFormatter.format(num); line = line + numOut + " " + "\n"; return line; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 |
/* * Herkunft/Origin: http://java-crypto.bplaced.net/ * Programmierer/Programmer: Michael Fehr * Copyright/Copyright: frei verwendbares Programm (Public Domain) * Copyright: This is free and unencumbered software released into the public domain. * Lizenttext/Licence: <http://unlicense.org> * getestet mit/tested with: Java Runtime Environment 8 Update 191 x64 * getestet mit/tested with: Java Runtime Environment 11.0.4 x64 * Datum/Date (dd.mm.jjjj): 11.02.2020 * Funktion: TextPane Internationalisierung * Function: TextPane Internationalisation * * Sicherheitshinweis/Security notice * Die Programmroutinen dienen nur der Darstellung und haben keinen Anspruch auf eine korrekte Funktion, * insbesondere mit Blick auf die Sicherheit ! * Prüfen Sie die Sicherheit bevor das Programm in der echten Welt eingesetzt wird. * The program routines just show the function but please be aware of the security part - * check yourself before using in the real world ! */ import javax.swing.*; import javax.swing.text.BadLocationException; import javax.swing.text.DefaultStyledDocument; import javax.swing.text.StyleContext; import javax.swing.text.StyledDocument; import javax.swing.text.rtf.RTFEditorKit; import java.awt.*; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.*; public class TextPane { private JPanel panelMain; private JPanel panelButtons; private JButton buttonLoad; private JScrollPane scrollPane; private JTextPane textPane; private JButton buttonSave; private JButton buttonColorChooser; public TextPane() { textPane.setContentType("text/rtf"); textPane.setEditorKit(new RTFEditorKit()); buttonLoad.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { loadRtf("textpane_rtf.rtf"); } }); buttonSave.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { saveRtf("textpane_rtf.rtf"); } }); buttonColorChooser.addActionListener(new ActionListener() { @Override public void actionPerformed(ActionEvent actionEvent) { JColorChooser jcc = new JColorChooser(); Color color = jcc.showDialog(null, "Farbauswahl", null); textPane.setBackground(color); textPane.requestFocus(); } }); } public JPanel getPanel() { return panelMain; } public void setButtonLoad(String text) { buttonLoad.setText(text); } public void setButtonSave(String text) { buttonSave.setText(text); } public void setButtonBackgroundColorChooser(String text) { buttonColorChooser.setText(text); } public void textPaneNewLine(String text) throws BadLocationException { textPane.getDocument().insertString(0, text + "\n", null); } public void loadRtf(String filename) { RTFEditorKit RTF_KIT = new RTFEditorKit(); textPane.setContentType("text/rtf"); InputStream inputStream; try { inputStream = new FileInputStream(filename); final DefaultStyledDocument styledDocument = new DefaultStyledDocument(new StyleContext()); RTF_KIT.read(inputStream, styledDocument, 0); textPane.setDocument(styledDocument); // delete added last line String content = textPane.getDocument().getText(0, textPane.getDocument().getLength()); int lastLineBreak = content.lastIndexOf('\n'); textPane.getDocument().remove(lastLineBreak, textPane.getDocument().getLength() - lastLineBreak); } catch (IOException | BadLocationException e) { e.printStackTrace(); } textPane.requestFocus(); } public void saveRtf(String filename) { try { FileOutputStream fos = new FileOutputStream(filename); RTFEditorKit kit = (RTFEditorKit) textPane.getEditorKit(); StyledDocument doc = textPane.getStyledDocument(); int len = doc.getLength(); kit.write(fos, doc, 0, len); fos.close(); } catch (IOException | BadLocationException e) { e.printStackTrace(); } textPane.requestFocus(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
<?xml version="1.0" encoding="UTF-8" ?> - <form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="TextPane"> - <grid id="27dc6" binding="panelMain" layout-manager="GridLayoutManager" row-count="2" column-count="1" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="10" left="10" bottom="10" right="10" /> - <constraints> <xy x="20" y="20" width="500" height="400" /> </constraints> <properties /> <border type="none" /> - <children> - <grid id="69623" binding="panelButtons" layout-manager="GridLayoutManager" row-count="1" column-count="3" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1"> <margin top="0" left="0" bottom="0" right="0" /> - <constraints> <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="3" hsize-policy="3" anchor="0" fill="3" indent="0" use-parent-layout="false" /> </constraints> <properties /> <border type="none" /> - <children> - <component id="b1559" class="javax.swing.JButton" binding="buttonLoad"> - <constraints> <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false" /> </constraints> - <properties> <text value="load" /> </properties> </component> - <component id="1e556" class="javax.swing.JButton" binding="buttonSave"> - <constraints> <grid row="0" column="2" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false" /> </constraints> - <properties> <text value="save" /> </properties> </component> - <component id="598e3" class="javax.swing.JButton" binding="buttonColorChooser"> - <constraints> <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="3" anchor="0" fill="1" indent="0" use-parent-layout="false" /> </constraints> - <properties> <text value="background color chooser" /> </properties> </component> </children> </grid> - <scrollpane id="5bf0e" binding="scrollPane"> - <constraints> <grid row="1" column="0" row-span="1" col-span="1" vsize-policy="7" hsize-policy="7" anchor="0" fill="3" indent="0" use-parent-layout="false" /> </constraints> <properties /> <border type="none" /> - <children> - <component id="100bc" class="javax.swing.JTextPane" binding="textPane"> <constraints /> <properties /> </component> </children> </scrollpane> </children> </grid> </form> |
1 2 3 4 5 6 7 8 9 10 11 12 |
application.title=TextPane Internationalization and Localisation\ mainButtonLoad=load mainButtonSave=save language=Language menuFile=File menuItemEnglish=English menuItemExit=Exit menuItemGerman=German menuLanguage=Language mainOutputData=Next lines are the date, time, currency and numbers for en-US mainButtonBackgroundColorChooser=Change Background color |
1 2 3 4 5 6 7 8 9 10 11 |
application.title=TextPane Internationalisierung und Lokalisierung mainButtonLoad=laden mainButtonSave=speichern language=Sprache menuFile=Datei menuItemEnglish=Englisch menuItemExit=Ende menuItemGerman=Deutsch menuLanguage=Sprache mainOutputData=Es folgen das Datum, die Zeit, Währung und Nummern für de-DE mainButtonBackgroundColorChooser=Hintergrundfarbe ändern |
Alle Quellcodes zur JTextPane findet Ihr zum Download in meinem Github-Repository, welches Ihr über diesen Link erreicht: https://github.com/java-crypto/JTextPane. Alle Programme sind unter Java 11 lauffähig (vermutlich auch unter Java 8) und wurden mit intelliJ IDEA entwickelt, welches für das eigene „Spielen“ notwendig ist.
Die Lizenz zum obigen Beispiel findet Ihr auf der eigenen Lizenz-Seite.
Letzte Bearbeitung: 12.02.2020