JavaFX changing Locale in whole application

3.6k views Asked by At

Here is my StartApp.java, entry point of my application.

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

}

Then on LoginController.java I create instance of StartApp and set onActions for 2 buttons

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

here is my changeLanguage method, which refresh current window and changes its language

public void changeLanguage(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/LoginView.fxml"), bundle);
        Parent root = fxmlLoader.load();        
    primaryStage.setTitle("Title2");
    primaryStage.setScene(new Scene(root, 750, 400));
    primaryStage.setResizable(false);
    primaryStage.show();
}

And till now everything works fine, it changes language once I click buttons. But what I want to do now is to open new window(stage) with choosen language, but unfortunatelly, it always open new scene with language set on StartApp.

Here is method in LoginController than opens new stage.

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

Btw. Iv tried just extending StartApp to LoginController, making locale public, etc, everytime it ends up the same. When I created

Locale newLocale = null;

in LoginController, and then tried to assign values to it once I click buttons defined in initialize, I got nullpointerexception.

1

There are 1 answers

0
Manfredi On

I don't know how to change all text by one command. To keep it simlpe i just rename each element separately.

Example:

i18n/messages.properties

label.welcome = Welcome

i18n/messages_ukr.properties

label.welcome = Ласкаво просимо

i18n/messages_rus.properties

label.welcome = Добро пожаловать

Singleton Context.java which will contain current locale:

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

Util class MessageUtil.java will return message in current locale (seted in Context):

/**
 * 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);
    }

}

I use SceneBuilder tool for creating *.fxml files. For example screen controller for one of this files that contain Label and ChoiceBox for language change might looks like following:

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