Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Swing Internationalization - How to update language at runtime

I've followed Googles howto for its Window Builder Pro extension to internationalize my app. Strings that will be shown in labels are now stored in property-files that were created by the "Externalize Strings" wizard (I've used classic eclipse message files).

Inside my initialize method, all my lables are initialized and their text is set like this:

JLabel lblLanguage = new JLabel(Messages.getString("App.lblLanguage.text")); //$NON-NLS-1$

I have created an enum type inside my class App which holds the GUI:

private enum Lang { German(Locale.GERMAN), English(Locale.ENGLISH);

    private Locale loc;
    Lang (Locale l) {
        loc = l;
    }

    Locale getLocale() {
        return loc;
    }
}

The language will be set with a combobox that uses the enum type Lang to show the languages available:

    JComboBox cboLanguage = new JComboBox();
    cboLanguage.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent e) {
            JComboBox cb = (JComboBox)e.getSource();
            Lang l = (Lang)cb.getSelectedItem();
            // TODO: update language
        }
    });
    cboLanguage.setModel(new DefaultComboBoxModel(Lang.values()));

I've found many other howtos and tutorials covering the internationalization of swing applications but none of them covers how to update all labels (and possible other controls with text in them). There's this answer here on SO that might have been helpful if the links weren't dead.
Because I'm kind of new to GUI programming in java, I don't really know what to do now and here are my questions:

  • What is the best way, to change the language of all controls at runtime if a new language is set?
  • Is it ok (recommended) to declare all controls as private members of my App class to have a method update their text property (-> an updateLanguage method would do that)
like image 771
wullxz Avatar asked Feb 11 '13 10:02

wullxz


4 Answers

When I did this, I couldn't find a built-in way of doing it either.

So I created my own LocaleChangeListener interface. All UI screens/panels implemented that and registered themselves as listeners on my central UI controller class. Then a localeChanged(lang) call was fired to each of them whenever the language changed. I kept the component initialisation separate though - no need to do all of that again.

They just had code like this:

public void localeChanged(Lang lang) {
    nameLabel.setText(lang.getText("label.name"));
    submitButton.setText(lang.getText("button.submit"));
    etc...

    // I think on some screens I needed this to fix some quirky JTable/JTabbedPane layout issues:
    this.revalidate();
}
like image 76
David Lavender Avatar answered Oct 31 '22 12:10

David Lavender


I solve this by extending JLabel and overwriting getText to return the evaluation of the language selection.

You will also need some pub/sub mechanism to 'tell' your labels that the language has changed.

Here i´m using Guava event bus

import com.google.common.eventbus.EventBus;

public class EventBusHolder {

    private static EventBus bus = new EventBus();

    public static EventBus get() {
        return bus;
    }
}

When the user change the language you fire the event:

JComboBox cboLanguage = new JComboBox();
cboLanguage.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        JComboBox cb = (JComboBox)e.getSource();
        Lang l = (Lang)cb.getSelectedItem();
        // this is the event fire
        ChangeEvent event = getChangeEvent(l);
        EventBusHolder.get().post(event);
    }
});

the label component to be used:

public class MyLabel extends JLabel {

    private static final long serialVersionUID = 1L;

    public MyLabel (String key) {
        super(key);
        // register this in event bus
        EventBusHolder.get().register(this);
    }

    @Override
    public String getText() {
        return Messages.getString(super.getText());
    }

    @Subscribe 
    public void recordCustomerChange(ChangeEvent e) {
        revalidate();
    }

}

And when you instanciate the Label you must pass the key for translation:

JLabel lbl = new JLabel("App.lblLanguage.text");

more usage examples on guava event bus

like image 43
Heitor Avatar answered Oct 31 '22 11:10

Heitor


As a user will not often change the language, a restart is just barely acceptable. This has the advantage that layouting artifacts of the old language will not mangle the text.

This needs a saveable state of the application.

A JLabel new JLabel(Messages.getString("App.lblLanguage.text")); immediately evaluates to a localized text, and might layout to that text's width. So that is not suited to dynamic language change.

The other way is to rely on a name attribute or reflection to fill all text values. Or code:

JLabel lblLanguage = new JLabel();
Nls.setText(lblLanguage, "App.lblLanguage");

With some Nls.setText making a weak reference of lblLanguage in a set.

like image 1
Joop Eggen Avatar answered Oct 31 '22 11:10

Joop Eggen


There is nothing in core Swing to ease the pain. You might be happy if you are using some kind of application framework which supports resource injection, as f.i. (Better)SwingApplicationFramework.

Assuming all texts are declared in properties files as usual, something like the snippet below does the trick:

public static class ToggleLocaleAction extends AbstractAction {

    private boolean isAlternative;
    private SingleFrameApplication app;

    public ToggleLocaleAction(SingleFrameApplication app) {
        super("Locale");
        this.app = app;
    }

    @Override
    public void actionPerformed(ActionEvent e) {
        // just toggling as an example
        Locale.setDefault(isAlternative ? Locale.GERMAN : Locale.ENGLISH);
        isAlternative = !isAlternative;
        injectResources(app);
    }

    public void injectResources(SingleFrameApplication app) {
        app.getContext().getResourceMap().injectComponents(app.getMainFrame());
    }
}

Just noticed some glitches:

  • doesn't update the frame title (probably because that's not a beans property)
  • doesn't update the action texts (might need to force some context action update, not sure)
like image 1
kleopatra Avatar answered Oct 31 '22 12:10

kleopatra