java 国际化 自定义,Javafx国际化与自定义语言

I'm developing a JavaFX application with multiple language support. My app sometimes shows an alert box, for example:

package application;

import java.util.Locale;

import javafx.application.Application;

import javafx.event.ActionEvent;

import javafx.stage.Stage;

import javafx.scene.Scene;

import javafx.scene.control.Alert;

import javafx.scene.control.Button;

import javafx.scene.control.Alert.AlertType;

import javafx.scene.layout.BorderPane;

public class Main extends Application {

@Override

public void start(Stage primaryStage) {

try {

Button btn = new Button("Show alert");

btn.setOnAction(this::handleButton);

BorderPane root = new BorderPane();

root.setCenter(btn);

Scene scene = new Scene(root,200, 200);

primaryStage.setScene(scene);

primaryStage.show();

} catch(Exception e) {

e.printStackTrace();

}

}

void handleButton(ActionEvent e){

Alert alert = new Alert(AlertType.CONFIRMATION);

alert.showAndWait();

}

static Locale getLocaleSettingFromConfigurationFile(){

return Locale.FRENCH;

//return new Locale("vi");

}

public static void main(String[] args) {

Locale appLocale = getLocaleSettingFromConfigurationFile();

Locale.setDefault(appLocale);

launch(args);

}

}

The language setting is obtained via getLocaleSettingFromConfigurationFile() method

In the code above, I used Locale.FRENCH as app language and everything works file:

QQK6Z.png

Two confirm buttons have been translated to French.

Now I want my app to support Vietnamese as well (uncomment return new Locale("vi") from the code above). After digging into details, I found that:

->Two confirm button "Ok", "Cancel" are constructed from:

package javafx.scene.control;

import com.sun.javafx.scene.control.skin.resources.ControlResources;

import javafx.beans.NamedArg;

import javafx.scene.control.Button;

import javafx.scene.control.ButtonBar.ButtonData;

/**

* The ButtonType class is used as part of the JavaFX {@link Dialog} API (more

* specifically, the {@link DialogPane} API) to specify which buttons should be

* shown to users in the dialogs. Refer to the {@link DialogPane} class javadoc

* for more information on how to use this class.

*

* @see Alert

* @see Dialog

* @see DialogPane

* @since JavaFX 8u40

*/

public final class ButtonType {

/**

* A pre-defined {@link ButtonType} that displays "Apply" and has a

* {@link ButtonData} of {@link ButtonData#APPLY}.

*/

public static final ButtonType APPLY = new ButtonType(

"Dialog.apply.button", null, ButtonData.APPLY);

/**

* A pre-defined {@link ButtonType} that displays "OK" and has a

* {@link ButtonData} of {@link ButtonData#OK_DONE}.

*/

public static final ButtonType OK = new ButtonType(

"Dialog.ok.button", null, ButtonData.OK_DONE);

/**

* A pre-defined {@link ButtonType} that displays "Cancel" and has a

* {@link ButtonData} of {@link ButtonData#CANCEL_CLOSE}.

*/

public static final ButtonType CANCEL = new ButtonType(

"Dialog.cancel.button", null, ButtonData.CANCEL_CLOSE);

/**

* A pre-defined {@link ButtonType} that displays "Close" and has a

* {@link ButtonData} of {@link ButtonData#CANCEL_CLOSE}.

*/

public static final ButtonType CLOSE = new ButtonType(

"Dialog.close.button", null, ButtonData.CANCEL_CLOSE);

/**

* A pre-defined {@link ButtonType} that displays "Yes" and has a

* {@link ButtonData} of {@link ButtonData#YES}.

*/

public static final ButtonType YES = new ButtonType(

"Dialog.yes.button", null, ButtonData.YES);

/**

* A pre-defined {@link ButtonType} that displays "No" and has a

* {@link ButtonData} of {@link ButtonData#NO}.

*/

public static final ButtonType NO = new ButtonType(

"Dialog.no.button", null, ButtonData.NO);

/**

* A pre-defined {@link ButtonType} that displays "Finish" and has a

* {@link ButtonData} of {@link ButtonData#FINISH}.

*/

public static final ButtonType FINISH = new ButtonType(

"Dialog.finish.button", null, ButtonData.FINISH);

/**

* A pre-defined {@link ButtonType} that displays "Next" and has a

* {@link ButtonData} of {@link ButtonData#NEXT_FORWARD}.

*/

public static final ButtonType NEXT = new ButtonType(

"Dialog.next.button", null, ButtonData.NEXT_FORWARD);

/**

* A pre-defined {@link ButtonType} that displays "Previous" and has a

* {@link ButtonData} of {@link ButtonData#BACK_PREVIOUS}.

*/

public static final ButtonType PREVIOUS = new ButtonType(

"Dialog.previous.button", null, ButtonData.BACK_PREVIOUS);

private final String key;

private final String text;

private final ButtonData buttonData;

/**

* Creates a ButtonType instance with the given text, and the ButtonData set

* as {@link ButtonData#OTHER}.

*

* @param text The string to display in the text property of controls such

* as {@link Button#textProperty() Button}.

*/

public ButtonType(@NamedArg("text") String text) {

this(text, ButtonData.OTHER);

}

/**

* Creates a ButtonType instance with the given text, and the ButtonData set

* as specified.

*

* @param text The string to display in the text property of controls such

* as {@link Button#textProperty() Button}.

* @param buttonData The type of button that should be created from this ButtonType.

*/

public ButtonType(@NamedArg("text") String text,

@NamedArg("buttonData") ButtonData buttonData) {

this(null, text, buttonData);

}

/**

* Provide key or text. The other one should be null.

*/

private ButtonType(String key, String text, ButtonData buttonData) {

this.key = key;

this.text = text;

this.buttonData = buttonData;

}

/**

* Returns the ButtonData specified for this ButtonType in the constructor.

*/

public final ButtonData getButtonData() { return this.buttonData; }

/**

* Returns the text specified for this ButtonType in the constructor;

*/

public final String getText() {

if (text == null && key != null) {

return ControlResources.getString(key);

} else {

return text;

}

}

/** {@inheritDoc} */

@Override public String toString() {

return "ButtonType [text=" + getText() + ", buttonData=" + getButtonData() + "]";

}

}

->The button displaying text is rendered from ControlResources.getString(key), its source code:

package com.sun.javafx.scene.control.skin.resources;

import java.util.ResourceBundle;

public final class ControlResources {

// Translatable properties

private static final String BASE_NAME = "com/sun/javafx/scene/control/skin/resources/controls";

// Non-translateable properties

private static final String NT_BASE_NAME = "com/sun/javafx/scene/control/skin/resources/controls-nt";

// Do not cache the bundle here. It is cached by the ResourceBundle

// class and may be updated if the default locale changes.

private ControlResources() {

// no-op

}

/*

* Look up a string in the properties file corresponding to the

* default locale (i.e. the application's locale). If not found, the

* search then falls back to the base controls.properties file,

* containing the default string (usually English).

*/

public static String getString(String key) {

return ResourceBundle.getBundle(BASE_NAME).getString(key);

}

/*

* Look up a non-translatable string in the properties file

* corresponding to the default locale (i.e. the application's

* locale). If not found, the search then falls back to the base

* controls-nt.properties file, containing the default string.

*

* Note that property values may be set in locale-specific files,

* e.g. when a property value is defined for a country rather than

* a language. However, there are no such files included with

* JavaFX 8, but may be added to the classpath by developers or

* users.

*/

public static String getNonTranslatableString(String key) {

return ResourceBundle.getBundle(NT_BASE_NAME).getString(key);

}

}

Now, I tried my solution as follow:

Step 1: create Vietnamese resource file com/sun/javafx/scene/control/skin/resources/controls_vi.properties in the project

JQK4o.png

### Dialogs ###

Dialog.apply.button = Áp d\u1EE5ng

Dialog.ok.button = OK

Dialog.close.button = \u0110óng

Dialog.cancel.button = H\u1EE7y b\u1ECF

Dialog.yes.button = Có

Dialog.no.button = Không

Dialog.finish.button = Hoàn thành

Dialog.next.button = Ti\u1EBFp

Dialog.previous.button = Tr\u01B0\u1EDBc

After lauching the app, the button language still English.

Step 2: I figured out that the class loader to load JavaFx resource file is differ from my app class loader (see ResourceBundle.getBundle(BASE_NAME) API). This is resource inside jfxrt.jar:

NLGDu.png

I tried to load the ControlResources class with application class loader but still no result:

public static void main(String[] args) throws Exception {

List fxSupported = Arrays.asList(Locale.ENGLISH, Locale.FRENCH); // Add later ....

Locale appLocale = getLocaleSettingFromConfigurationFile();

Locale.setDefault(appLocale);

// Load class from current class loader

if (!fxSupported.contains(appLocale)){

ClassLoader loader = Main.class.getClassLoader();

Class> loadedCls = Class.forName("com.sun.javafx.scene.control.skin.resources.ControlResources", true, loader);

System.out.printf("Loader 1: %s\nloader 2: %s\n", loader, loadedCls.getClassLoader());

// Loader 1: sun.misc.Launcher$AppClassLoader@73d16e93

// loader 2: sun.misc.Launcher$ExtClassLoader@6d06d69c

}

launch(args);

}

Fallback solution

I can create my own ButtonType "OK", "Cancel" and load my own resource string, the set created button list to the Alert object, but I want to use the system provided resource instead.

ResourceBundle res = ResourceBundle.getBundle("application.myownres");

ButtonType OK = new ButtonType(res.getString("btn.ok"), ButtonData.OK_DONE);

ButtonType CANCEL = new ButtonType(res.getString("btn.cancel"), ButtonData.CANCEL_CLOSE);

Alert alert = new Alert(AlertType.CONFIRMATION, "Are you sure", OK, CANCEL);

alert.showAndWait();

So, anyone has solution that does not need to create new ButtonType object.

Thanks

解决方案

I am very upset that the JRE inside very few exotic languages. And this is a big problem. I've been looking for a solution, I created an open source project which demonstrates how to add new languages resources in this project.

Project on GitHub

I translated the system controls JavaFX into a new language (be-BY, ru-RU):

DTGo0.png

Structure of my project:

java

|------ com\krasutski\language\Messages.java

|------ com\krasutski\util\PropertyLoader.java

|------ com\krasutski\util\ReflectionUtils.java

|------ com\krasutski\view\MainController.java

|------ com\krasutski\MainApp.java

resources

|------ com\sun\javafx\scene\control\skin\resources\controls_be_BY.properties

|------ com\sun\javafx\scene\control\skin\resources\controls_ru.properties

|------ fxml\main.fxml

|------ icons\app-128x128x32.png

|------ messages\messages.properties

|------ messages\messages_be_BY.properties

|------ messages\messages_ru.properties

|------ styles\styles.css

the solution to the problem is in Messages.java

/**

* The class with all messages of this application.

*/

public abstract class Messages {

private static ResourceBundle BUNDLE;

private static final String FIELD_NAME = "lookup";

private static final String BUNDLE_NAME = "messages/messages";

private static final String CONTROLS_BUNDLE_NAME = "com/sun/javafx/scene/control/skin/resources/controls";

public static final String MAIN_APP_TITLE;

public static final String DIALOG_HEADER;

public static final String MAIN_CONTROLLER_CONTENT_TEXT;

public static final String MAIN_CONTROLLER_HELLO_TEXT;

public static final String MAIN_CONTROLLER_GOODBYE_TEXT;

static {

final Locale locale = Locale.getDefault();

final ClassLoader classLoader = ControlResources.class.getClassLoader();

final ResourceBundle controlBundle = getBundle(CONTROLS_BUNDLE_NAME,

locale, classLoader, PropertyLoader.getInstance());

final ResourceBundle overrideBundle = getBundle(CONTROLS_BUNDLE_NAME,

PropertyLoader.getInstance());

final Map override = getUnsafeFieldValue(overrideBundle, FIELD_NAME);

final Map original = getUnsafeFieldValue(controlBundle, FIELD_NAME);

//noinspection ConstantConditions,ConstantConditions,unchecked

original.putAll(override);

BUNDLE = getBundle(BUNDLE_NAME, PropertyLoader.getInstance());

MAIN_APP_TITLE = BUNDLE.getString("MainApp.title");

DIALOG_HEADER = BUNDLE.getString("Dialog.information.header");

MAIN_CONTROLLER_CONTENT_TEXT = BUNDLE.getString("MainController.contentText");

MAIN_CONTROLLER_HELLO_TEXT = BUNDLE.getString("MainController.helloText");

MAIN_CONTROLLER_GOODBYE_TEXT = BUNDLE.getString("MainController.goodbyeText");

}

public static ResourceBundle GetBundle() {

return BUNDLE;

}

}

and in PropertyLoader.java

public class PropertyLoader extends ResourceBundle.Control {

private static final String PROPERTIES_RESOURCE_NAME = "properties";

private static final PropertyLoader INSTANCE = new PropertyLoader();

public static PropertyLoader getInstance() {

return INSTANCE;

}

@Override

public ResourceBundle newBundle(final String baseName, final Locale locale, final String format,

final ClassLoader loader, final boolean reload)

throws IllegalAccessException, InstantiationException, IOException {

final String bundleName = toBundleName(baseName, locale);

final String resourceName = toResourceName(bundleName, PROPERTIES_RESOURCE_NAME);

ResourceBundle bundle = null;

InputStream stream = null;

if (reload) {

final URL url = loader.getResource(resourceName);

if (url != null) {

final URLConnection connection = url.openConnection();

if (connection != null) {

connection.setUseCaches(false);

stream = connection.getInputStream();

}

}

} else {

stream = loader.getResourceAsStream(resourceName);

}

if (stream != null) {

try {

bundle = new PropertyResourceBundle(new InputStreamReader(stream, StandardCharsets.UTF_8));

} finally {

stream.close();

}

}

return bundle;

}

}

An example slice file controls_be_BY.properties

# encoding=utf-8

# ProgressIndicator, the string that's displayed at 100%

ProgressIndicator.doneString=Гатова

# ListView

ListView.noContent=Няма змесціва

# TableView

TableView.noContent=Няма змесціва ў табліцы

TableView.noColumns=Няма калонак ў табліцы

Here you don't need to use a special character \u you just write to any text editor which supports Unicode.

You can add your exotic languages folder resources/com/sun/javafx/scene/control/skin/resources of this project. Send me your controls_*.properties and I'll add them to this project.

Ready assembled example you can download in the releases section

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>