2016-11-28 95 views
3

我正在开发具有多种语言支持的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作为应用程序的语言获得,一切工作文件:
enter image description here
两确认按钮已被翻译成法语。

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

- >双确认“确定”按钮,“取消”从构造:

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

现在,我想我的解决方案如下:
步骤1:在越南创建资源文件com/sun/javafx/scene/control/skin/resources/controls_vi.properties项目
enter image description here

### 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资源文件的类加载器与我的应用类加载器不同(请参阅ResourceBundle.getBundle(BASE_NAME) API)。这是资源的内部jfxrt.jar
enter image description here
我试图加载ControlResources类应用程序类加载器,但还是没有结果:

public static void main(String[] args) throws Exception { 
    List<Locale> 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: [email protected] 
//   loader 2: [email protected] 

    } 

    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对象。
谢谢

+1

你可以打包你的翻译文件放在一个单独的JAR并将它添加到引导类路径,从而使JavaFX的类加载器可以看到他们:'java的-Xbootclasspath/A :my-translations.jar -cp ...'。无论是自定义按钮类型是否值得...我不知道! –

回答

1

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

项目上GitHub

我翻译的系统控制的JavaFX成一种新的语言(是-BY,RU-RU): enter image description here

我的项目的结构:

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,我会将它们添加到此项目中。

准备组装的例子,你可以在releases部分下载