繁体   English   中英

JavaFX:在运行时更改应用程序语言

[英]JavaFX: Change application language on the run

我正在使用 FXML 中描述的核心组件制作 JavaFX 桌面应用程序,我想为用户提供更改语言的选项。 但是,一旦从 FXML 加载了组件,我还没有找到任何直接的方法来更改语言。

问题是有没有标准的方法来处理 JavaFX 中的语言切换。

你可以做这样的事情。 与您的回答一样,您要么希望将其实现为单例,要么使用 DI 框架在您需要的任何地方注入单个实例:

public class ObservableResourceFactory {

    private ObjectProperty<ResourceBundle> resources = new SimpleObjectProperty<>();
    public ObjectProperty<ResourceBundle> resourcesProperty() {
        return resources ;
    }
    public final ResourceBundle getResources() {
        return resourcesProperty().get();
    }
    public final void setResources(ResourceBundle resources) {
        resourcesProperty().set(resources);
    }

    public StringBinding getStringBinding(String key) {
        return new StringBinding() {
            { bind(resourcesProperty()); }
            @Override
            public String computeValue() {
                return getResources().getString(key);
            }
        };
    }
}

现在您可以执行以下操作:

ObservableResourceFactory resourceFactory = .... ;

resourceBundle.setResources(...);

Label greetingLabel = new Label();
greetingLabel.textProperty().bind(resourceFactory.getStringBinding("greeting"));

任何时候你更新资源

resourceFactory.setResources(...);

将导致标签更新其文本。

这是一个 SSCCE(对于将ResourceBundle强制转换为单个可运行类的极其丑陋的方式深表歉意......)

import java.util.ListResourceBundle;
import java.util.Locale;
import java.util.ResourceBundle;

import javafx.application.Application;
import javafx.beans.binding.StringBinding;
import javafx.beans.property.ObjectProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.control.Label;
import javafx.scene.control.ListCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

public class ResourceBundleBindingExample extends Application {

    private static final String RESOURCE_NAME = Resources.class.getTypeName() ;

    private static final ObservableResourceFactory RESOURCE_FACTORY = new ObservableResourceFactory();

    static {
        RESOURCE_FACTORY.setResources(ResourceBundle.getBundle(RESOURCE_NAME));
    }

    @Override
    public void start(Stage primaryStage) {
        ComboBox<Locale> languageSelect = new ComboBox<>();
        languageSelect.getItems().addAll(Locale.ENGLISH, Locale.FRENCH);
        languageSelect.setValue(Locale.ENGLISH);
        languageSelect.setCellFactory(lv -> new LocaleCell());
        languageSelect.setButtonCell(new LocaleCell());

        languageSelect.valueProperty().addListener((obs, oldValue, newValue) -> {
            if (newValue != null) {
                RESOURCE_FACTORY.setResources(ResourceBundle.getBundle(RESOURCE_NAME, newValue));
            }
        });

        Label label = new Label();
        label.textProperty().bind(RESOURCE_FACTORY.getStringBinding("greeting"));

        BorderPane root = new BorderPane(null, languageSelect, null, label, null);
        root.setPadding(new Insets(10));
        Scene scene = new Scene(root, 400, 400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static class LocaleCell extends ListCell<Locale> {
        @Override
        public void updateItem(Locale locale, boolean empty) {
            super.updateItem(locale, empty);
            if (empty) {
                setText(null);
            } else {
                setText(locale.getDisplayLanguage(locale));
            }
        }
    }

    public static class ObservableResourceFactory {

        private ObjectProperty<ResourceBundle> resources = new SimpleObjectProperty<>();
        public ObjectProperty<ResourceBundle> resourcesProperty() {
            return resources ;
        }
        public final ResourceBundle getResources() {
            return resourcesProperty().get();
        }
        public final void setResources(ResourceBundle resources) {
            resourcesProperty().set(resources);
        }

        public StringBinding getStringBinding(String key) {
            return new StringBinding() {
                { bind(resourcesProperty()); }
                @Override
                public String computeValue() {
                    return getResources().getString(key);
                }
            };
        }

    }

    public static class Resources extends ListResourceBundle {

        @Override
        protected Object[][] getContents() {
            return new Object[][] {
                    {"greeting", "Hello"}
            };
        }

    }

    public static class Resources_fr extends ListResourceBundle {

        @Override
        protected Object[][] getContents() {
            return new Object[][] {
                    {"greeting", "Bonjour"}
            };
        }

    }

    public static void main(String[] args) {
        launch(args);
    }
}

和@Chiggiddi 一样,我也喜欢国际化的字符串方法。 在java端绑定每个标签太繁琐了。 所以我想出了一种在 fxml 端进行绑定和表达式绑定的混合方法。 我今天在这里分享这个解决方案,因为我在我访问过的所有 stackoverflow 问题中都没有找到类似的解决方案。 我希望它会对某人有所帮助。

首先创建一个从 ResourceBundle 的键填充的地图的可观察地图,如下所示:

public class LocaleManager extends SimpleMapProperty<String, Object> {

    private String bundleName = "language"; // a file language.properties must be present at the root of your classpath
    
    public LocaleManager() {
        super(FXCollections.observableHashMap());
        reload();
    }

    public void changeLocale(Locale newLocale) {
        Locale.setDefault(newLocale);
        reload();
    }

    private void reload() {
        ResourceBundle bundle = ResourceBundle.getBundle(bundleName);
        Enumeration<String> keys = bundle.getKeys();
        while (keys.hasMoreElements()) {
            String key = keys.nextElement();
            String value = bundle.getString(key);
            
            String[] parts = key.split("\\.");
            
            MapProperty<String, Object> map = this;
            
            for (int i=0;i < parts.length; i++) {
                String part = parts[i];
                if (i == parts.length - 1) {
                    map.put(part, value);
                } else {
                    if (!map.containsKey(part)) {
                        map.put(part, new SimpleMapProperty<>(FXCollections.observableHashMap()));
                    }
                    map = (MapProperty<String, Object>)map.get(part);
                }
            }
        }
    }
    
    public StringBinding bind(String key) {
        String[] parts = key.split("\\.");
        
        MapProperty<String, Object> map = this;
        
        for (int i=0;i < parts.length; i++) {
            String part = parts[i];
            if (i == parts.length - 1) {
                return Bindings.valueAt(map, part).asString();
            } else {
                if (!map.containsKey(part)) {
                    map.put(part, new SimpleMapProperty<>(FXCollections.observableHashMap()));
                }
                map = (MapProperty<String, Object>)map.get(part);
            }
        }
        throw new NullPointerException("Unknown key : " + key);
    }
}

现在您需要为您的视图创建一个基类,将 LocaleManager 作为包含 getter 和 setter 的属性公开:

public class BaseView {

    private LocaleManager lang;
    
    public BaseView() {
        lang = new LocaleManager();
    }
    
    public LocaleManager langProperty() {
        return lang;
    }

    public ObservableMap<String, Object> getLang() {
        return lang.get();
    }

    public void setLang(MapProperty<String, Object> resource) {
        this.lang.set(resource);
    }

}

现在,如果您的视图扩展 BaseView

public MyView extends BaseView {}

fxml 中的任何表达式,如 ${controller.lang。 my.resource.key } 将绑定到您的 ResourceBundle 中的相同键

java端的绑定仍然可以使用:

someField.textProperty().bind(langProperty().bind(BUNDLE_KEY));

现在要即时更改语言,只需使用:

langProperty().changeLocale(newLocale);

如果您想更改所有应用程序的语言,请记住将 LocaleManager 作为应用程序中的单例。

尚不支持在 SceneBuilder 端字符串字段的绑定表达式。 但是如果将来被接受,以下拉取请求可能会有所帮助:

拉取请求

编辑:

要回答@Novy 评论,绑定密钥包含 2 个部分。 第一部分“controller.lang”让您可以访问控制器的 lang 属性。 与在控制器类中编写this.getLang()相同

因此,如果在您的属性文件中,您具有以下属性:

item1 = "somestring"
item1.item2 = "someotherstring"
item1.item2.item3 = "someotherotherstring"

然后

${controller.lang.item1} == "somestring"
${controller.lang.item1.item2} == "someotherstring"
${controller.lang.item1.item2.item3} == "someotherotherstring"

${controller.lang.item1.item2} 可以翻译成你的控制器类中的以下java代码

((Map<String, Object)this.getLang().get("item1")).get("item2").toString()

或使用 javafx 提供的隐式转换

this.getLang().get("item1").get("item2")

我目前正在使用单例(在后期可能通过 DI 框架注入)作为语言 ResourceBundle 的包装器。

我的计划是实现可观察模式并通知所有需要更改的组件(使用 @FXML 语句注入的子组件)。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM