ventajas sobre platzi libreria documentación descargar como comandos java javafx javafx-8

platzi - todo sobre javafx



Internacionalización de Javafx con lenguaje personalizado (1)

Estoy muy enojado porque el JRE está dentro de muy pocos idiomas exóticos. Y este es un gran problema. He estado buscando una solución. Creé un proyecto de código abierto que demuestra cómo agregar nuevos recursos de idiomas en este proyecto.

Proyecto en GitHub

Traducí el sistema controla JavaFX a un nuevo idioma (be-BY, ru-RU):

Estructura de mi proyecto

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

la solución al problema está en 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; } }

y en 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; } }

Un archivo de división de ejemplo 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=Няма калонак ў табліцы

Aquí no necesita usar un carácter especial /u Usted simplemente escribe en cualquier editor de texto que admita Unicode.

Puede agregar sus resources/com/sun/javafx/scene/control/skin/resources carpeta de idiomas exóticos resources/com/sun/javafx/scene/control/skin/resources de este proyecto. Envíame tus controls_*.properties y los agregaré a este proyecto.

Ejemplo ensamblado listo que puede descargar en la sección de lanzamientos

Estoy desarrollando una aplicación JavaFX con soporte de múltiples idiomas. Mi aplicación a veces muestra un cuadro de alerta, por ejemplo:

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

La configuración del idioma se obtiene a través del método getLocaleSettingFromConfigurationFile()
En el código anterior, utilicé Locale.FRENCH como lenguaje de aplicación y todo funciona en el archivo:

Dos botones de confirmación han sido traducidos al francés.

Ahora quiero que mi aplicación también sea compatible con vietnamita (no reelegir la return new Locale("vi") del código anterior). Luego de profundizar en los detalles, descubrí que:

-> Dos botones de confirmación "Ok", "Cancelar" se construyen a partir de:

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

-> El botón que muestra el texto se representa desde ControlResources.getString(key) , su código fuente:

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

Ahora, probé mi solución de la siguiente manera:
Paso 1 : crear el archivo de recursos vietnamita com/sun/javafx/scene/control/skin/resources/controls_vi.properties en el proyecto

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

Después de iniciar la aplicación, el idioma de los botones sigue siendo inglés.
Paso 2 : descubrí que el cargador de clases para cargar el archivo de recursos JavaFx es diferente de mi cargador de clases de aplicaciones (vea la API ResourceBundle.getBundle(BASE_NAME) ). Este es un recurso dentro de jfxrt.jar :

Traté de cargar la clase ControlResources con el cargador de clases de la aplicación, pero todavía no hay resultados:

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: sun.misc.Launcher$AppClassLoader@73d16e93 // loader 2: sun.misc.Launcher$ExtClassLoader@6d06d69c } launch(args); }

Solución de emergencia
Puedo crear mi propio ButtonType "OK", "Cancelar" y cargar mi propia cadena de recursos, la lista de botones creados creados para el objeto Alert , pero en su lugar quiero usar el recurso proporcionado por el sistema.

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

Entonces, cualquiera tiene una solución que no necesita crear un nuevo objeto ButtonType .
Gracias