Internacionalización Javafx con lenguaje personalizado

Estoy desarrollando una aplicación JavaFX con soporte para 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 degetLocaleSettingFromConfigurationFile() método
En el código anterior, uséLocale.FRENCH como idioma de la aplicación y todo funciona archivo:

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

Ahora quiero que mi aplicación también sea compatible con vietnamitas (descomentarreturn new Locale("vi") del código de arriba). Después 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 desdeControlResources.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 un archivo de recursos vietnamitacom/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 del botón sigue siendo inglés.
Paso 2: Descubrí que el cargador de clases para cargar el archivo de recursos JavaFx es diferente del cargador de clases de mi aplicación (verResourceBundle.getBundle(BASE_NAME) API). Este es un recurso dentrojfxrt.jar:

Traté de cargar elControlResources clase con el cargador de clases de la aplicación pero aún sin resultado:

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 alternativa
Puedo crear el míoButtonType "Aceptar", "Cancelar" y cargar mi propia cadena de recursos, la lista de botones creada para elAlert objeto, pero quiero usar el recurso proporcionado por el sistema en su lugar.

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 nuevosButtonType objeto.
Gracias

Respuestas a la pregunta(1)

Su respuesta a la pregunta