JavaFX localization Sample

I just wanted to know how to make JavaFX Application for localization.
First of all, this code shows you only how to apply resource properties to the JavaFX Label control. And Java8 has 5 type of Chronology classes that easily fit to JavaFX DatePicker control.
FYI see http://docs.oracle.com/javafx/scenebuilder/1/user_guide/i18n-support.htm

  • Figure1

You can select Locale and Calendar in this screen.
When you click the locale ComboBox, you can see country and language(e.g. United States(English)).
But once you select the locale, this ComboBox displays the locale.toString???(e.g. en_US)
I have no idea how to fix this problem…
In the EntryStart.java(see code-2-2) shows you how to set locale to the comboBox and change the label using StringConverter.
You can get Calendars from Chronology#getAvailableChronologies method and put each Chronology to the RadioButton by setUserData method.

  • Figure2

When you select the [United States(English)] and [ISO] by Label_en_US.properties
LocalizationInfoTitle.fxml(see code3-1)and Entry1.fxml(see code5-1) show you how to set the properties key using the present sign(%).
Thanks to the JavaFX, it is easy to set the resource properties to your application.
You just make properties file for each locale(see Figure 8), and use FXMLLoader with ResourceBundle class which is created with the properties file and locale.(see code3-2)
I think the NetBeans has very good editor for properties file(see Figure7).

  • Figure3

When you select the [Japan(Japanese)] and [Japanese Calendar] by Label_ja_JP.properties
I needed function, that has page transfer and localizations data(locale and calendar) transfer.
I found that The JavaFX custom control has this function.
In the code3-1,3-2 and 4 show you how to create Localization.java and Entry1.java(EntryBase.java) as a custom control.
F.Y.R http://docs.oracle.com/javafx/2/fxml_get_started/custom_control.htm
I got the hint from this web site(http://skrb.hatenablog.com/entry/2012/06/16/175920) which explain how to implement page transitions.

  • Figure4

When you select the [Taiwan(Chinese)] and [Minguo Calendar] by Label.properties(default)

  • Figure5

When you select the [Saudi Arabia(Arabic)] and [Islamic Umm al-aura Calendar] by Label_ar_SA.properties
JavaFX DataPicker has setChronology method, all you need is to use this method for the specific calendar.(see code5-2)

  • Figure6

When you select the [Thailamd(Thai)] and [Buddhist Calendar] by Label.properties(default)

  • Figure7

NetBean8 has properties editor

  • Figure8

The project files

  • code1-1(Main.java)
public class Main extends Application{
    private BorderPane root;
    private EntryStartController entryStartController; 
    private HBox bottomHBox;
    private Button startButton;
    private Button resetLocaleButton;
    private Button nextButton;
    private PresentInfo presentInfo;
    private int fxmlIx = 0;
    private ENTRY[] fxmls = ENTRY.values();

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

    @Override
    public void start(Stage stage) throws Exception {
        this.presentInfo = new PresentInfo();
        Locale.setDefault(Locale.US);
        this.root = new BorderPane();
        setEntryStart();
        this.bottomHBox = new HBox();
        this.bottomHBox.getStyleClass().add("hbox");
        this.bottomHBox.setId("hbox-custom");        
        this.startButton = new Button("Start");
        this.resetLocaleButton = new Button("reset locale");
        this.nextButton = new Button("next");
        this.bottomHBox.getChildren().add(this.startButton);        
        this.setStartEvent();
        this.setResetLocaleEvent();
        this.setNextEvent();
        this.root.setBottom(this.bottomHBox);
        Scene scene = new Scene(root, 700, 500);
        stage.setTitle("Localization Sample");
        scene.getStylesheets().addAll("/css/layout.css");
        stage.setScene(scene);
        stage.show();
    }

    private void setEntryStart(){
        URL url = getClass().getResource("EntryStart.fxml");
        FXMLLoader loader = new FXMLLoader(url);
        try {
            this.presentInfo.centerNode = loader.<Node>load();
        } catch (IOException ex) {
            Logger.getLogger(Main.class.getName()).log(Level.SEVERE, null, ex);
        }
        this.root.setCenter(this.presentInfo.centerNode);
        this.entryStartController = loader.getController();
    }

    private void setStartEvent() {
        this.startButton.setOnAction(event -> {
            this.presentInfo.localizationInfo = this.entryStartController.getLocalizationInfo();
            this.root.getChildren().remove(this.presentInfo.centerNode);
            this.presentInfo.topTitle = new LocalizationInfoTitle(this.presentInfo.localizationInfo);
            this.root.setTop(this.presentInfo.topTitle);
            this.bottomHBox.getChildren().remove(this.startButton);
            this.bottomHBox.getChildren().addAll(this.resetLocaleButton, this.nextButton);
            setEntry();
        });
    }
    
    private void setResetLocaleEvent() {
        this.resetLocaleButton.setOnAction(event -> {
            this.root.getChildren().remove(this.presentInfo.centerNode);
            this.root.getChildren().remove(this.presentInfo.topTitle);
            fxmlIx = 0;
            this.bottomHBox.getChildren().remove(this.resetLocaleButton);
            this.bottomHBox.getChildren().remove(this.nextButton);
            this.bottomHBox.getChildren().addAll(this.startButton);
            this.setEntryStart();
        });
    }

    private void setNextEvent() {
        this.nextButton.setOnAction(event -> {
            this.root.getChildren().remove(this.presentInfo.centerNode);
            fxmlIx++;
            setEntry();
        });
    }
    private void setEntry() {
        ENTRY entry = fxmls[fxmlIx];
        EntryBase entryBase = entry.getController();
        entryBase.loadFxml(this.presentInfo.localizationInfo, entry.getFxmlPath());
        this.root.setCenter(entryBase);
    }
    
private class PresentInfo{
    Node centerNode;
    LocalizationInfo localizationInfo;
    LocalizationInfoTitle topTitle;
}

}
enum ENTRY{
    Entry1("Entry1.fxml") {
        @Override
        EntryBase getController() {
            return new Entry1();
        }
    },
    Entry2("Entry2.fxml") {
        @Override
        EntryBase getController() {
            return new Entry2();
        }
    };
    private String fxmlPath;
    private ENTRY(String fxmlPath) {
        this.fxmlPath = fxmlPath;
    }
    abstract EntryBase getController();
    String getFxmlPath(){
        return this.fxmlPath;
    }
}
  • code2-1(EntryStart.fxml)
<AnchorPane id="AnchorPane" prefHeight="500.0" prefWidth="800.0" stylesheets="@../../css/layout.css" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.sample.EntryStartController">
   <children>
      <ComboBox fx:id="localeCombo" layoutX="172.0" layoutY="54.0" prefWidth="150.0" />
      <Label layoutX="45.0" layoutY="54.0" prefHeight="26.0" prefWidth="102.0" text="Select Locale" />
      <Label layoutX="45.0" layoutY="113.0" text="Select Calendar" />
      <VBox fx:id="calendarVBox" layoutX="172.0" layoutY="113.0" prefHeight="179.0" prefWidth="288.0" spacing="10.0" style="-fx-border-style: solid; -fx-border-color: #666666;">
         <padding>
            <Insets bottom="10.0" left="10.0" right="10.0" top="10.0" />
         </padding></VBox>
   </children>
</AnchorPane>
  • code2-2(EntryStartController.java)
public class EntryStartController implements Initializable{
    @FXML
    private ComboBox<Locale> localeCombo;
    @FXML
    private VBox calendarVBox;
    private ToggleGroup calendarGroup = new ToggleGroup();

    @Override
    public void initialize(URL location, ResourceBundle resources) {        
        Locale.setDefault(Locale.ENGLISH);
        setLocaleCombo();
        setCalendar();
    }
    private void setLocaleCombo() {
        ObservableList<Locale> list = FXCollections.observableArrayList(
                new Locale("ja", "JP"),  // Japan
                new Locale("en", "US"),  // United States
                new Locale("fr", "FR"),  // France
                new Locale("de", "DE"),  // Germany
                new Locale("th", "TH"),  // Thailand
                new Locale("ar", "SA"),  // Saudi Arabia
                new Locale("zh", "TW")   // Taiwan
        );
        StringConverter<Locale> converter = new StringConverter<Locale>(){
            @Override
            public String toString(Locale object) {
                return String.format("%s(%s)", object.getDisplayCountry(), object.getDisplayLanguage());
            }
            @Override
            public Locale fromString(String string) {
                return null;
            }
        };
        localeCombo.getItems().addAll(list);
        localeCombo.setCellFactory(ComboBoxListCell.<Locale>forListView(converter));
        localeCombo.getSelectionModel().select(0);
    }

    private void setCalendar() {
        List<RadioButton> chronolories = Chronology.getAvailableChronologies().stream()
                .map(chronology -> {
                    RadioButton radioButton = new RadioButton(chronology.getDisplayName(TextStyle.FULL, Locale.getDefault()));
                    radioButton.setToggleGroup(calendarGroup);
                    radioButton.setUserData(chronology);
                    if (chronology.getCalendarType().equals("iso8601")) {
                        radioButton.setSelected(true);
                    }
                    return radioButton;
                }).collect(Collectors.toList());
        calendarVBox.getChildren().addAll(chronolories);
    }
    public LocalizationInfo getLocalizationInfo() {
        Locale locale = localeCombo.getSelectionModel().getSelectedItem();
        Chronology chronology = (Chronology) calendarGroup.getSelectedToggle().getUserData();
        LocalizationInfo localizationInfo = new LocalizationInfo();
        localizationInfo.setLocale(locale);
        localizationInfo.setChronolory(chronology);
        return localizationInfo;
    }
}
  • code3-1(LocalizationInfoTitle.fxml)
<fx:root type="javafx.scene.layout.HBox" xmlns:fx="http://javafx.com/fxml" 
         stylesheets="@../../css/layout.css" styleClass="hbox"> 
      <Label text="%locale_label" styleClass="locale-label" />
      <Label fx:id="selectedLocale"  text="" styleClass="locale-label"/>
        <HBox.margin>
           <Insets right="50.0" />
        </HBox.margin>
      <Label text="%chronology_label" styleClass="locale-label" />
      <Label fx:id="selectedChronology" text="" styleClass="locale-label" />
</fx:root>
  • code3-2(LocalizationInfoTitle.java)
public class LocalizationInfoTitle extends HBox implements Initializable{
    private LocalizationInfo localizationInfo;
    @FXML
    private Label selectedLocale;
    @FXML
    private Label selectedChronology;
    
    public LocalizationInfoTitle(LocalizationInfo info) {
        this.localizationInfo = info;
        loadFxml();
    }
    private void loadFxml() {
        URL url = getClass().getResource("LocalizationInfoTitle.fxml");
        ResourceBundle bundle = ResourceBundle.getBundle("resources/Label"
                , localizationInfo.getLocale());
        FXMLLoader loader = new FXMLLoader(url, bundle);
        loader.setRoot(this);
        loader.setController(this);
        try {
            loader.<HBox>load();
        } catch (IOException ex) {
            Logger.getLogger(LocalizationInfoTitle.class.getName()).log(Level.SEVERE, null, ex);
        }
    }
    @Override
    public void initialize(URL location, ResourceBundle resources) {
        Locale locale = this.localizationInfo.getLocale();
        this.selectedLocale.setText(String.format("%s(%s)"
                , locale.getDisplayCountry(locale), locale.getDisplayLanguage(locale)));
        Chronology chronolory = this.localizationInfo.getChronolory();
        this.selectedChronology.setText(chronolory.getDisplayName(TextStyle.FULL, locale));
    }
}
  • code4(EntryBase.java)
public abstract class EntryBase extends GridPane implements Initializable {
    protected LocalizationInfo localizationInfo;

    protected void loadFxml(LocalizationInfo localizationInfo, String fxmlPath) {
        this.localizationInfo = localizationInfo;
        URL url = getClass().getResource(fxmlPath);
        ResourceBundle bundle = ResourceBundle.getBundle("resources/Label", localizationInfo.getLocale());
        FXMLLoader loader = new FXMLLoader(url, bundle);
        loader.setRoot(this);
        loader.setController(this);
        try {
            loader.<GridPane>load();
        } catch (IOException ex) {
            Logger.getLogger(LocalizationInfoTitle.class.getName()).log(Level.SEVERE, null, ex);
        }
    }

    public abstract boolean goNext();
}
  • code5-1(Entry1.xml)
<fx:root xmlns:fx="http://javafx.com/fxml/1" type="javafx.scene.layout.GridPane" hgap="10" vgap="10" 
    stylesheets="@../../css/layout.css" styleClass="grid"> 
    <children>
      <Label text="%letter_field_label" GridPane.columnIndex="0" GridPane.rowIndex="0" />
      <TextField fx:id="letterText" GridPane.columnIndex="1" GridPane.rowIndex="0" />
      <Label text="%digit_field_label" GridPane.columnIndex="0" GridPane.rowIndex="1" />
      <TextField fx:id="digitText" GridPane.columnIndex="1" GridPane.rowIndex="1" />
      <Label text="%date_label" GridPane.columnIndex="0" GridPane.rowIndex="2" />
      <DatePicker fx:id="datePicker" GridPane.columnIndex="1" GridPane.rowIndex="2" />
      <Label text="%currency_field_label" GridPane.columnIndex="0" GridPane.rowIndex="3" />
      <TextField fx:id="currencyText" GridPane.columnIndex="1" GridPane.rowIndex="3" />
    </children>
</fx:root>
  • code5-2(Entry1.java)
public class Entry1 extends EntryBase {
    @FXML
    private DatePicker datePicker;
    
    @Override
    public boolean goNext() {
        // TODO do something about locatizaltion
        return true;
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        this.datePicker.setChronology(this.localizationInfo.getChronolory());
    }    
}

The whole code is here(https://github.com/tomoTaka01/LocalizationSample)
I will continue working on this project to learn about localization.
I am really happy if you leave any comments.
keep coding... ;-)