Javafx国际化与自定义语言

我正在开发一个支持多种语言的JavaFX应用程序。 我的应用有时会显示一个警告框,例如:

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

语言设置是通过getLocaleSettingFromConfigurationFile()方法获得的
在上面的代码中,我使用Locale.FRENCH作为应用程序语言,所有工作文件:
在此处输入图像描述
两个确认按钮已翻译为法语。

现在我希望我的应用程序也支持越南语(从上面的代码中取消注释return new Locale("vi") )。 在深入细节之后,我发现:

– >两个确认按钮“Ok”,“Cancel”由以下构造:

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

– >显示文本的按钮是从ControlResources.getString(key)呈现的,其源代码如下:

 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 (ie 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 (ie 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, * eg 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); } } 

现在,我尝试了我的解决方案如下:
第1步 :在项目中创建越南语资源文件com/sun/javafx/scene/control/skin/resources/controls_vi.properties
在此处输入图像描述

 ### 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 

在推出应用程序后,按钮语言仍然是英语。
第2步 :我发现加载JavaFx资源文件的类加载器与我的app类加载器不同(请参阅ResourceBundle.getBundle(BASE_NAME) API)。 这是jfxrt.jar资源:
在此处输入图像描述
我试图用应用程序类加载器加载ControlResources类但仍然没有结果:

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

后备解决方案
我可以创建自己的ButtonType “OK”,“取消”并加载我自己的资源字符串,设置创建按钮列表到Alert对象,但我想使用系统提供的资源。

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

因此,任何人都有解决方案,不需要创建新的ButtonType对象。
谢谢

我非常沮丧JRE里面很少有异国语言。 这是一个大问题。 我一直在寻找解决方案,我创建了一个开源项目,演示了如何在这个项目中添加新的语言资源。

GitHub上的项目

我将系统控件JavaFX翻译成一种新语言(be-BY,ru-RU): 在此处输入图像描述

我项目的结构:

 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 

问题的解决方案在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; } } 

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

一个示例切片文件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=Няма калонак ў табліцы 

在这里,您不需要使用特殊字符\u您只需写入任何支持Unicode的文本编辑器。

您可以添加此项目的外来语言文件夹resources/com/sun/javafx/scene/control/skin/resources 。 发送你的controls_*.properties ,我会将它们添加到这个项目中。

您可以在发布部分下载准备好的组装示例