16
votes

I try to change the Locale at runtime in my swing application.
But I can't figure out how it supposed to work, or there are no master plan?

I can only think of two choices:
1. Restart the application, not the best user experience.
2. Create a localization manager that can register/unregister components, on a change it just iterate all components and change the text.

Both 1 and 2 feels awkward.

Other info:
For the moment the orientation is not a target.
The application is obfuscated.

Example:

LocRes_en.properties:


    text1 = English text

LocRes_ja.properties


    text1 = Japanese text

ChangeLocale.java:


    import java.awt.EventQueue;
    import java.awt.FlowLayout;
    import java.awt.event.ActionEvent;
    import java.awt.event.ActionListener;
    import java.util.Locale;
    import java.util.ResourceBundle;

    import javax.swing.JButton;
    import javax.swing.JFrame;
    import javax.swing.JLabel;

    public class ChangeLocale {

        private JFrame frame;

        public static void main(String[] args) {
            EventQueue.invokeLater(new Runnable() {
                public void run() {
                    try {
                        ChangeLocale window = new ChangeLocale();
                        window.frame.setVisible(true);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            });
        }

        public ChangeLocale() {
            initialize();
        }

        private void initialize() {
            frame = new JFrame();
            frame.setBounds(100, 100, 450, 300);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            FlowLayout flowLayout = new FlowLayout(FlowLayout.CENTER, 5, 5);
            frame.getContentPane().setLayout(flowLayout);

            JButton btnChangeLoc = new JButton("Change Locale");
            frame.getContentPane().add(btnChangeLoc);

            final JLabel lblLabel1 = new JLabel("New label");
            frame.getContentPane().add(lblLabel1);
            Locale.setDefault(new Locale("en"));
            ResourceBundle r = ResourceBundle.getBundle("LocRes");
            lblLabel1.setText(r.getString("text1"));

            btnChangeLoc.addActionListener(new ActionListener() {
                public void actionPerformed(ActionEvent e) {
                    Locale.setDefault(new Locale("ja"));
                    ResourceBundle r = ResourceBundle.getBundle("LocRes");
                    // Manually iterate through all components :(
                    lblLabel1.setText(r.getString("text1"));
                    //
                }
            });
        }
    }
4

4 Answers

7
votes

I've implemented this with ResourceBundles and an EventBus.

When the locale is changed, the EventBus fires an localeChangedEvent. All JFrame windows which have localized strings then have to subscribe to this event. They must also implement a changeLocale() method which is executed when the event is received.

In this method all strings will be updated to the current locale.

public void changeLocale(ResourceBundle rb) {
    lblLabel1.setText(rb.getString("text1"));
    lblLabel2.setText(rb.getString("text2"));
    ...
}
4
votes

You can try LocaleChangeListener interface -

Changing locale at runtime in Swing

4
votes

Define a GUI and a combobox with supported languages. Add an ItemListener so when the combobox is changed, the texts are updated.

LanguageGUIClient.java:

import javax.swing.*;

public class LanguageGUIClient
{
    public static void main(String[] arguments) throws Exception
    {
        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());

        SwingUtilities.invokeLater(() ->
        {
            LanguageGUI languageGUI = new LanguageGUI();
            languageGUI.setVisible(true);
        });
    }
}

LanguageGUI.java:

import javax.swing.*;
import java.util.Locale;
import java.util.ResourceBundle;

public class LanguageGUI extends JFrame
{
    public static ResourceBundle resourceBundle;

    private JPanel rootPanel;
    private JComboBox<Locale> languageComboBox;
    private JLabel languageLabel;

    public LanguageGUI()
    {
        configureFrameProperties();
    }

    private void configureFrameProperties()
    {
        add(rootPanel);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        setLocationRelativeTo(null);
        languageComboBox.addItem(Locale.US);
        languageComboBox.addItem(Locale.GERMANY);
        languageComboBox.addItem(Locale.FRANCE);
        setTexts();
        languageComboBox.addItemListener(itemEvent -> setTexts());
        setSize(300, 100);
    }

    private void setTexts()
    {
        Locale locale = languageComboBox.getItemAt(languageComboBox.getSelectedIndex());
        resourceBundle = ResourceBundle.getBundle("Bundle", locale);
        setTitle(resourceBundle.getString("application.title"));
        languageLabel.setText(resourceBundle.getString("language") + ":");
        languageComboBox.setToolTipText(resourceBundle.getString("language.tooltip"));
    }
}

Note that I'm using IntelliJ's form designer so the .form file contents are necessary as well.

LanguageGUI.form:

<?xml version="1.0" encoding="UTF-8"?>
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="LanguageGUI">
  <grid id="27dc6" binding="rootPanel" layout-manager="GridLayoutManager" row-count="2" column-count="2" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
    <margin top="0" left="0" bottom="0" right="0"/>
    <constraints>
      <xy x="20" y="20" width="500" height="400"/>
    </constraints>
    <properties/>
    <border type="none"/>
    <children>
      <component id="eb651" class="javax.swing.JComboBox" binding="languageComboBox">
        <constraints>
          <grid row="0" column="1" row-span="1" col-span="1" vsize-policy="0" hsize-policy="2" anchor="8" fill="1" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties>
          <toolTipText value=""/>
        </properties>
      </component>
      <vspacer id="977c1">
        <constraints>
          <grid row="1" column="1" row-span="1" col-span="1" vsize-policy="6" hsize-policy="1" anchor="0" fill="2" indent="0" use-parent-layout="false"/>
        </constraints>
      </vspacer>
      <component id="e59f" class="javax.swing.JLabel" binding="languageLabel">
        <constraints>
          <grid row="0" column="0" row-span="1" col-span="1" vsize-policy="0" hsize-policy="0" anchor="8" fill="0" indent="0" use-parent-layout="false"/>
        </constraints>
        <properties>
          <text value=""/>
        </properties>
      </component>
    </children>
  </grid>
</form>

Furthermore, you need to create the respective resource bundle files in your resources directory:

The result is a GUI with a combobox allowing you to change language immediately:

2
votes

I have done something similar once. Although my task was simpler: this was a system tray application, so I just had to change the menu items text.

But in your case I think this is doable. First, avoid hard-coded strings in your GUI layer. Create class that changes locale and then iterates over all visible frames and goes down to all panels and components and changes the text written on them. The only problem I can expect here is text drawn on canvas.