# Frame "on-the-fly" auf andere Sprache umstellen



## scooterJava (28. Mai 2008)

Ich versuche seit Tagen, Java davon zu überzeugen, auf Knopfdruck die Sprache des Frames umzustellen.

Dazu habe ich folgendes Testprogramm:


```
package test;


import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;


public class Test
	extends JFrame
	implements ActionListener
{
	private static final long serialVersionUID = 1L;
	private static ResourceBundle messages;
	private static String language = "en";
	private static String country = "US";
	private static JPanel panel = new JPanel();

	private static Locale currentLocale = new Locale( language, country );

	static public void main( String[] args )
	{
		Test window = new Test();
		window.setLocationRelativeTo( null );
		window.setDefaultCloseOperation( EXIT_ON_CLOSE );
		window.pack();
		window.setVisible( true );
	}

	public Test()
	{
		messages = ResourceBundle.getBundle( "Test.Test", currentLocale );
		panel.setLayout( new FlowLayout( FlowLayout.RIGHT ) );
		JPanel panelForm = new JPanel();
		JButton btnDE = new JButton( messages.getString( "Deutsch" ) );
		JButton btnEN = new JButton( messages.getString( "Englisch" ) );
		btnDE.addActionListener( this );
		btnEN.addActionListener( this );
		panelForm.add( btnDE );
		panelForm.add( btnEN );
		panel.add( panelForm );
		add( panel );
		System.out.println( messages.getString( "Deutsch" ) );
		System.out.println( messages.getString( "Englisch" ) );
	}

	public void actionPerformed( ActionEvent ae )
	{
		String action = ae.getActionCommand();
		String[] actions = { messages.getString( "Deutsch" ),
			messages.getString( "Englisch" ) };
		LinkedList<String> actionslist = new LinkedList<String>( Arrays
			.asList( actions ) );
		int langIndex = 1 + actionslist.indexOf( action );

		switch( langIndex )
		{
			case 1: // Deutsch
				switchLanguage( "de", "DE" );
				break;
			case 2: // Englisch
				switchLanguage( "en", "US" );
				break;
		}

	}

	private void switchLanguage( String language, String country )
	{
		panel.setLocale( new Locale( language, country ) );
		System.out.println( panel.getLocale() );
		messages = ResourceBundle.getBundle( "Test.Test", currentLocale );
		panel.updateUI();
	}
}
```

Die Properties-Dateien liegen korrekt vor; syntaktisch ist auch alles in Ordnung. Tatsächlich setzt Java die neue Locale bei "... getLocale()" am Codeende. "getBundle()" wird dann offenbar auch ausgeführt, zeigt aber keine Wirkung. Aus Verweiflung habe ich noch ein "updateUI()" angefügt; aber auch "repaint()" zeigt keine Wirkung.

Ich kann mir kaum vorstellen, dass eine so weit verbreitete Entwicklerplattform wie Java keine vernünftige kurze Sprachumstellung bietet. Im Netz finde ich nur Aussagen wie "So müsste es gehen" bis "Geht nicht, wenn du Java nicht neu startest oder jeder Komponente selber einen neuen Text zuweist". Komme ich wirklich bei einer Anwendung wie Java nicht umhin, etwas zu schreiben wie:


```
void switchLanguage( ...)
{
	// ...
	btnDE.setText( messages.getString("Deutsch"));
	// usw.
}

Das haben ja andere Entwicklungsumgebungen wie Trolltechs Qt viel besser gelöst!
```


----------



## SlaterB (28. Mai 2008)

tja, sei mal froh, dass es überhaupt Swing gibt und du nicht noch viel mehr zu meckern hast 

setText() einzeln aufzurufen wäre natürlich keine Lösung, stattdessen müsste das Framework um diese Funktionalität intelligent erweitert werden,
evtl.  sowas wie hier 
http://www.joconner.com/javai18n/articles/Multilingual.html


kann nicht genau sagen, was Swing oder andere Libraries (SWT?) da schon bieten


----------



## scooterJava (28. Mai 2008)

Vielen Dank! Dies scheint genau das zu sein, was ich solange gesucht habe. Werds umgehend ausprobieren.


----------



## scooterJava (12. Jun 2008)

Für die Leute, die es gerne funktionierend sehen möchten, hat Herr Connor mir etwas unter die Arme gegriffen.

So gehts:


```
package test;


import java.awt.FlowLayout;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;


public class Test
extends JFrame
implements ActionListener
{
	private static final long serialVersionUID = 1L;
	private static ResourceBundle messages;
	private static String language = "en";
	private static String country = "US";
	private static JPanel panel = new JPanel();
	private static Locale currentLocale = new Locale( language, country );

	private final JButton btnDE;
	private final JButton btnEN;

	static public void main( String[] args )
	{
		Test window = new Test();
		window.setLocationRelativeTo( null );
		window.setDefaultCloseOperation( EXIT_ON_CLOSE );
		window.pack();
		window.setVisible( true );
	}

	public Test()
	{
		messages = ResourceBundle.getBundle( "Test.Test", currentLocale );
		panel.setLayout( new FlowLayout( FlowLayout.RIGHT ) );
		JPanel panelForm = new JPanel();
		btnDE = new JButton( messages.getString( "Deutsch" ) );
		btnEN = new JButton( messages.getString( "Englisch" ) );
		btnDE.addActionListener( this );
		btnEN.addActionListener( this );
		panelForm.add( btnDE );
		panelForm.add( btnEN );
		panel.add( panelForm );
		add( panel );
		System.out.println( messages.getString( "Deutsch" ) );
		System.out.println( messages.getString( "Englisch" ) );
	}

	public void actionPerformed( ActionEvent ae )
	{
		String action = ae.getActionCommand();
		String[] actions = { messages.getString( "Deutsch" ),
			messages.getString( "Englisch" ) };
		LinkedList<String> actionslist = new LinkedList<String>( Arrays
		.asList( actions ) );
		int langIndex = 1 + actionslist.indexOf( action );

		switch( langIndex )
		{
			case 1: // Deutsch
				switchLanguage( "de", "DE" );
				break;
			case 2: // Englisch
				switchLanguage( "en", "US" );
				break;
		}

	}

	private void switchLanguage( String language, String country )
	{
		currentLocale = new Locale( language, country );
		messages = ResourceBundle.getBundle( "Test.Test", currentLocale );
		btnDE.setText( messages.getString( "Deutsch" ) );
		btnEN.setText( messages.getString( "Englisch" ) );
		panel.updateUI();
	}

}
```

Der Code ist natürlich noch zu optimieren:
* Die hartkodierten Strings sollten vermieden werden.
* Ein Listener reicht, wenn man die Sprachauswahl mit einer JCOmboBox gestaltet.
* Den verwirrenden Zugriff, den ich in actionPerformed() auf die Labels nehme, sollte man besser duch getSource() ersetzen:


```
Object action = ae.getSource();
if( action == btnDE ) { ... }
if( action == btnEN ) { ... }
```

Ausgaben:













[/img]


----------



## scooterJava (12. Jun 2008)

Für die Leute, die es gerne funktionierend sehen möchten, hat Herr Connor mir etwas unter die Arme gegriffen.

So gehts:


```
package test;


import java.awt.FlowLayout;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;


public class Test
extends JFrame
implements ActionListener
{
	private static final long serialVersionUID = 1L;
	private static ResourceBundle messages;
	private static String language = "en";
	private static String country = "US";
	private static JPanel panel = new JPanel();
	private static Locale currentLocale = new Locale( language, country );

	private final JButton btnDE;
	private final JButton btnEN;

	static public void main( String[] args )
	{
		Test window = new Test();
		window.setLocationRelativeTo( null );
		window.setDefaultCloseOperation( EXIT_ON_CLOSE );
		window.pack();
		window.setVisible( true );
	}

	public Test()
	{
		messages = ResourceBundle.getBundle( "Test.Test", currentLocale );
		panel.setLayout( new FlowLayout( FlowLayout.RIGHT ) );
		JPanel panelForm = new JPanel();
		btnDE = new JButton( messages.getString( "Deutsch" ) );
		btnEN = new JButton( messages.getString( "Englisch" ) );
		btnDE.addActionListener( this );
		btnEN.addActionListener( this );
		panelForm.add( btnDE );
		panelForm.add( btnEN );
		panel.add( panelForm );
		add( panel );
		System.out.println( messages.getString( "Deutsch" ) );
		System.out.println( messages.getString( "Englisch" ) );
	}

	public void actionPerformed( ActionEvent ae )
	{
		String action = ae.getActionCommand();
		String[] actions = { messages.getString( "Deutsch" ),
			messages.getString( "Englisch" ) };
		LinkedList<String> actionslist = new LinkedList<String>( Arrays
		.asList( actions ) );
		int langIndex = 1 + actionslist.indexOf( action );

		switch( langIndex )
		{
			case 1: // Deutsch
				switchLanguage( "de", "DE" );
				break;
			case 2: // Englisch
				switchLanguage( "en", "US" );
				break;
		}

	}

	private void switchLanguage( String language, String country )
	{
		currentLocale = new Locale( language, country );
		messages = ResourceBundle.getBundle( "Test.Test", currentLocale );
		btnDE.setText( messages.getString( "Deutsch" ) );
		btnEN.setText( messages.getString( "Englisch" ) );
		panel.updateUI();
	}

}
```

Der Code ist natürlich noch zu optimieren:
* Die hartkodierten Strings sollten vermieden werden.
* Ein Listener reicht, wenn man die Sprachauswahl mit einer JCOmboBox gestaltet.
* Den verwirrenden Zugriff, den ich in actionPerformed() auf die Labels nehme, sollte man besser duch getSource() ersetzen:


```
Object action = ae.getSource();
if( action == btnDE ) { ... }
if( action == btnEN ) { ... }
```

Ausgaben:


----------



## Wildcard (12. Jun 2008)

Das dieser Code zu nichts zu gebrauchen ist, sollte klar sein.
Das eigentliche Problem ist das hier:      

```
btnDE.setText( messages.getString( "Deutsch" ) );
btnEN.setText( messages.getString( "Englisch" ) );
```
In einer komplexen GUI ist das aus offensichtlichen Gründen kein praktikabler Ansatz.
Die Alternative besteht in einem erstmal nur in einem übermäßig komplexen Notification Konstrukt das sich über die ganze Anwendung erstreckt.
Meine ehrliche Meinung dazu:
viel Lärm um nichts, denn die Sprache stellt man genau einmal ein (üblicherweise automatisch) und da ist ein Neustart zu verkraften.


----------



## scooterJava (13. Jun 2008)

Auf einen bereits geschlossenen Thread noch was zu schreiben, ist schon merkwürdig ...

Zur Praktikablität:
Wie vielleicht aufgefallen ist, ist dies eine Testanwendung. Primär sollte es nur darum gehen, wie ein gestelltes Problem gelöst werden kann. Nach Codeschönheit wurde nicht gefragt und Kritik habe ich zum Schluss ja auch selber angebracht.

Zur Einsatzmöglichkeit:
Immer wieder wird behauptet, dass eine on-the-fly-Sprachumstellung, wie sie hier im Test veranschaulicht wird, unnütz ist -- man könnte die Anwendung nach Umstellung in den Optionen schließlich wieder neu starten. Dazu sind zwei Dinge zu sagen: Zum einen ist es in der heutigen Zeit ein Schlag ins Gesicht der Usability und in den des Anwenders, dass er das Programm neu starten muss und dabei vielleicht bisherige Eingaben verliert, bloß weil er -- vielleicht aus Verständnisgründen -- mal kurz die GUI in einer anderen Sprache lesen will; zum anderen wird vergessen, wie sinnvoll solch eine Möglichkeit bei shared applications sein kann: Wenn online mehrere Anwender ein Programm nutzen, kann es für sie eine Erleichterung sein, dass jeder das Programm in seiner Sprache bedienen kann. Es soll tatsächlich Programmierer geben, die nicht nur eindimensional denken ...


----------



## Wildcard (13. Jun 2008)

Dann warte mal ab wie die Sache in einer echten Applikation aussieht, dann sprechen wir uns nochmal  :wink:


----------



## scooterJava (13. Jun 2008)

Und schon wieder auf einen geschlossenen Thread geantwortet ...

So nett ein Meinungsaustausch auch sein kann, aber sind dir die Forenregeln klar?


----------



## Wildcard (13. Jun 2008)

Der Thread ist nicht geschlossen (das wäre meine Aufgabe, oder die eines anderen Mods), sondern als erledigt markiert.
Sprich: Dein Problem betrachtest du als gelöst, was natürlich nicht ausschließt die gefundene Lösung zu kommentieren.


----------



## Illuvatar (13. Jun 2008)

Ich denke mal, Wildcard als Moderator wird die Forenregeln kennen...

Mal ein Beispiel: Ein Benutzer schreibt in einem Forum, dass er gerne ein iPhone kaufen würde, aber ihm 5 Cent fehlen. Er findet dann selbst die Lösung: Er bringt den Präsidenten der USA um und erleichtert ihn um sein Geld. Der Thread ist abgehakt. Aber gäbe es nicht _vielleicht_ bessere Lösungen auf die man hinweisen sollte?

@topic: Ich kann bei Interesse auch Code von mir posten, ich hab sowas schonmal gemacht. Da verwendet man dann statt dem JComponent das man normal verwenden würde eine Unterklasse (LLabel, LButton, LProgressBar, ...) die sich bei einem Manager als Listener registriert und nur einen resourceString kennt. Wenn die Locale vom Manager geändert wird, werden alle Listener benachrichtigt und holen vom Manager, der das ResourceBundle kennt, die entsprechende neue, zu ihrem resourceString passende Beschriftung.
Das Gute daran: Man kann die neuen Components genau verwenden wie die alten und muss sich überhaupt nicht mehr um die Sprache kümmern. Wenn dann im LanguageChooser eine andere Sprache gewählt wird, wird alles automatisch aktualisiert.


----------



## Wildcard (13. Jun 2008)

Illuvatar hat gesagt.:
			
		

> Da verwendet man dann statt dem JComponent das man normal verwenden würde eine Unterklasse (LLabel, LButton, LProgressBar, ...) die sich bei einem Manager als Listener registriert und nur einen resourceString kennt. Wenn die Locale vom Manager geändert wird, werden alle Listener benachrichtigt und holen vom Manager, der das ResourceBundle kennt, die entsprechende neue, zu ihrem resourceString passende Beschriftung.
> Das Gute daran: Man kann die neuen Components genau verwenden wie die alten und muss sich überhaupt nicht mehr um die Sprache kümmern. Wenn dann im LanguageChooser eine andere Sprache gewählt wird, wird alles automatisch aktualisiert.


Genau sowas ist eine sinnvolle Lösung. Wichtig ist bei soetwas, das man zusätzlich ein Interface anbietet um keine Vererbungshierarchie vorzuschreiben.
Bei zwei Dingen wird's allerdings sehr hässlich:
-SWT, weil man von vielen Widgets nicht erben darf
-alles Selbstgezeichnete


----------



## scooterJava (13. Jun 2008)

@Illuvatar: Dann hätte ich Interesse an deinem Code.


----------



## Illuvatar (13. Jun 2008)

Ist ein bisschen viel ums hier direkt zu posten, ich hab es hier hochgeladen.

Kleiner Beispielcode:
_Test.java_

```
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import javax.swing.*;

import de.illu.i18n.*;

public class Test extends LFrame
{
    private static List<Locale> locales = new ArrayList<Locale>();
    static{
        locales.add(Locale.GERMANY);
        locales.add(Locale.US);
    }
    public Test(String resource, I18NManager man)
    {
        super(resource, man);
        setLayout(new BorderLayout(5, 5));
        final LProgressBar pb = new LProgressBar(man, "progress",
                SwingConstants.HORIZONTAL, 0, 100);
        add(pb, BorderLayout.CENTER);
        JPanel buttons = new JPanel();
        buttons.setLayout(new BoxLayout(buttons, BoxLayout.X_AXIS));
        LanguageChooser chooser = new LanguageChooser("language", man, locales);
        buttons.add(chooser);
        buttons.add(Box.createHorizontalStrut(75));
        LButton b1 = new LButton("next", man);
        buttons.add(b1);
        b1.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e)
            {
                pb.setValue(pb.getValue() + 10);
            }
        });
        buttons.add(Box.createHorizontalStrut(5));
        LButton b2 = new LButton("exit", man);
        b2.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e)
            {
                System.exit(0);
            }
        });
        buttons.add(b2);
        add(buttons, BorderLayout.SOUTH);
        pack();
        setLocationRelativeTo(null);
        setVisible(true);
    }

    public static void main(String... args)
    {
        I18NManager man = new I18NManager("test");
        new Test("title", man);
    }
}
```
_test_de.properties_

```
title=Testtitel
progress=Berechnung der Weltformel...
language=Sprache wählen
next=Weiter
exit=Beenden
```
_test_en.properties_

```
title=Test Title
progress=Doing some stuff
language=Select Language
next=Next
exit=Exit
```


----------



## Guest (13. Okt 2008)

Mal ne kurze Frage, prinzipiell ist hier alles erklärt, aber wie hast du bei deiner Lösung die Sache 
mit den Accelerators gelöst?? Das würde mich wirklich mal interessieren, weil ich hab mittlerweile mir selber so ein I18n-Framework gebastelt das sehr schön funktioniert, allerdings bleiben die Accelerator im Menü (STRG+G -> CTRL+G) weiterhin in der zuerst gestarteten Locale und das gefällt mir nicht... wäre um eine Lösung dankbar.

Gruß

DarkLoG


----------



## scooterJava (14. Okt 2008)

Damals habe ich keine Hotkeys nach den Locales neu belegt. Da es nur ein Testprogramm war, habe ich einfach die Art des Betriebssystems abgefragt (Windows oder nicht) und je nach einer der beiden Möglichkeiten Standard-Hotkeys für die Minimal-Menüs vergeben, also z. B. Alt-F4 unter Windows fürs Beenden (bei Linux dann glaub ich Strg+Alt+Esc).

Wenn ich den Code noch recht in Erinnerung habe, muss die Neubelegung per Hand in der gleichen Funktion erfolgen, welche die neue Locale festlegt. Am besten ein Switch-Konstrukt mit den Cases der gewählten Locale und dann die Acceleratoren neu zuordnen.

Java-Programmierer halten einen solchen on-the-fly-Wechsel oft nicht für nötig, und Java selber legt dem Programmierer hier die eine oder andere Hürde in den Weg. Doch solch ein Wechsel kann bei bestimmten Programmen durchaus sinnvoll sein. Beispielsweise kann in einem Multiplayer-Onlinespiel jeder Spieler die Oberfläche in seiner gewünschten Sprache wählen, unabhängig von der Locale des installieren Betriebssystems. Vielleicht kommen die von Sun ja auch noch auf den Trichter, es hier mal einfacher zu machen ... (manches ist ja schon in Java 6 besser geworden).


----------



## Guest (14. Okt 2008)

Alles klar danke, ich werd mal weiterschauen wie ich die Accelerators auch noch davon überzeuge, dass ich jetzt ne andere Sprache will, zur Not mach ichs per Hand. Den Rest hab ich jetzt eigentlich ganz schön automatisiert. Ich bin Java-Programmierer und halte es für sehr sinnvoll, dass man die Sprache on the fly umschalten kann, insbesondere da ich an einem Programm arbeite, dass Live-Daten ständig anzeigt und diese sollten auch ständig überwacht werden, also neustarten ist in diesem Fall eine sehr schlechte Lösung. Außerdem gibt es viele Länder in denen mehrere Sprachen gesprochen werden (Schweiz, Belgien etc.) und wechselende Benutzer/Beobachter können so ganz einfach in die gewünschte Sprache schalten. 
Nächster Punkt - ein Kunde aus Russland ruft an er hat ein Problem, ich gehe per VNC auf seinen Rechner schaue mir die Sache an und bumm alles russisch, neustarten löst das Problem vielleicht automatisch, daher ist das keine vernünftige Option, also zack die Sprache auf Deutsch gestellt und das Problem analysiert...

Gruß

DarkLoG


----------



## scooterJava (21. Nov 2008)

Der russische Kunde ist natürlich ein gutes Argument gegen diejenigen, die einen Sprachenwechsel on-the-fly unter Java für Quatsch halten. Im Gegensatz zu (schreibfaulen?) Programmierern sehen dies Anwender glücklicherweise immer anders, denn sie sind es ja, die mit den Programmen zu kämpfen haben ...


----------



## Wildcard (21. Nov 2008)

Ist doch lächerlich... schreibfaule Programmierer  :roll: 
Diejenigen von uns, die die Programmierung nicht nur als Hobby betreiben, richten sich nach den Anforderungen an die Software. Ist ein on the fly Language Change nicht enthalten, wird er nicht eingebaut, weil das viel zu teuer ist.
Würdest du die Entwickler bezahlen, würdest du dir ein solches Gimmik auch 4 mal überlegen bevor du das Requirement auflistest.


----------

