Breaking Down Language Barriers in JavaFX Apps

By Kenji Tachibana, April 22, 2009

With JavaFX technology, you can easily internationalize your application for worldwide users.

Understanding the Code

Internationalizing a JavaFX application is very similar to internationalizing a Java application with the .properties file, except that in the JavaFX language you use the .fxproperties file. The .fxproperties file differs from the .properties file in the following ways:

  1. All the message keys and corresponding strings are quoted with " and combined with =.
  2. You no longer need native2ascii for .fxproperties. You can specify encoding at the top of the .fxproperties file. For example, @charset="shift_jis". The default encoding is UTF-8.
  3. The .fxproperties file can use a string as a message ID, like a .po file. Also, the file can use a message key like the .properties file.
  4. To call a message from .fxproperties in JavaFX, use ##, for example:

    • var s = ##"This needs to be localized"


  5. To call a message from .fxproperties by using message ID, specify the ID between [ and ]. For example:

    • var s = ##[msgID]"This needs to be localized"


Figure 1 shows the Main_ja.fxproperties file, which is used in this sample.

Source Code
// Common Strings, DO NOT Localize
"English" = "English"
"Japanese" = "?????????"
"Chinese" = "????????????"
"Korean" = "?????????"
"Brazilian Portuguese" = "Portugu??s do Brasil"

// Strings for localization.
"Start breaking the barriers!" = "???????????????????????????"
"NB_Title" = "JavaFX ??? NetBeans IDE"
"NB_Desc" = "JavaFX ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????"
"PS_Title" = "JavaFX Production Suite"
"PS_Desc" = "??????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????? JavaFX ????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????????"
"SDK_Title" = "JavaFX SDK"
"SDK_Desc" = "?????????????????????????????????????????????????????? JavaFX ???????????????????????????????????????????????????????????? "

Figure 1: Main_ja.fxproperties

The code in Figure 2 defines each language button.

Source Code
class langButton extends CustomNode {
    // Button coordinates
    var button_x: Number; var button_y: Number;
    // Text in button coordinates
    var text_x: Number; var text_y: Number;
    // This is used to identify which button is clicked.
    // Also used to get the localized button label
    var text: String;
    // Button width
    var button_width: Number;
    // Localized button label
    var ButtonText: String;

    // This function is used when a button is clicked
    // to calculate coordinates for each city,
    // and get the time there.
    // English: Los_Angeles
    // Japanese: Tokyo
    // Simplified Chinese: Shanghai
    // Korean: Seoul
    // Brazilian Portuguese: Sao Paulo
    public function CalcLoc():Void {
        StartX = movedStartX;
        movedStartX = StartX + MoveX;
        JPX = JPX + MoveX;
        USX = USX + MoveX;
        CNX = CNX + MoveX;
        KRX = KRX + MoveX;
        BRX = BRX + MoveX;
    }

    override public function create():Node {
        // Initial locale is English (Los Angeles).
        Locale.setDefault(new Locale("en", "US"));
        TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
        // Get localized button label from Main_en.properties
        if (text.equals("English")) {
            ButtonText = ##"English";
        } else if (text.equals("Japanese")) {
            ButtonText = ##"Japanese";
        } else if (text.equals("Chinese")) {
            ButtonText = ##"Chinese";
        }else if (text.equals("Korean")) {
            ButtonText = ##"Korean";
        }else if (text.equals("Brazilian Portuguese")) {
            ButtonText = ##"Brazilian Portuguese";
        }
        // Button and label group
        Group {
            content: [
                // Button
                Rectangle {
                    x: button_x, y: button_y
                    width: button_width, height: 40
                    arcWidth: 20, arcHeight: 55
                    stroke: Color.BLACK
                    // Draw Gradient for Button
                    fill: LinearGradient {
                        startX: 0.0, startY: 0.0
                        endX: 0.0, endY: 1.0
                        proportional: true
                        stops: [
                            Stop { offset: 0.0, color: Color.WHITE },
                            Stop { offset: 1.0, color: Color.BLACK }
                        ]
                    }
                },
                // Label
                Text {
                    font: Font { size: 18 }
                    x: text_x, y: text_y
                    fill: Color.WHITE
                    content: ButtonText
                }
            ]
            // Add Reflection effect for Button
            effect: Reflection {
                fraction: 0.9
                topOpacity: 0.5
                topOffset: 2.5
            }
            // Action for Button is clicked
            onMouseClicked: function( e: MouseEvent ):Void {
                // Change Flag gif image
                Flag = "{__DIR__}{text}.gif";

                // Actions for each Language Button.
                // 1. Set locale
                // 2. Set timezone
                // 3. Play animation
                if (text.equals("Japanese")) {
                    Locale.setDefault(new Locale("ja", "JP"));
                    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));
                    MoveX = CenterX - JPX;
                    CalcLoc();
                    t.playFromStart();
                } else if (text.equals("Chinese")) {
                    Locale.setDefault(new Locale("zh", "CN"));
                    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Shanghai"));
                    MoveX = CenterX - CNX;
                    CalcLoc();
                    t.playFromStart();
                } else if (text.equals("Korean")) {
                    Locale.setDefault(new Locale("ko", "KR"));
                    TimeZone.setDefault(TimeZone.getTimeZone("Asia/Seoul"));
                    MoveX = CenterX - KRX;
                    CalcLoc();
                    t.playFromStart();
                } else if (text.equals("Brazilian Portuguese")) {
                    Locale.setDefault(new Locale("pt", "BR"));
                    TimeZone.setDefault(TimeZone.getTimeZone("America/Sao_Paulo"));
                    MoveX = CenterX - BRX;
                    CalcLoc();
                    t.playFromStart();
                } else {
                    Locale.setDefault(new Locale("en", "US"));
                    TimeZone.setDefault(TimeZone.getTimeZone("America/Los_Angeles"));
                    MoveX = CenterX - USX;
                    CalcLoc();
                    t.playFromStart();
                }
                // Change Display Text.
                // Those Strings are bound to the Text in Stage.
                viewtext = ##"Start breaking the barriers!";
                NBTitle = ##[NB_Title]"";
                NBDesc = ##[NB_Desc]"";
                PSTitle = ##[PS_Title]"";
                PSDesc = ##[PS_Desc]"";
                SDKTitle = ##[SDK_Title]"";
                SDKDesc = ##[SDK_Desc]"";
            }
        }
    }
}

Figure 2: langButton Class

The onMouseClicked function defines the action of the button clicked. For example, if the '?????????' (which means Japanese) button is clicked, the following actions are performed.

  1. The application locale is changed to Japan by using the java.util.Locale class:

    • Locale.setDefault(new Locale("ja", "JP"));


  2. The application time zone is changed to Tokyo by using the java.util.Timezone class. This class is for the clock displayed in the bottom of the window.

    • TimeZone.setDefault(TimeZone.getTimeZone("Asia/Tokyo"));


  3. The location of Tokyo is calculated and the animation of the world map starts.

    1. MoveX = CenterX - JPX;
      CalcLoc();
      t.playFromStart();


This sample uses the java.util.Locale class for runtime localization. But it can also be implemented by using the javafx.util.StringLocalizer API in JavaFX technology. The StringLocalizer API can define which .fxproperties file will be loaded, so you can load different .fxproperties files based on the clicked language button. But in this case, you need to prepare differently named .fxproperties files, like "Japanese_ja.fxproperties" and "Korean_ko.fxproperties". The reason is if you start a JavaFX application in a Japanese locale, the _ja_JP.fxproperties or _ja.fxproperties file is called from the JavaFX application. So, to use the same file name prefix in runtime localization, you need to change the application locale.

After setting the application locale or time zone from the button action, localized strings are called from the fxproperties. If you clicked the "????????????" (Simplified Chinese) button, Simplified Chinese strings are taken from Main_zh_CN.fxproperties by the code in Figure 3, because the application locale is zh_CN.

Source Code
// Change Display Text.
// Those Strings are bound to the Text in Stage.
viewtext = ##"Start breaking the barriers!";
NBTitle = ##[NB_Title]"";
NBDesc = ##[NB_Desc]"";
PSTitle = ##[PS_Title]"";
PSDesc = ##[PS_Desc]"";
SDKTitle = ##[SDK_Title]"";
SDKDesc = ##[SDK_Desc]"";

Figure 3: Loading Localized String

Those values are bound to the text and they are changed dynamically after the user clicks the language button.

One more useful feature for internationalization in JavaFX technology is extended format for date and time. JavaFX technology can use POSIX strftime style format to display the date and time. Figure 4 is an example of extended format.

Source Code
class Clock {
    public function nextTime() {
        var now = new Date();
        LocalizedDate = "{%tEc now}";
    }

Figure 4: Extended Format

{%tEc now} is the extended format used for the java.util.Date object ("now"). It displays the localized date and time format automatically. The following are examples of extended format.

  • %tx - Localized date
  • %tX - Localized time
  • %tu - A day of the week (1-7, 1 is for Monday)
  • %tEx - Locale's alternate date representation
  • %tEX - Locale's alternate time representation


For details, refer to the POSIX strftime specification.