JavaFX 2 und Internationalisierung


83

Ich habe gerade angefangen, meine erste JavaFX 2-Anwendung zu schreiben, nachdem ich die Grundlagen gelernt habe, und möchte sie internationalisieren.

Ich stelle fest, dass in JavaFX 1.x die Skriptsprache eine sehr einfache Internationalisierung von Zeichenfolgen ermöglichte. Gibt es ähnliche Funktionen in JavaFX 2?

Grundsätzlich gilt: Was ist die beste Vorgehensweise für die Internationalisierung einer JavaFX 2-Anwendung?


Zum Umschalten zwischen Sprachen finden Sie hier einige Informationen: [ stackoverflow.com/a/26318795/2131257[1] [1]: stackoverflow.com/a/26318795/2131257
Androdos

Antworten:


163

Die grundlegenden Schritte (unter anderem) einer Internationalisierung einer Java-App sind das LocaleLizing und die Ressourcenbündelung. In JavaFX können Sie dies verwenden FXMLLoader#setResources(). Hier eine SSCCE-Demo, um dies zu demonstrieren. Die Codes sind selbstbeschreibend.
Struktur des Demopakets:

bundledemo
    |------ BundleDemo.java
    |------ MyController.java
    |------ MyView.fxml  
bundles
    |------ MyBundle_en.properties
    |------ MyBundle_kg.properties

MyBundle_en.properties

key1=Name Surname
key2=How are you?

MyBundle_kg.properties

key1=Aты Жөнү
key2=Кандайсың?

MyView.fxml

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.layout.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.*?>

<BorderPane fx:controller="bundledemo.MyController" xmlns:fx="http://javafx.com/fxml">
    <top>
        <!-- This label's text will be set by the controller -->
        <Label fx:id="lblTextByController"/> 
    </top>
    <center>
        <!-- This label's text will be taken from the bundle automatically -->
        <Label text="%key2"/>
    </center>
</BorderPane>

MyController.java

package bundledemo;

import java.net.URL;
import java.util.ResourceBundle;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.control.Label;

public class MyController implements Initializable {

    @FXML private Label lblTextByController;
    private ResourceBundle bundle;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        bundle = resources;
        lblTextByController.setText(bundle.getString("key1"));
    }
}

BundleDemo.java

package bundledemo;
// imports are ignored.

public class BundleDemo extends Application {

    private Stage stage;

    @Override
    public void start(Stage primaryStage) {
        stage = primaryStage;
        Button btnEN = new Button();
        btnEN.setText("English");
        btnEN.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent event) {
                loadView(new Locale("en", "EN"));
            }
        });

        Button btnKG = new Button();
        btnKG.setText("Kyrgyz");
        btnKG.setOnAction(new EventHandler<ActionEvent>() {
            @Override public void handle(ActionEvent event) {
                loadView(new Locale("kg", "KG"));
            }
        });

        VBox root = new VBox(20);
        root.getChildren().add(HBoxBuilder.create().spacing(10).style("-fx-background-color: gray").padding(new Insets(5)).children(btnEN, btnKG).build());
        root.getChildren().add(new StackPane());
        primaryStage.setScene(new Scene(root, 300, 250));
        primaryStage.show();
    }

    private void loadView(Locale locale) {
        try {
            FXMLLoader fxmlLoader = new FXMLLoader();
            fxmlLoader.setResources(ResourceBundle.getBundle("bundles.MyBundle", locale));
            Pane pane = (BorderPane) fxmlLoader.load(this.getClass().getResource("MyView.fxml").openStream());
            // replace the content
            StackPane content = (StackPane) ((VBox) stage.getScene().getRoot()).getChildren().get(1);
            content.getChildren().clear();
            content.getChildren().add(pane);
        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}

Bildschirmfoto:

Geben Sie hier die Bildbeschreibung ein


Ausgezeichnete Antwort und ich werde es so akzeptieren, wie es ist, aber ich hätte erwähnen sollen, dass ich die Schnittstelle eher in Code als in FXML baue. Gibt es eine schnelle und einfache Möglichkeit, Code zu internationalisieren? Mir ist klar, dass ich eine Suche nach ResourceBundle.getBundle + durchführen kann, aber ich hatte gehofft, dass es so etwas wie die% key-Notation gibt, die ich stattdessen verwenden kann.
Wobblycogs

7
Dann können Sie dies auf normale Weise wie in jeder anderen Java-Anwendung tun. Bestimmen Sie das Gebietsschema des Benutzers / Kunden und ändern Sie das Gebietsschema der App entsprechend (holen Sie sich lang spezifische Daten von DB vs.). Laden Sie das entsprechende Bundle von ResourceBundle.getBundle("bundles.MyBundle", locale). Ändern Sie jeden Text, den Sie in Ihrer Ansicht / Seite verwendet haben bundle.getString("key").
Uluk Biy

2
Es funktioniert bei mir nicht, wenn ich ein ResourceBundle über die setResources () -Methode bereitstelle. Es funktioniert, wenn ich das ResourceBundle über die load () -Methode bereitstelle.
Jurica Krizanic

1
@Jurica Krizanic: hatte das gleiche Problem und löste es auf die gleiche Weise: FXMLLoader.load(getClass().getResource(sceneId), getResources())Wo sceneIdist Zeichenfolge und Methode getResources()gibt Ressource mit dem richtigen Gebietsschema zurück.
TG

Schön, dass eine Variante mit der statischen Methode wie im Orakel-Tutorial auch funktioniert (und sie ist kürzer): Pane pane = (BorderPane) FxmlLoader.load (this.getClass (). GetResource ("MyView.fxml"), ResourceBundle.getBundle ("bundles.MyBundle", Gebietsschema));
pdem

14

Das funktioniert bei mir:

└───src
    ├───app
    ├───bundles // <- here the "bundles"
    ├───dicts
    ├───images
    ├───libs
    └───resources

Im Bundles-Paket sind

LangBundle_en.properties
LangBundle_de.properties

Beispielinhalt:

enter_pwd=Enter your password:

Um sie zu laden, benutze ich den folgenden Code:

@Override
public void initialize(URL location, ResourceBundle resources) {
    ResourceBundle lngBndl = ResourceBundle
            .getBundle("bundles.LangBundle", new Locale("en", "EN"));

    tvSetupPwd.setText(lngBndl.getString("enter_pwd"));
    // ...
}

4

Schau dir mein Beispiel an Geben Sie hier die Bildbeschreibung ein

Mehr habe ich hier oder auf GitHub beschrieben

Aktualisieren:

Die Lösung ist 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;
    }
}

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

@Moritz Sie hätten auf den Link geklickt und die detaillierte Antwort und die vollständigen Quelldateien gesehen. Ich habe einen Link als Antwort auf stackoverflow.com eingefügt. Wegen dir muss ich überall den gleichen Code einfügen. Ich erwarte, dass Sie mögen und nicht nicht mögen
Andrei Krasutski
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.