Changing the language in JavaFX 8 DatePicker

2020-03-24 06:56发布

问题:

When adding a DatePicker to my app I get the following:

I assume this is because I use Hebrew on my computer. How can I change the language of the DatePicker to be English?

回答1:

You can define the default locale for your instance of the Java Virtual Machine calling:

Locale.setDefault(Locale.ENGLISH);

Or if you can't find the locale, you need, in the pre made constants, you can look up the country code in the list of officially supported locales and create your "custom" locale like this:

Locale.setDefault(Locale("cs")) //locale for Czech language

on the start method. If you also want to implement a custom formatter for the text editor, you should add locale to the formatter too.

This is just an example:

private final DateTimeFormatter formatter = 
        DateTimeFormatter.ofPattern("EEEE, d.MM.uuuu", Locale.ENGLISH);

@Override
public void start(Stage primaryStage) {
    Locale.setDefault(Locale.ENGLISH);

    DatePicker datePicker=new DatePicker();
    datePicker.setValue(LocalDate.now());
    datePicker.setConverter(new StringConverter<LocalDate>() {

        @Override
        public String toString(LocalDate object) {
            return object.format(formatter);
        }

        @Override
        public LocalDate fromString(String string) {
            return LocalDate.parse(string, formatter);
        }
    });
    StackPane root = new StackPane(datePicker);
    Scene scene = new Scene(root, 400, 400);

    primaryStage.setScene(scene);
    primaryStage.show();
}

EDIT

By design, DatePicker uses Locale.getDefault() in all the formats applied to the controls displayed on the popup. This can be checked in com.sun.javafx.scene.control.skin.DatePickerContent class.

Unless you provide a custom skin for the control changing these formatters, in order to change the DatePicker content to English, avoiding further changes in other localized controls, a workaround could be this:

private final Locale myLocale = Locale.getDefault(Locale.Category.FORMAT);

@Override
public void start(Stage primaryStage) {
    DatePicker datePicker=new DatePicker();
    datePicker.setValue(LocalDate.now());
    datePicker.setOnShowing(e-> Locale.setDefault(Locale.Category.FORMAT,Locale.ENGLISH));
    datePicker.setOnShown(e-> Locale.setDefault(Locale.Category.FORMAT,myLocale));
    ...
}

EDIT 2

Returning to the original locale on setOnShown is too soon, since if the user changes the month, the original locale is used and it will not be shown properly. To work it should be turned off both on setOnHiding and on setOnAction.

private final Locale myLocale = Locale.getDefault(Locale.Category.FORMAT);

@Override
public void start(Stage primaryStage) {
    DatePicker datePicker=new DatePicker();
    datePicker.setValue(LocalDate.now());
    datePicker.setOnShowing(e-> Locale.setDefault(Locale.Category.FORMAT,Locale.ENGLISH));
    datePicker.setOnHiding(e-> Locale.setDefault(Locale.Category.FORMAT,myLocale));
    datePicker.setOnAction(e-> Locale.setDefault(Locale.Category.FORMAT,myLocale));
    ...
}


回答2:

Starting with fx9, the skins are moved out into public api - so you might consider creating a custom skin that allows to configure the Locale per-picker. Unfortunately, the class that does the internal formatting is still hidden in internals, so tweaking requires to go dirty.

If you are daring/allowed to access internals, one way to go is

  • put the picker-specific Locale that should be used in the picker's property map
  • extend DatePickerContent (internal class - dirty!) to override its (public! no danger here) getLocale() to first check whether the picker's properties contain a custom locale - if so return that, otherwise delegate to super
  • extend DatePickerSkin to inject (reflective access needed - dirty!) the custom content
  • extend DatePicker to return the custom skin as its default
  • configure the picker's cronology and converter to use the specific Locale - this is needed to format the text in the editor appropriately

Sample code:

public class DatePickerLocale extends Application {

    /**
     * Custom DatePickerContent that uses a per-picker Locale if 
     * available.
     */
    public static class XDatePickerContent extends DatePickerContent {

        public XDatePickerContent(DatePicker datePicker) {
            super(datePicker);
        }

        @Override
        protected Locale getLocale() {
            if (datePicker != null) {
                Object locale = datePicker.getProperties().get("CONTROL_LOCALE");
                if (locale instanceof Locale) {
                    return (Locale) locale;
                }
            }
            return super.getLocale();
        }

    }

    /**
     * Custom DatePickerSkin that injects a custom content.
     */
    public static class XDatePickerSkin extends DatePickerSkin {

        public XDatePickerSkin(DatePicker control) {
            super(control);
        }

        @Override
        public Node getPopupContent() {
            DatePickerContent content = (XDatePickerContent) getDatePickerContent();
            if (!(content instanceof XDatePickerContent)) {
                content = new XDatePickerContent((DatePicker) getSkinnable());
                replaceDatePickerContent(content);
            }
            return content;
        }

        //------------- going dirty: reflective access to super

        protected DatePickerContent getDatePickerContent() {
            return (DatePickerContent) FXUtils.invokeGetFieldValue(DatePickerSkin.class, this, "datePickerContent");
        }

        protected void replaceDatePickerContent(DatePickerContent content) {
            FXUtils.invokeSetFieldValue(DatePickerSkin.class, this, "datePickerContent", content);
        }
    }

    private Parent createContent() {
        LocalDate now = LocalDate.now();
        DatePicker picker = new DatePicker(now) {

            @Override
            protected Skin<?> createDefaultSkin() {
                return new XDatePickerSkin(this);
            }

        };

        Locale customLocale = Locale.CHINA;
        // config locale for content
        picker.getProperties().put("CONTROL_LOCALE", customLocale);
        // config locale for chronology/converter
        picker.setChronology(Chronology.ofLocale(customLocale));
        picker.setConverter(new LocalDateStringConverter(FormatStyle.SHORT, 
                customLocale, picker.getChronology()));
        // just to see some formats with default locale
        List<String> patterns = List.of("e", "ee", "eee", "eeee", "eeeee");
        HBox box = new HBox(10);
        patterns.forEach(p -> {
            DateTimeFormatter ccc = DateTimeFormatter.ofPattern(p);
            String name = ccc.withLocale(Locale.getDefault(Locale.Category.FORMAT)).format(now);
            box.getChildren().add(new Label(name));
        });

        BorderPane content = new BorderPane(picker);
        content.setBottom(box);
        return content;
    }

    @Override
    public void start(Stage stage) throws Exception {
        stage.setScene(new Scene(createContent(), 400, 200));
        stage.setTitle(FXUtils.version());
        stage.show();
    }

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

    @SuppressWarnings("unused")
    private static final Logger LOG = Logger
            .getLogger(DatePickerLocale.class.getName());

}