JavaFX在整个应用程序中更改语言环境

JavaFX在整个应用程序中更改语言环境,javafx,locale,resourcebundle,Javafx,Locale,Resourcebundle,这是我的StartApp.java,我的应用程序的入口点 public class StartApp extends Application { private Locale locale = new Locale("en"); public Locale getLocale(){ return locale; } public void setLocale(Locale locale){ this.locale = locale; } @Override public v

这是我的
StartApp.java
,我的应用程序的入口点

public class StartApp extends Application {
private Locale locale = new Locale("en");

public Locale getLocale(){
    return locale;
}

public void setLocale(Locale locale){
    this.locale = locale;
}

@Override
public void start(Stage primaryStage) throws Exception{
    ResourceBundle bundle = ResourceBundle.getBundle("resources.bundles/MyBundle", locale);
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("../view/LoginView.fxml"), bundle);
    Parent root = fxmlLoader.load();        
    primaryStage.setTitle("Title");
    primaryStage.setScene(new Scene(root, 750, 400));
    primaryStage.setResizable(false);
    primaryStage.show();
}


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

}
然后在
LoginController.java
上,我创建StartApp的实例,并为2个按钮设置操作

StartApp startApp = new StartApp(); 


@Override
public void initialize(URL location, ResourceBundle resources) {
    bundle = resources;

plBtn.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        try {
            startApp.setLocale(new Locale("pl"));
            changeLanguage(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});

enBtn.setOnAction(new EventHandler<ActionEvent>() {
    @Override
    public void handle(ActionEvent event) {
        try {
            startApp.setLocale(new Locale("en"));
            changeLanguage(event);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
});
到目前为止,一切都很好,只要我点击按钮,它就会改变语言。但我现在想做的是用choosen语言打开新的窗口(舞台),但不幸的是,它总是用StartApp上的语言打开新的场景

下面是
LoginController
中打开新阶段的方法

public void register(ActionEvent event) throws Exception{
    ((Node)event.getSource()).getScene().getWindow().hide();
    Stage primaryStage = new Stage();
    ResourceBundle bundle = ResourceBundle.getBundle("resources.bundles/MyBundle", startApp.getLocale());
    FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("../view/RegisterView.fxml"), bundle);
    Parent root = fxmlLoader.load();        
    primaryStage.setTitle("Title2");
    primaryStage.setScene(new Scene(root, 750, 400));

    primaryStage.setResizable(false);
    primaryStage.show();
}
顺便说一句,Iv尝试将StartApp扩展到LoginController,公开区域设置等,每次都是一样的。当我创造

Locale newLocale = null;

LoginController
中,当我单击
initialize
中定义的按钮时,尝试为其赋值,我得到了nullpointerexception

我不知道如何通过一个命令更改所有文本。 为了保持简单,我只需分别重命名每个元素

例如:

i18n/messages.properties

i18n/messages\u ukr.properties

i18n/messages\u rus.properties

将包含当前区域设置的Singleton Context.java:

public class Context {

    private final List<Locale> availableLocales;

    private Locale currentLocale;

    private static Context instance;

    public static Context getInstance() {
        if (instance == null) {
            instance = new Context();
        }
        return instance;
    }

    private Context() {
        availableLocales = new LinkedList<>();
        availableLocales.add(new Locale("eng"));
        availableLocales.add(new Locale("ukr"));
        availableLocales.add(new Locale("rus"));
        currentLocale = new Locale("eng");  // default locale
    }

    /**
     * This method is used to return available locales
     *
     * @return available locales
     */
    public List<Locale> getAvailableLocales() {
        return availableLocales;
    }

    /**
     * This method is used to return current locale setting
     *
     * @return current locale
     */
    public Locale getCurrentLocale() {
        return currentLocale;
    }

    /**
     * This method is used to set current locale setting
     *
     * @param currentLocale locale to set
     */
    public void setCurrentLocale(Locale currentLocale) {
        this.currentLocale = currentLocale;
    }
我使用SceneBuilder工具创建*.fxml文件。 例如,包含用于语言更改的标签和选择框的其中一个文件的屏幕控制器可能如下所示:

public class LanguageChangeScreenController implements Initializable {
    @FXML
    private Label welcomeLabel;
    
    @FXML
    private ChoiceBox<Locale> languageChoiceBox;
    
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        initLanguageChangeListener();
        refreshLocalization(); // I use %key.name syntax in .fxml files to initialize component's names instead of calling this method here
    }
    
    **
     * This method is used to update the name of each component on the screen
     */
    private void refreshLocalization() {
        welcomeLabel.setText(MessageUtil.getMessage("label.welcome"));
    }
    
    private void initLanguageChangeListener() {
        languageChoiceBox.getItems().addAll(Context.getInstance().getAvailableLocales());
        languageChoiceBox.getSelectionModel().select(Context.getInstance().getCurrentLocale());
        languageChoiceBox.setOnAction(actionEvent -> {
            Context.getInstance().setCurrentLocale(languageChoiceBox.getSelectionModel().getSelectedItem());
            refreshLocalization();
        });
    }
}
公共类语言ChangeScreenController实现可初始化{
@FXML
自有品牌welcomeLabel;
@FXML
私人选择框语言选择框;
@凌驾
公共void初始化(URL、ResourceBundle、ResourceBundle){
initLanguageChangeListener();
refreshLocalization();//我在.fxml文件中使用%key.name语法来初始化组件的名称,而不是在此处调用此方法
}
**
*此方法用于更新屏幕上每个组件的名称
*/
私有void refreshLocalization(){
welcomeLabel.setText(MessageUtil.getMessage(“label.welcome”);
}
私有void initLanguageChangeListener(){
languageChoiceBox.getItems().addAll(Context.getInstance().GetAvailableCales());
languageChoiceBox.getSelectionModel().select(Context.getInstance().getCurrentLocale());
languageChoiceBox.setOnAction(actionEvent->{
Context.getInstance().setCurrentLocale(languageChoiceBox.getSelectionModel().getSelectedItem());
刷新本地化();
});
}
}

我不知道如何通过一个命令更改所有文本。 为了保持简单,我只需分别重命名每个元素

例如:

i18n/messages.properties

i18n/messages\u ukr.properties

i18n/messages\u rus.properties

将包含当前区域设置的Singleton Context.java:

public class Context {

    private final List<Locale> availableLocales;

    private Locale currentLocale;

    private static Context instance;

    public static Context getInstance() {
        if (instance == null) {
            instance = new Context();
        }
        return instance;
    }

    private Context() {
        availableLocales = new LinkedList<>();
        availableLocales.add(new Locale("eng"));
        availableLocales.add(new Locale("ukr"));
        availableLocales.add(new Locale("rus"));
        currentLocale = new Locale("eng");  // default locale
    }

    /**
     * This method is used to return available locales
     *
     * @return available locales
     */
    public List<Locale> getAvailableLocales() {
        return availableLocales;
    }

    /**
     * This method is used to return current locale setting
     *
     * @return current locale
     */
    public Locale getCurrentLocale() {
        return currentLocale;
    }

    /**
     * This method is used to set current locale setting
     *
     * @param currentLocale locale to set
     */
    public void setCurrentLocale(Locale currentLocale) {
        this.currentLocale = currentLocale;
    }
我使用SceneBuilder工具创建*.fxml文件。 例如,包含用于语言更改的标签和选择框的其中一个文件的屏幕控制器可能如下所示:

public class LanguageChangeScreenController implements Initializable {
    @FXML
    private Label welcomeLabel;
    
    @FXML
    private ChoiceBox<Locale> languageChoiceBox;
    
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        initLanguageChangeListener();
        refreshLocalization(); // I use %key.name syntax in .fxml files to initialize component's names instead of calling this method here
    }
    
    **
     * This method is used to update the name of each component on the screen
     */
    private void refreshLocalization() {
        welcomeLabel.setText(MessageUtil.getMessage("label.welcome"));
    }
    
    private void initLanguageChangeListener() {
        languageChoiceBox.getItems().addAll(Context.getInstance().getAvailableLocales());
        languageChoiceBox.getSelectionModel().select(Context.getInstance().getCurrentLocale());
        languageChoiceBox.setOnAction(actionEvent -> {
            Context.getInstance().setCurrentLocale(languageChoiceBox.getSelectionModel().getSelectedItem());
            refreshLocalization();
        });
    }
}
公共类语言ChangeScreenController实现可初始化{
@FXML
自有品牌welcomeLabel;
@FXML
私人选择框语言选择框;
@凌驾
公共void初始化(URL、ResourceBundle、ResourceBundle){
initLanguageChangeListener();
refreshLocalization();//我在.fxml文件中使用%key.name语法来初始化组件的名称,而不是在此处调用此方法
}
**
*此方法用于更新屏幕上每个组件的名称
*/
私有void refreshLocalization(){
welcomeLabel.setText(MessageUtil.getMessage(“label.welcome”);
}
私有void initLanguageChangeListener(){
languageChoiceBox.getItems().addAll(Context.getInstance().GetAvailableCales());
languageChoiceBox.getSelectionModel().select(Context.getInstance().getCurrentLocale());
languageChoiceBox.setOnAction(actionEvent->{
Context.getInstance().setCurrentLocale(languageChoiceBox.getSelectionModel().getSelectedItem());
刷新本地化();
});
}
}

当您重新加载
LoginView.fxml
时,它会创建一个新的控制器;在该控制器中,您创建了一个新的
StartApp
实例,并且不在该实例上设置区域设置。这里的方法似乎是错误的:创建自己的
应用程序
子类的实例基本上总是一个坏主意:应该只有一个实例(为您创建的实例上调用了
start(…)
)。为此,请尝试使用MVC方法,并将区域设置(或者资源包)作为属性包含在模型中。然后,只需与所有控制器共享一个模型实例。请看一看,当您重新加载
LoginView.fxml
时,它会创建一个新的控制器;在该控制器中,您创建了一个新的
StartApp
实例,并且不在该实例上设置区域设置。这里的方法似乎是错误的:创建自己的
应用程序
子类的实例基本上总是一个坏主意:应该只有一个实例(为您创建的实例上调用了
start(…)
)。为此,请尝试使用MVC方法,并将区域设置(或者资源包)作为属性包含在模型中。然后只与所有控制器共享一个模型实例
public class Context {

    private final List<Locale> availableLocales;

    private Locale currentLocale;

    private static Context instance;

    public static Context getInstance() {
        if (instance == null) {
            instance = new Context();
        }
        return instance;
    }

    private Context() {
        availableLocales = new LinkedList<>();
        availableLocales.add(new Locale("eng"));
        availableLocales.add(new Locale("ukr"));
        availableLocales.add(new Locale("rus"));
        currentLocale = new Locale("eng");  // default locale
    }

    /**
     * This method is used to return available locales
     *
     * @return available locales
     */
    public List<Locale> getAvailableLocales() {
        return availableLocales;
    }

    /**
     * This method is used to return current locale setting
     *
     * @return current locale
     */
    public Locale getCurrentLocale() {
        return currentLocale;
    }

    /**
     * This method is used to set current locale setting
     *
     * @param currentLocale locale to set
     */
    public void setCurrentLocale(Locale currentLocale) {
        this.currentLocale = currentLocale;
    }
/**
 * This class is used to provide methods to work with localized messages
 *
 * @author manfredi
 */
public abstract class MessageUtil {
    private final static Logger logger = LoggerFactory.getLogger(MessageUtil.class);
    private final static String RESOURCE_NAME = "i18n.messages";

    /**
     * This method is used to return resource bundle with current locale
     *
     * @return resource bundle with current locale. Otherwise resource bundle with default locale.
     */
    public static ResourceBundle getResourceBundle() {
        return ResourceBundle.getBundle(RESOURCE_NAME, Context.getInstance().getCurrentLocale());
    }

    /**
     * This method is used to return localized message by it`s {@code key}
     *
     * @param key message key
     * @return localized message
     */
    public static String getMessage(String key) {
        String message;
        try {
            message = getResourceBundle().getString(key);
        } catch (MissingResourceException e) {
            logger.error("{}", e.getMessage());
            message = key;
        }
        return message;
    }

    /**
     * This method is used to format localized message by it`s {@code key} using {@code args} as arguments list
     *
     * @param key  message key by which the corresponding message will be found
     * @param args list of arguments used in the message
     * @return formatted localized message
     */
    public static String formatMessage(String key, Object... args) {
        MessageFormat messageFormat = new MessageFormat(getMessage(key), getResourceBundle().getLocale());
        return messageFormat.format(args);
    }

}
public class LanguageChangeScreenController implements Initializable {
    @FXML
    private Label welcomeLabel;
    
    @FXML
    private ChoiceBox<Locale> languageChoiceBox;
    
    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        initLanguageChangeListener();
        refreshLocalization(); // I use %key.name syntax in .fxml files to initialize component's names instead of calling this method here
    }
    
    **
     * This method is used to update the name of each component on the screen
     */
    private void refreshLocalization() {
        welcomeLabel.setText(MessageUtil.getMessage("label.welcome"));
    }
    
    private void initLanguageChangeListener() {
        languageChoiceBox.getItems().addAll(Context.getInstance().getAvailableLocales());
        languageChoiceBox.getSelectionModel().select(Context.getInstance().getCurrentLocale());
        languageChoiceBox.setOnAction(actionEvent -> {
            Context.getInstance().setCurrentLocale(languageChoiceBox.getSelectionModel().getSelectedItem());
            refreshLocalization();
        });
    }
}