> Markdown version of [Localization](https://vaadin.com/docs/latest/flow/i18n). Section index: [llms.txt](https://vaadin.com/docs/latest/flow/llms.txt)

# Localization

Vaadin has built-in support for localizing applications. This page covers translating strings, letting users pick a language, translating Vaadin component labels, and formatting numbers and dates for different locales.

## <a id="translation-strings"></a>Translation Strings

Call `getTranslation()` from any Vaadin component to look up a translation key for the current locale. Parameters are substituted into `{0}`, `{1}`, etc. placeholders using `MessageFormat`:

```java
Span title = new Span(getTranslation("app.title"));
Span greeting = new Span(getTranslation("greeting", user.getName()));
```

### <a id="where-translations-are-stored"></a>Where Translations Are Stored

By default, translations are loaded from property files in `src/main/resources/vaadin-i18n/`, with the filename prefix `translations`:

- `translations.properties` — default translations (fallback)

- `translations_en.properties` — English

- `translations_es.properties` — Spanish

- `translations_de.properties` — German

The filename supports up to three parts: `translations_language_country_variant.properties` (e.g., `translations_en_US.properties`).

translations.properties

```properties
app.title=My Application
greeting=Hello, {0}!
items.count=You have {0} items.
```

translations\_es.properties

```properties
app.title=Mi Aplicación
greeting=¡Hola, {0}!
items.count=Tienes {0} artículos.
```

> **Note:** Translation files are discovered automatically at runtime. You don’t need to register them — just place them in `src/main/resources/vaadin-i18n/`.

To load translations from another source — such as a database, a remote service, or a different file structure — implement a custom `I18NProvider`. See [Custom I18N Provider](https://vaadin.com/docs/latest/flow/i18n/custom-i18n-provider.md) for details.

### <a id="reacting-to-locale-changes"></a>Reacting to Locale Changes

Implement `LocaleChangeObserver` to update translations when the locale changes. The observer is called on navigation when the component is attached, but before `onAttach()`:

```java
public class MainView extends VerticalLayout implements LocaleChangeObserver {

    private final Span title = new Span();
    private final Span greeting = new Span();

    public MainView() {
        add(title, greeting);
    }

    @Override
    public void localeChange(LocaleChangeEvent event) {
        title.setText(getTranslation("app.title"));
        greeting.setText(getTranslation("greeting", "World"));
    }
}
```

Signals offer a reactive alternative (since V25.2): `UI` and `VaadinSession` expose the current locale as a read-only signal through `localeSignal()`. Reading it with `get()` inside `Signal.effect()` re-runs the effect on every locale change, which fits the signal-based UI style and avoids implementing `LocaleChangeObserver`:

```java
Signal.effect(this, () -> {
    UI.getCurrent().localeSignal().get(); // (1)
    title.setText(getTranslation("app.title"));
    greeting.setText(getTranslation("greeting", "World"));
});
```

1. Reading the signal registers it as a dependency, so the effect re-runs whenever the locale changes. Use `getLocale()` instead for a non-reactive read.

> **Tip:** Use Copilot to automate extraction of static strings into translation files. See [Internationalization in Copilot](https://vaadin.com/docs/latest/tools/copilot/i18n.md).

## <a id="locale-selection"></a>Language Selection

### <a id="automatic-locale-from-browser"></a>Automatic Locale from Browser

When a session starts, Vaadin picks an initial locale by matching the browser’s `Accept-Language` header against the locales reported by the active `I18NProvider` via `getProvidedLocales()`:

1. If an exact match (language + country) is found, it’s used.

2. Otherwise, a language-only match is tried.

3. If neither matches, `I18NProvider.getDefaultLocale()` is used, which by default is the first locale in `getProvidedLocales()`.

4. If no locales are provided at all, `Locale.getDefault()` is used.

With the built-in `DefaultI18NProvider`, the provided locales are discovered automatically from the `translations_*.properties` filenames in `src/main/resources/vaadin-i18n/` — there’s no separate list to declare. With a custom `I18NProvider`, you control both lists by implementing `getProvidedLocales()` and optionally `getDefaultLocale()`.

### <a id="changing-the-locale-programmatically"></a>Changing the Locale Programmatically

Set the locale for the current session with:

```java
UI.getCurrent().setLocale(new Locale("es"));
```

This triggers a `LocaleChangeEvent` on all attached components that implement `LocaleChangeObserver`.

### <a id="language-switcher-example"></a>Language Switcher Example

A language switcher lets users choose their preferred language:

```java
public class LanguageSwitcher extends Select<Locale> {

    public LanguageSwitcher() {
        setItems(
            Locale.ENGLISH,
            Locale.FRENCH,
            Locale.GERMAN,
            new Locale("es")
        );
        setItemLabelGenerator(locale -> locale.getDisplayLanguage(locale));
        setValue(UI.getCurrent().getLocale());

        addValueChangeListener(event ->
            UI.getCurrent().setLocale(event.getValue())
        );
    }
}
```

### <a id="binding-to-the-locale-signal"></a>Binding to the Locale Signal (since V25.2)

The current locale is also available as a read-only signal through `UI.localeSignal()`. Rather than seeding the field’s value and adding a value-change listener, you can two-way bind the switcher to the signal with `bindValue()`, passing `setLocale` as the write callback:

```java
public class LanguageSwitcher extends Select<Locale> {

    public LanguageSwitcher() {
        setItems(Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN, new Locale("es"));
        setItemLabelGenerator(locale -> locale.getDisplayLanguage(locale));

        UI ui = UI.getCurrent();
        bindValue(ui.localeSignal(), ui::setLocale);
    }
}
```

The field shows the current locale, user selections call `setLocale()`, and any programmatic `setLocale()` elsewhere updates the field automatically. Bind to `VaadinSession.getCurrent().localeSignal()` instead to follow the session locale, which applies to every UI in the session.

The signal is read-only, so always change the locale through `setLocale()` — this keeps locale propagation and `LocaleChangeObserver` notifications working.

## <a id="translating-vaadin-component-strings"></a>Translating Vaadin Component Strings

Many Vaadin components have built-in strings for labels, error messages, and accessibility text (e.g., Upload, Login, Date Picker). These are localized using a component-specific i18n object passed to `setI18n()`.

Each component defines its own i18n class. Create an instance, set the translated strings, and apply it:

```java
Upload upload = new Upload();

UploadI18N i18n = new UploadI18N();
i18n.setAddFiles(new UploadI18N.AddFiles()
        .setOne(getTranslation("upload.add.file"))
        .setMany(getTranslation("upload.add.files")));
i18n.setDropFiles(new UploadI18N.DropFiles()
        .setOne(getTranslation("upload.drop.file"))
        .setMany(getTranslation("upload.drop.files")));
i18n.setError(new UploadI18N.Error()
        .setFileIsTooBig(getTranslation("upload.error.file.too.big")));
upload.setI18n(i18n);
```

To update component i18n when the locale changes, do it inside `localeChange()`:

```java
@Override
public void localeChange(LocaleChangeEvent event) {
    UploadI18N i18n = new UploadI18N();
    i18n.setAddFiles(new UploadI18N.AddFiles()
            .setOne(getTranslation("upload.add.file")));
    // ... set other strings
    upload.setI18n(i18n);
}
```

See the documentation for each component for its specific i18n properties and keys.

## <a id="formatting-numbers-dates"></a>Formatting Numbers and Dates

Java provides comprehensive locale-aware formatting for numbers, currency, and dates.

### <a id="number-formatting"></a>Number Formatting

Use `NumberFormat` to format numbers according to locale conventions (decimal separators, grouping, etc.):

```java
Locale locale = UI.getCurrent().getLocale();

NumberFormat numberFormat = NumberFormat.getInstance(locale);
String formatted = numberFormat.format(1234567.89);
// en_US: "1,234,567.89"
// de_DE: "1.234.567,89"
// es_ES: "1.234.567,89"
```

### <a id="currency-formatting"></a>Currency Formatting

```java
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);
String price = currencyFormat.format(49.99);
// en_US: "$49.99"
// de_DE: "49,99 €"
// ja_JP: "￥50"
```

For a specific currency regardless of locale:

```java
NumberFormat currencyFormat = NumberFormat.getCurrencyInstance(locale);
currencyFormat.setCurrency(Currency.getInstance("EUR"));
String price = currencyFormat.format(49.99);
// en_US: "€49.99"
// de_DE: "49,99 €"
```

### <a id="custom-number-patterns"></a>Custom Number Patterns

Use `DecimalFormat` for custom patterns:

```java
DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(locale);
DecimalFormat format = new DecimalFormat("#,##0.00", symbols);
String result = format.format(1234.5);
// Uses locale-specific grouping and decimal separators
```

### <a id="date-and-time-formatting"></a>Date and Time Formatting

Use `DateTimeFormatter` with locale for localized date and time output:

```java
LocalDate date = LocalDate.now();
Locale locale = UI.getCurrent().getLocale();

// Localized styles
DateTimeFormatter shortDate = DateTimeFormatter.ofLocalizedDate(FormatStyle.SHORT)
    .withLocale(locale);
DateTimeFormatter longDate = DateTimeFormatter.ofLocalizedDate(FormatStyle.LONG)
    .withLocale(locale);

String shortFormatted = date.format(shortDate);
// en_US: "3/10/26"
// de_DE: "10.03.26"

String longFormatted = date.format(longDate);
// en_US: "March 10, 2026"
// de_DE: "10. März 2026"
```

For date-time values:

```java
LocalDateTime dateTime = LocalDateTime.now();

DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)
    .withLocale(locale);
String formatted = dateTime.format(formatter);
```

### <a id="custom-date-patterns"></a>Custom Date Patterns

```java
DateTimeFormatter custom = DateTimeFormatter.ofPattern("dd MMM yyyy", locale);
String formatted = LocalDate.now().format(custom);
// en_US: "10 Mar 2026"
// de_DE: "10 Mär 2026"
```

<!-- vale Vaadin.ProductName = NO -->

### <a id="vaadin-components-and-formatting"></a>Vaadin Components and Formatting

<!-- vale Vaadin.ProductName = YES -->

Some Vaadin components, such as Date Picker and Date Time Picker, handle their own date format localization through their i18n objects. You don’t typically need to format dates manually for those components — configure them through `setI18n()` and the component’s locale. See the component documentation for details.

## <a id="right-to-left-languages"></a>Right-to-Left Languages

Vaadin components support right-to-left (RTL) text direction out of the box. To enable it, set the UI direction based on the locale:

```java
UI ui = UI.getCurrentOrThrow();
if ("ar".equals(ui.getLocale().getLanguage())) {
    ui.setDirection(Direction.RIGHT_TO_LEFT);
} else {
    ui.setDirection(Direction.LEFT_TO_RIGHT);
}
```

For reactive locale switching, set the direction inside `localeChange()`:

```java
@Override
public void localeChange(LocaleChangeEvent event) {
    if ("ar".equals(event.getLocale().getLanguage())) {
        event.getUI().setDirection(Direction.RIGHT_TO_LEFT);
    } else {
        event.getUI().setDirection(Direction.LEFT_TO_RIGHT);
    }
}
```

For details on adding RTL support to custom elements and custom styles, see [RTL Support for Custom Elements](https://vaadin.com/docs/latest/flow/i18n/custom-element-rtl.md).

`722E7AE4-191E-4DE8-90F1-CAE8AE6CD3DF`
