Swing国际化 - 如何在运行时更新语言

5
我已经按照Google的Window Builder Pro扩展的使用说明国际化了我的应用程序。将显示在标签中的字符串现在存储在由“外部化字符串”向导创建的属性文件中(我使用了经典的eclipse消息文件)。
在我的initialize方法中,所有的标签都被初始化并设置它们的文本如下:
JLabel lblLanguage = new JLabel(Messages.getString("App.lblLanguage.text")); //$NON-NLS-1$

我已经在我的类App中创建了一个枚举类型,它保存了GUI界面:

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

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

    Locale getLocale() {
        return loc;
    }
}

语言将通过使用枚举类型Lang的组合框来设置,以显示可用的语言:

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

我找到了很多关于Swing应用国际化的教程和指南,但是没有一个涵盖如何更新所有标签(以及可能包含文本的其他控件)。这里在SO上有this answer一篇文章,如果链接不死的话可能会有帮助。
因为我对Java中的GUI编程还比较新,所以我现在不太知道该怎么做,以下是我的问题:

  • 如果设置了新语言,更改运行时所有控件的语言的最佳方法是什么?
  • 是否可以(推荐)将所有控件声明为我的App类的私有成员,以便有一个方法来更新它们的文本属性(->一个updateLanguage方法将执行此操作)?
4个回答

3
当我这样做的时候,我也没有找到内置的方法来实现它。
所以我创建了自己的LocaleChangeListener接口。所有的UI屏幕/面板都实现了该接口,并在我的中央UI控制器类上注册为侦听器。每当语言改变时,就会向它们中的每一个发出localeChanged(lang)调用。
不过,我仍然保留了组件初始化的单独部分 - 没有必要再次进行所有操作。
他们只需要像这样的代码: public void localeChanged(Lang lang) { nameLabel.setText(lang.getText("label.name")); submitButton.setText(lang.getText("button.submit")); etc...
// 我认为在某些屏幕上,我需要使用这个来解决一些奇怪的JTable/JTabbedPane布局问题: this.revalidate(); }

2
我通过扩展JLabel并覆盖getText来解决这个问题,以返回语言选择的评估结果。
您还需要一些发布/订阅机制来“告诉”标签语言已更改。
在这里我使用Guava事件总线
import com.google.common.eventbus.EventBus;

public class EventBusHolder {

    private static EventBus bus = new EventBus();

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

当用户更改语言时,您会触发事件:
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);
    }
});

要使用的标签组件:
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();
    }

}

当你实例化标签时,必须传递翻译的键:
JLabel lbl = new JLabel("App.lblLanguage.text");

更多的使用示例,请查看guava事件总线

重写getText听起来确实是个好主意,尽管已经过了这么长时间,我甚至都记不得在哪个项目中遇到了这个特定的问题^^ 不过有一件事:你的类名叫"MyLabel",但你的构造函数却是StudioLabel... - wullxz
是的,我以为你已经找到了解决方案。我提供答案是因为它可能对其他人有用。感谢提醒,我已经更正了名字。 - Heitor

1

由于用户不经常更改语言,因此重新启动仅仅是可以接受的。这样做的好处是旧语言的布局工件不会破坏文本。

这需要应用程序的可保存状态。

JLabel new JLabel(Messages.getString("App.lblLanguage.text")); 立即评估为本地化文本,并可能布局到该文本的宽度。因此,它不适用于动态语言更改。

另一种方法是依赖名称属性或反射来填充所有文本值。或者代码:

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

使用一些Nls.setText在设置中对lblLanguage进行弱引用。

1

核心 Swing 没有什么可以减轻这种痛苦的东西。如果您正在使用某种支持资源注入的应用程序框架(例如 BetterSwingApplicationFramework),那么您可能会感到高兴。

假设所有文本都像通常情况下那样在属性文件中声明,那么类似以下片段的内容就可以解决问题:

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

刚刚注意到一些小问题:

  • 不会更新框架标题(可能是因为这不是bean属性)
  • 不会更新操作文本(可能需要强制更新一些上下文操作,不确定)

修改后,大部分字符串值不适合,因此我使用了重新布局整个容器的原因。 - mKorbel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接