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
- Java version
- 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
- 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... ;-)