From 2e89875b80572aa618a49a5f91cd4326e49792c0 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 10 Mar 2026 10:59:06 +0000 Subject: [PATCH 1/6] docs: restructure i18n docs into top-level Flow section Move localization documentation from a single page under Advanced Topics to a new top-level Flow Reference section with practical guidance on translation strings, language selection, component i18n, and number/date formatting. Advanced content (custom I18NProvider, RTL for custom elements) moves to a sub-page. Update all cross-references. --- .../forms-data/create-custom-field/index.adoc | 2 +- .../components/date-picker/date-formats.adoc | 2 +- articles/flow/advanced/i18n-localization.adoc | 282 ---------------- articles/flow/configuration/properties.adoc | 2 +- articles/flow/i18n/custom-i18n-provider.adoc | 212 ++++++++++++ articles/flow/i18n/index.adoc | 314 ++++++++++++++++++ articles/tools/copilot/i18n.adoc | 2 +- 7 files changed, 530 insertions(+), 286 deletions(-) delete mode 100644 articles/flow/advanced/i18n-localization.adoc create mode 100644 articles/flow/i18n/custom-i18n-provider.adoc create mode 100644 articles/flow/i18n/index.adoc diff --git a/articles/building-apps/forms-data/create-custom-field/index.adoc b/articles/building-apps/forms-data/create-custom-field/index.adoc index ce6f57ceb4..bd1932bbcc 100644 --- a/articles/building-apps/forms-data/create-custom-field/index.adoc +++ b/articles/building-apps/forms-data/create-custom-field/index.adoc @@ -652,7 +652,7 @@ Always call `updateI18n()` whenever texts should be refreshed, for example, insi === Further Reading -For more details on localization in Vaadin, see the <<{articles}/flow/advanced/i18n-localization#,Localization>> article. +For more details on localization in Vaadin, see the <<{articles}/flow/i18n#,Localization>> article. == Accessibility diff --git a/articles/components/date-picker/date-formats.adoc b/articles/components/date-picker/date-formats.adoc index cbf1d3d4a9..12e69e0e77 100644 --- a/articles/components/date-picker/date-formats.adoc +++ b/articles/components/date-picker/date-formats.adoc @@ -17,7 +17,7 @@ Two-digit year formats are supported, and you can set the <>). Setting a specific locale ensures that all users see the same format. +By default, Date Picker displays and parses dates using the user's locale (<<{articles}/flow/i18n#locale-selection,reference>>). Setting a specific locale ensures that all users see the same format. In the example here, the Date Picker is set to the date format used in Finland: diff --git a/articles/flow/advanced/i18n-localization.adoc b/articles/flow/advanced/i18n-localization.adoc deleted file mode 100644 index 01528ac5fa..0000000000 --- a/articles/flow/advanced/i18n-localization.adoc +++ /dev/null @@ -1,282 +0,0 @@ ---- -title: Localization -page-title: How to localize Vaadin apps with i18n -description: Implementing localization and translation strings using I18NProvider. -meta-description: Set up internationalization in Vaadin for multilingual support. -order: 20 ---- - - -= Localization - -To use localization and translation strings, the application needs only to have the translation properties available on the classpath under the directory, `vaadin-i18n` with the filename prefix, `translations` (e.g., `src/main/resources/vaadin-i18n/translations.properties`). - -When using localization in an application, calling for a translation, or when the I18NProvider is used for the first time, the folder resource, `vaadin-i18n` is checked if it contains any `translations.properties` or `translations_[langcode].properties` files. Any language codes are collected from the available property files and added as provided locales in the [classname]`DefaultI18NProvider`. - -The file `translations.properties` is a default translation file that'll be used for any [classname]`Locale` that doesn't have a specific translations file. Locale translation files are named, for example, like [filename]`translations_fi_FI.properties` or [filename]`translations_de.properties`. The automatic [classname]`Locale` creation supports from one to three parts (e.g., `translations_language_country_variant.properties`). - -[NOTE] -[classname]`DefaultI18NProvider` is available as of version 24.3 of Vaadin Flow. For an earlier version, you'll need to implement your own [interfacename]`I18NProvider`, as documented in <<#defining-i18n-provider-property,Defining I18n Provider Property>> section. - - -== Locale Selection for New Session - -The initial locale is determined by matching the locales provided by the [classname]`I18NProvider` against the `Accept-Language` header in the initial response from the client. - -If an exact match (i.e., language and country) is found, it'll be used. Otherwise, it tries to match only on language. If neither is found, the locale is set to the first 'supported' locale from [methodname]`I18NProvider.getProvidedLocales()`. If that's empty, [methodname]`Locale.getDefault()` is used. - - -=== Using Localization in Application - -Implementing internationalization in an application is a combination of using `I18NProvider` and updating the translations on locale change. - -To make this simple, the application classes that control the captions and texts that are localized, can implement [interfacename]`LocaleChangeObserver` to receive events related to locale change. This observer is also notified on navigation when the component is attached, but before [methodname]`onAttach()` is called. Any URL parameters from the navigation are set so that they can be used to determine the state. - -[source,java] ----- -public class LocaleObserver extends Div implements LocaleChangeObserver { - - @Override - public void localeChange(LocaleChangeEvent event) { - setText(getTranslation("my.translation", getUserId())); - } -} ----- - - -==== Using Localization without LocaleChangeObserver - -.I18NProvider without [interfacename]`LocaleChangeObserver` -[source,java] ----- -public class MyLocale extends Div { - - public MyLocale() { - setText(getTranslation("my.translation", getUserId())); - } -} ----- - - -== Defining I18n Provider Property - -To use a custom I18N provider with more features than available by the default one, the application needs only to implement [interfacename]`I18NProvider` and define the fully qualified class name in the property, `i18n.provider`. - -[NOTE] -For a Spring project, the property is not needed if the custom provider is a Bean of type [interfacename]`I18NProvider`. - -The `i18n.provider` property can be set from the command-line as a system property, as a Servlet initial parameter in the [filename]`web.xml` file, or using the `@WebServlet` annotation. - -As a system property, the parameter needs the `vaadin` prefix like this: - -[source,terminal] ----- -mvn jetty:run -Dvaadin.i18n.provider=com.vaadin.example.ui.TranslationProvider ----- - -When using the annotation, you could have the servlet class as something such as this: - -[source,java] ----- -@WebServlet(urlPatterns = "/*", name = "slot", asyncSupported = true, loadOnStartup = 1, - initParams = { @WebInitParam(name = "i18n.provider", value = "com.vaadin.example.ui.TranslationProvider") }) -public class ApplicationServlet extends VaadinServlet { -} ----- - -Or, if you prefer to use the [filename]`web.xml` file, you might set it as this: - -[source,xml] ----- - - - - - myservlet - - com.vaadin.flow.server.VaadinServlet - - 1 - - - i18n.provider - com.vaadin.example.ui.TranslationProvider - - - - - myservlet - /* - - ----- - -You can provide an [interfacename]`I18NProvider` as a bean if you're using Spring. In that case, you need only annotate your implementation with `@Component`, so that it's available as a Spring bean. The Spring add-on uses it if it's available. See the class https://github.com/vaadin/flow-spring-tutorial/blob/master/src/main/java/org/vaadin/spring/tutorial/SimpleI18NProvider.java[`SimpleI18NProvider.java`] implemented in the tutorial project as an example. - - -[[provider-sample-for-translation]] -== Using I18NProvider for Translation - -For this example, the use of Finnish and English is enabled, with Finnish being the default that's used if the user client doesn't specify English as an accepted language. The language [filename]`.properties` files start with "translate": for example, [filename]`translate.properties` for the default, as well as [filename]`translate_fi_FI.properties`, and [filename]`translate_en_GB.properties`. - -Here the translation properties files are loaded using the class loader. Hence, they should be located on the classpath, for example in the resources folder. For a default Maven setup, this would be `src/main/resources`. - -.I18NProvider Implementation Example -[source,java] ----- -public class TranslationProvider implements I18NProvider { - - public static final String BUNDLE_PREFIX = "translate"; - - public final Locale LOCALE_FI = new Locale("fi", "FI"); - public final Locale LOCALE_EN = new Locale("en", "GB"); - - private List locales = Collections - .unmodifiableList(Arrays.asList(LOCALE_FI, LOCALE_EN)); - - @Override - public List getProvidedLocales() { - return locales; - } - - @Override - public String getTranslation(String key, Locale locale, Object... params) { - if (key == null) { - LoggerFactory.getLogger(TranslationProvider.class.getName()) - .warn("Got lang request for key with null value!"); - return ""; - } - - final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_PREFIX, locale); - - String value; - try { - value = bundle.getString(key); - } catch (final MissingResourceException e) { - LoggerFactory.getLogger(TranslationProvider.class.getName()) - .warn("Missing resource", e); - return "!" + locale.getLanguage() + ": " + key; - } - if (params.length > 0) { - value = MessageFormat.format(value, params); - } - return value; - } -} ----- - - -== Supporting Right-to-Left Mode - -Vaadin components have support for right-to-left languages. The components work out-of-the-box in this mode. However, to allow your application to support both left-to-right and right-to-left modes, you'll need to make a few changes. - -Continuing from the previous examples, suppose your application now has also been translated into a right-to-left Language, such as Arabic. As well as <>, in your main layout you can add code such as the following: - -[source,java] ----- -public class MainLayout extends VerticalLayout { - - public MainLayout() { - // ... - final UI ui = UI.getCurrent(); - if (ui.getLocale().getLanguage() == "ar") { - ui.setDirection(Direction.RIGHT_TO_LEFT); - } - } -} ----- - -This works if the change of locale is based only on the `Accept-Language` coming from the client. However, if the user can specify their language, for instance, on your application's settings page, you can have your main layout implement the [interfacename]`LocaleChangeObserver` interface. In this way, it receives changes of locale, so you can then set the text direction based on the specified locale: - -[source,java] ----- -public class MainLayout extends VerticalLayout implements LocaleChangeObserver { - - @Override - public void localeChange(LocaleChangeEvent event) { - if (event.getLocale().getLanguage() == "ar") { - event.getUI().setDirection(Direction.RIGHT_TO_LEFT); - } else { - event.getUI().setDirection(Direction.LEFT_TO_RIGHT); - } - } -} ----- - - -== Frontend Projects - -For frontend applications only, to set right-to-left mode, you can specify, `document.dir = 'rtl'`. - - -== Adding Right-to-Left Support - -If you have custom elements, or if your application has custom styles, there are a few steps needed to add right-to-left support to them. - -First, if your element extends Vaadin's [classname]`ElementMixin`, no changes are needed. Otherwise, you can have the element extend it or [classname]`DirMixin` only (i.e., [classname]`DirMixin` is part of the `@vaadin/component-base` package). - -[source,javascript] ----- -import { PolymerElement } from '@polymer/polymer/polymer-element.js'; -import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; - -class MyElement extends DirMixin(PolymerElement) {} ----- - -The [classname]`DirMixin` registers the element to respond to changes in the `dir` attribute at the document level and keeps it synchronized with the element's `dir` attribute. This is helpful to adjust to the text-direction status in both CSS and JS code. - -Second, make sure your styles are adjusted for right-to-left mode. For example, if you define values for the padding on the `:host`, as follows: - -[source,css] ----- -:host { - padding-right: 1em; - padding-left: 2em; -} ----- - -You may want to define the style for right-to-left, as follows: - -[source,css] ----- -:host([dir="rtl"]) { - padding-right: 2em; - padding-left: 1em; -} ----- - -Third, you should also review settings such as `padding`, `margin`, `text-align`, `float` and `transform` in your styles. If your custom element doesn't need to support old browsers, you can replace some properties with *CSS Logical Properties*. The https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties[MDN web documentation] has a full list of CSS Logical Properties and their available values, along with browser support for each property. Flex and Grid containers are usually handled well by the browser and don't require anything extra. You can find more information in this https://rtlstyling.com/posts/rtl-styling/[comprehensive right-to-left styling guide]. - -For help with adjusting styles for right-to-left mode, you can use the tools available on the https://rtlcss.com/playground/#[RTLCSS] page. There, you can paste original styles and it'll generate code that you can use for your element. - -If your element uses icons or Unicode symbols to define direction (e.g., for a _Back_ button), you may need to use the right icons or symbols for right-to-left mode. - -If keyboard interactions are used -- for example, to navigate between items with arrow keys -- define the direction of the movement based on the `dir` attribute like so: - -[source,javascript] ----- -// somewhere in your code -const dirIncrement = this.getAttribute('dir') === 'rtl' ? -1 : 1; - -switch (event.key) { - // ... - case 'ArrowLeft': - idx = currentIdx - dirIncrement; - break; - case 'ArrowRight': - idx = currentIdx + dirIncrement; - break; - // ... -} ----- - -Custom elements that rely on JavaScript calculations for sizing, position, or horizontal scroll, may need some adjustments for right-to-left. - -If you have visual tests, you may want to add or update the current ones to run also in right-to-left mode. - -[discussion-id]`722E7AE4-191E-4DE8-90F1-CAE8AE6CD3DF` diff --git a/articles/flow/configuration/properties.adoc b/articles/flow/configuration/properties.adoc index 4fff349ffd..3f89558e58 100644 --- a/articles/flow/configuration/properties.adoc +++ b/articles/flow/configuration/properties.adoc @@ -228,7 +228,7 @@ Default: 300 seconds (i.e., 5 minutes) + Mode: Runtime `**i18n.provider**`:: -Sets the fully-qualified name for the internationalization provider class. To translate strings for localization, the application should implement the `I18NProvider` interface and define the class name in the `i18n.provider` property. See the <<{articles}/flow/advanced/i18n-localization#, Localization documentation>> for details. + +Sets the fully-qualified name for the internationalization provider class. To translate strings for localization, the application should implement the `I18NProvider` interface and define the class name in the `i18n.provider` property. See the <<{articles}/flow/i18n#, Localization documentation>> for details. + Default: `null` + Mode: Runtime diff --git a/articles/flow/i18n/custom-i18n-provider.adoc b/articles/flow/i18n/custom-i18n-provider.adoc new file mode 100644 index 0000000000..1a6244b90c --- /dev/null +++ b/articles/flow/i18n/custom-i18n-provider.adoc @@ -0,0 +1,212 @@ +--- +title: Custom I18N Provider +page-title: Custom I18N Provider for Vaadin - Vaadin Docs +description: Implementing a custom I18NProvider for advanced localization needs. +meta-description: Learn how to implement a custom I18NProvider in Vaadin for loading translations from databases, using custom file structures, or other advanced i18n needs. +order: 10 +--- + + += Custom I18N Provider + +The built-in `DefaultI18NProvider` loads translations from property files in `src/main/resources/vaadin-i18n/`. If you need more control -- loading translations from a database, using a different file structure, or applying custom fallback logic -- implement the `I18NProvider` interface. + + +== Implementing I18NProvider + +The following example enables Finnish and English, with Finnish as the default. Translation files use the `translate` prefix (e.g., `translate.properties`, `translate_fi_FI.properties`, `translate_en_GB.properties`) and are loaded from the classpath (e.g., `src/main/resources/`): + +[source,java] +---- +public class TranslationProvider implements I18NProvider { + + public static final String BUNDLE_PREFIX = "translate"; + + public final Locale LOCALE_FI = new Locale("fi", "FI"); + public final Locale LOCALE_EN = new Locale("en", "GB"); + + private List locales = Collections + .unmodifiableList(Arrays.asList(LOCALE_FI, LOCALE_EN)); + + @Override + public List getProvidedLocales() { + return locales; + } + + @Override + public String getTranslation(String key, Locale locale, Object... params) { + if (key == null) { + LoggerFactory.getLogger(TranslationProvider.class.getName()) + .warn("Got lang request for key with null value!"); + return ""; + } + + final ResourceBundle bundle = ResourceBundle.getBundle(BUNDLE_PREFIX, locale); + + String value; + try { + value = bundle.getString(key); + } catch (final MissingResourceException e) { + LoggerFactory.getLogger(TranslationProvider.class.getName()) + .warn("Missing resource", e); + return "!" + locale.getLanguage() + ": " + key; + } + if (params.length > 0) { + value = MessageFormat.format(value, params); + } + return value; + } +} +---- + + +== Configuring the Provider + +The `i18n.provider` property must be set to the fully qualified class name of your provider. There are several ways to configure it. + +=== System Property + +[source,terminal] +---- +mvn jetty:run -Dvaadin.i18n.provider=com.vaadin.example.ui.TranslationProvider +---- + +=== @WebServlet Annotation + +[source,java] +---- +@WebServlet(urlPatterns = "/*", name = "slot", asyncSupported = true, loadOnStartup = 1, + initParams = { @WebInitParam(name = "i18n.provider", value = "com.vaadin.example.ui.TranslationProvider") }) +public class ApplicationServlet extends VaadinServlet { +} +---- + +=== web.xml + +[source,xml] +---- + + + + + myservlet + + com.vaadin.flow.server.VaadinServlet + + 1 + + + i18n.provider + com.vaadin.example.ui.TranslationProvider + + + + + myservlet + /* + + +---- + +=== Spring Bean + +For Spring projects, annotate your provider with `@Component` and it's automatically detected -- no property configuration needed: + +[source,java] +---- +@Component +public class TranslationProvider implements I18NProvider { + // ... +} +---- + +[NOTE] +CDI-based projects can use a similar approach with CDI beans. + + +[[adding-rtl-support]] +== Adding Right-to-Left Support to Custom Elements + +If you have custom elements or custom styles, there are additional steps to enable right-to-left (RTL) support. + + +=== DirMixin for Custom Elements + +If your element extends Vaadin's `ElementMixin`, no changes are needed. Otherwise, have the element extend `DirMixin` (from `@vaadin/component-base`): + +[source,javascript] +---- +import { PolymerElement } from '@polymer/polymer/polymer-element.js'; +import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; + +class MyElement extends DirMixin(PolymerElement) {} +---- + +`DirMixin` synchronizes the element's `dir` attribute with the document-level `dir` attribute, allowing CSS and JS code to respond to text direction changes. + + +=== Adjusting Styles for RTL + +Review properties like `padding`, `margin`, `text-align`, `float`, and `transform`. For example, if your styles define directional padding: + +[source,css] +---- +:host { + padding-right: 1em; + padding-left: 2em; +} +---- + +Add an RTL override: + +[source,css] +---- +:host([dir="rtl"]) { + padding-right: 2em; + padding-left: 1em; +} +---- + +You can replace directional properties with https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties[CSS Logical Properties]. Flex and Grid containers are handled well by the browser and typically don't require adjustments. + +For help adjusting styles, you can use the tools at https://rtlcss.com/playground/#[RTLCSS]. See also this https://rtlstyling.com/posts/rtl-styling/[comprehensive right-to-left styling guide]. + + +=== Icons and Directional Symbols + +If your element uses icons or Unicode symbols that indicate direction (e.g., a _Back_ button arrow), use the appropriate icons for RTL mode. + + +=== Keyboard Navigation + +If keyboard interactions use arrow keys for navigation, adjust the direction based on the `dir` attribute: + +[source,javascript] +---- +const dirIncrement = this.getAttribute('dir') === 'rtl' ? -1 : 1; + +switch (event.key) { + // ... + case 'ArrowLeft': + idx = currentIdx - dirIncrement; + break; + case 'ArrowRight': + idx = currentIdx + dirIncrement; + break; + // ... +} +---- + +Custom elements that rely on JavaScript calculations for sizing, position, or horizontal scroll may also need adjustments for RTL. + +If you have visual tests, consider adding or updating them to run in RTL mode as well. + + +=== Frontend-Only RTL + +For frontend-only applications, set `document.dir = 'rtl'` to enable right-to-left mode. diff --git a/articles/flow/i18n/index.adoc b/articles/flow/i18n/index.adoc new file mode 100644 index 0000000000..7d1c715cc1 --- /dev/null +++ b/articles/flow/i18n/index.adoc @@ -0,0 +1,314 @@ +--- +title: Localization +page-title: How to Localize Vaadin Applications - Vaadin Docs +description: Translate strings, switch languages, localize numbers and dates. +meta-description: Learn how to internationalize and localize Vaadin applications with translation strings, locale selection, component i18n, and number and date formatting. +order: 75 +--- + + += 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. + + +== Translation Strings + +Store translation properties files in `src/main/resources/vaadin-i18n/` with the filename prefix `translations`: + +- `translations.properties` -- default translations (fallback) +- `translations_en.properties` -- English +- `translations_fi.properties` -- Finnish +- `translations_de.properties` -- German + +The filename supports up to three parts: `translations_language_country_variant.properties` (e.g., `translations_en_US.properties`). + +.translations.properties +[source,properties] +---- +app.title=My Application +greeting=Hello, {0}! +items.count=You have {0} items. +---- + +.translations_fi.properties +[source,properties] +---- +app.title=Sovellukseni +greeting=Hei, {0}! +items.count=Sinulla on {0} kohdetta. +---- + +[NOTE] +Translation files are discovered automatically at runtime. You don't need to register them -- just place them in `src/main/resources/vaadin-i18n/`. + + +=== Using Translations in Views + +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`: + +[source,java] +---- +Span title = new Span(getTranslation("app.title")); +Span greeting = new Span(getTranslation("greeting", user.getName())); +---- + +=== 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()`: + +[source,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")); + } +} +---- + +[TIP] +Use Copilot to automate extraction of static strings into translation files. See <<{articles}/tools/copilot/i18n#,Internationalization in Copilot>>. + + +[[locale-selection]] +== Language Selection + +=== Automatic Locale from Browser + +The initial locale is determined by matching the locales in your translation files against the `Accept-Language` header from the browser: + +1. If an exact match (language + country) is found, it's used. +2. Otherwise, a language-only match is tried. +3. If no match, the first provided locale is used. +4. If no locales are provided, `Locale.getDefault()` is used. + + +=== Changing the Locale Programmatically + +Set the locale for the current session with: + +[source,java] +---- +UI.getCurrent().setLocale(new Locale("fi")); +---- + +This triggers a `LocaleChangeEvent` on all attached components that implement `LocaleChangeObserver`. + + +=== Language Switcher Example + +A language switcher lets users choose their preferred language: + +[source,java] +---- +public class LanguageSwitcher extends Select { + + public LanguageSwitcher() { + setItems( + Locale.ENGLISH, + Locale.FRENCH, + Locale.GERMAN, + new Locale("fi") + ); + setItemLabelGenerator(locale -> locale.getDisplayLanguage(locale)); + setValue(UI.getCurrent().getLocale()); + + addValueChangeListener(event -> + UI.getCurrent().setLocale(event.getValue()) + ); + } +} +---- + + +== Translating Vaadin Component Strings + +Many Vaadin components have built-in strings for labels, error messages, and accessibility text (e.g., Upload, Login, DatePicker). 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: + +[source,java] +---- +Upload upload = new Upload(); + +UploadI18N i18n = new UploadI18N(); +i18n.getAddFiles().setOne(getTranslation("upload.add.file")); +i18n.getAddFiles().setMany(getTranslation("upload.add.files")); +i18n.getDropFiles().setOne(getTranslation("upload.drop.file")); +i18n.getDropFiles().setMany(getTranslation("upload.drop.files")); +i18n.getError().setFileIsTooBig(getTranslation("upload.error.file.too.big")); +upload.setI18n(i18n); +---- + +To update component i18n when the locale changes, do it inside `localeChange()`: + +[source,java] +---- +@Override +public void localeChange(LocaleChangeEvent event) { + UploadI18N i18n = new UploadI18N(); + i18n.getAddFiles().setOne(getTranslation("upload.add.file")); + // ... set other strings + upload.setI18n(i18n); +} +---- + +See the documentation for each component for its specific i18n properties and keys. + + +[[formatting-numbers-dates]] +== Formatting Numbers and Dates + +Java provides comprehensive locale-aware formatting for numbers, currency, and dates. + + +=== Number Formatting + +Use `NumberFormat` to format numbers according to locale conventions (decimal separators, grouping, etc.): + +[source,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" +// fi_FI: "1 234 567,89" +---- + + +=== Currency Formatting + +[source,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: + +[source,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 €" +---- + + +=== Custom Number Patterns + +Use `DecimalFormat` for custom patterns: + +[source,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 +---- + + +=== Date and Time Formatting + +Use `DateTimeFormatter` with locale for localized date and time output: + +[source,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: + +[source,java] +---- +LocalDateTime dateTime = LocalDateTime.now(); + +DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM) + .withLocale(locale); +String formatted = dateTime.format(formatter); +---- + + +=== Custom Date Patterns + +[source,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" +---- + + +=== Vaadin Components and Formatting + +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. + + +== 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: + +[source,java] +---- +UI ui = UI.getCurrent(); +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()`: + +[source,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 <>. + + +== Custom I18N Provider + +The default translation file mechanism covers most use cases. If you need more control -- for example, loading translations from a database or using a different file structure -- you can implement a custom `I18NProvider`. See <> for details. + +[discussion-id]`722E7AE4-191E-4DE8-90F1-CAE8AE6CD3DF` diff --git a/articles/tools/copilot/i18n.adoc b/articles/tools/copilot/i18n.adoc index 456f921eb2..45ebbff4c4 100644 --- a/articles/tools/copilot/i18n.adoc +++ b/articles/tools/copilot/i18n.adoc @@ -42,7 +42,7 @@ The panel also provides a button for clearing the currently stored screenshots. == Limitations -The internationalization feature expects translation property files to be stored in the standard Vaadin <<{articles}/flow/advanced/i18n-localization#, translation file folder>>, [filename]`src/main/resources/vaadin-i18n`. +The internationalization feature expects translation property files to be stored in the standard Vaadin <<{articles}/flow/i18n#, translation file folder>>, [filename]`src/main/resources/vaadin-i18n`. The default translation property file is expected to be called [filename]`translations.properties`. Files for other locales are expected to be named according to the [filename]`translations\_\[langcode\].properties` format, such as [filename]`translations\_en\_US.properties` for U.S. English. From 9a41b6725d96eccd8511ae8e848b399659cd2725 Mon Sep 17 00:00:00 2001 From: Jouni Koivuviita Date: Thu, 12 Mar 2026 12:30:18 +0200 Subject: [PATCH 2/6] fix product name capitalization --- articles/flow/i18n/index.adoc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/articles/flow/i18n/index.adoc b/articles/flow/i18n/index.adoc index 7d1c715cc1..13cb8e66b9 100644 --- a/articles/flow/i18n/index.adoc +++ b/articles/flow/i18n/index.adoc @@ -133,7 +133,7 @@ public class LanguageSwitcher extends Select { == Translating Vaadin Component Strings -Many Vaadin components have built-in strings for labels, error messages, and accessibility text (e.g., Upload, Login, DatePicker). These are localized using a component-specific i18n object passed to `setI18n()`. +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: @@ -270,9 +270,12 @@ String formatted = LocalDate.now().format(custom); // de_DE: "10 Mär 2026" ---- +pass:[] === Vaadin Components and Formatting +pass:[] + 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. From 1e0ad8c662be7608ae163948e4cdfb453f1a8ab3 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 7 Apr 2026 12:33:19 +0000 Subject: [PATCH 3/6] fix: use LitElement instead of PolymerElement in DirMixin example --- articles/flow/i18n/custom-i18n-provider.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/articles/flow/i18n/custom-i18n-provider.adoc b/articles/flow/i18n/custom-i18n-provider.adoc index 1a6244b90c..f9912dbf4c 100644 --- a/articles/flow/i18n/custom-i18n-provider.adoc +++ b/articles/flow/i18n/custom-i18n-provider.adoc @@ -141,10 +141,10 @@ If your element extends Vaadin's `ElementMixin`, no changes are needed. Otherwis [source,javascript] ---- -import { PolymerElement } from '@polymer/polymer/polymer-element.js'; +import { LitElement } from 'lit'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; -class MyElement extends DirMixin(PolymerElement) {} +class MyElement extends DirMixin(LitElement) {} ---- `DirMixin` synchronizes the element's `dir` attribute with the document-level `dir` attribute, allowing CSS and JS code to respond to text direction changes. From eec394df01bc1ddf830aecf8557913e6046c7181 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Tue, 7 Apr 2026 12:35:06 +0000 Subject: [PATCH 4/6] fix: add required PolylitMixin to DirMixin example DirMixin uses Polymer-style property definitions internally and requires PolylitMixin as a compatibility layer when used with LitElement. --- articles/flow/i18n/custom-i18n-provider.adoc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/articles/flow/i18n/custom-i18n-provider.adoc b/articles/flow/i18n/custom-i18n-provider.adoc index f9912dbf4c..266a1718c9 100644 --- a/articles/flow/i18n/custom-i18n-provider.adoc +++ b/articles/flow/i18n/custom-i18n-provider.adoc @@ -143,8 +143,9 @@ If your element extends Vaadin's `ElementMixin`, no changes are needed. Otherwis ---- import { LitElement } from 'lit'; import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; -class MyElement extends DirMixin(LitElement) {} +class MyElement extends DirMixin(PolylitMixin(LitElement)) {} ---- `DirMixin` synchronizes the element's `dir` attribute with the document-level `dir` attribute, allowing CSS and JS code to respond to text direction changes. From c6aaded4e075e3923e25af2f6494e1bb62ac63f4 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Fri, 10 Apr 2026 11:47:04 +0000 Subject: [PATCH 5/6] docs: split RTL custom element guidance into its own page Move the Adding Right-to-Left Support to Custom Elements section out of custom-i18n-provider.adoc into a dedicated custom-element-rtl.adoc page, and simplify the I18NProvider configuration section to lead with the Spring Boot bean approach. --- articles/flow/i18n/custom-element-rtl.adoc | 90 ++++++++++++ articles/flow/i18n/custom-i18n-provider.adoc | 139 ++----------------- articles/flow/i18n/index.adoc | 2 +- 3 files changed, 100 insertions(+), 131 deletions(-) create mode 100644 articles/flow/i18n/custom-element-rtl.adoc diff --git a/articles/flow/i18n/custom-element-rtl.adoc b/articles/flow/i18n/custom-element-rtl.adoc new file mode 100644 index 0000000000..f6a785cf55 --- /dev/null +++ b/articles/flow/i18n/custom-element-rtl.adoc @@ -0,0 +1,90 @@ +--- +title: RTL Support for Custom Elements +page-title: RTL Support for Custom Elements - Vaadin Docs +description: Adding right-to-left support to custom web components and styles. +meta-description: Learn how to add right-to-left (RTL) support to custom web components, styles, icons, and keyboard navigation in Vaadin applications. +order: 20 +--- + + += RTL Support for Custom Elements + +Vaadin components support right-to-left (RTL) text direction out of the box. If you have custom elements or custom styles, there are additional steps to enable RTL support. + + +== DirMixin for Custom Elements + +If your element extends Vaadin's `ElementMixin`, no changes are needed. Otherwise, have the element extend `DirMixin` (from `@vaadin/component-base`): + +[source,javascript] +---- +import { LitElement } from 'lit'; +import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; +import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; + +class MyElement extends DirMixin(PolylitMixin(LitElement)) {} +---- + +`DirMixin` synchronizes the element's `dir` attribute with the document-level `dir` attribute, allowing CSS and JS code to respond to text direction changes. + + +== Adjusting Styles for RTL + +Review properties like `padding`, `margin`, `text-align`, `float`, and `transform`. For example, if your styles define directional padding: + +[source,css] +---- +:host { + padding-right: 1em; + padding-left: 2em; +} +---- + +Add an RTL override: + +[source,css] +---- +:host([dir="rtl"]) { + padding-right: 2em; + padding-left: 1em; +} +---- + +You can replace directional properties with https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties[CSS Logical Properties]. Flex and Grid containers are handled well by the browser and typically don't require adjustments. + +For help adjusting styles, you can use the tools at https://rtlcss.com/playground/#[RTLCSS]. See also this https://rtlstyling.com/posts/rtl-styling/[comprehensive right-to-left styling guide]. + + +== Icons and Directional Symbols + +If your element uses icons or Unicode symbols that indicate direction (e.g., a _Back_ button arrow), use the appropriate icons for RTL mode. + + +== Keyboard Navigation + +If keyboard interactions use arrow keys for navigation, adjust the direction based on the `dir` attribute: + +[source,javascript] +---- +const dirIncrement = this.getAttribute('dir') === 'rtl' ? -1 : 1; + +switch (event.key) { + // ... + case 'ArrowLeft': + idx = currentIdx - dirIncrement; + break; + case 'ArrowRight': + idx = currentIdx + dirIncrement; + break; + // ... +} +---- + +Custom elements that rely on JavaScript calculations for sizing, position, or horizontal scroll may also need adjustments for RTL. + +If you have visual tests, consider adding or updating them to run in RTL mode as well. + + +== Frontend-Only RTL + +For frontend-only applications, set `document.dir = 'rtl'` to enable right-to-left mode. diff --git a/articles/flow/i18n/custom-i18n-provider.adoc b/articles/flow/i18n/custom-i18n-provider.adoc index 266a1718c9..6907c6fc4e 100644 --- a/articles/flow/i18n/custom-i18n-provider.adoc +++ b/articles/flow/i18n/custom-i18n-provider.adoc @@ -62,60 +62,7 @@ public class TranslationProvider implements I18NProvider { == Configuring the Provider -The `i18n.provider` property must be set to the fully qualified class name of your provider. There are several ways to configure it. - -=== System Property - -[source,terminal] ----- -mvn jetty:run -Dvaadin.i18n.provider=com.vaadin.example.ui.TranslationProvider ----- - -=== @WebServlet Annotation - -[source,java] ----- -@WebServlet(urlPatterns = "/*", name = "slot", asyncSupported = true, loadOnStartup = 1, - initParams = { @WebInitParam(name = "i18n.provider", value = "com.vaadin.example.ui.TranslationProvider") }) -public class ApplicationServlet extends VaadinServlet { -} ----- - -=== web.xml - -[source,xml] ----- - - - - - myservlet - - com.vaadin.flow.server.VaadinServlet - - 1 - - - i18n.provider - com.vaadin.example.ui.TranslationProvider - - - - - myservlet - /* - - ----- - -=== Spring Bean - -For Spring projects, annotate your provider with `@Component` and it's automatically detected -- no property configuration needed: +For Spring Boot projects, annotate your provider with `@Component` and it's automatically detected -- no further configuration needed: [source,java] ---- @@ -125,89 +72,21 @@ public class TranslationProvider implements I18NProvider { } ---- -[NOTE] CDI-based projects can use a similar approach with CDI beans. +For other setups, set the `i18n.provider` property to the fully qualified class name of your provider, either as a system property: -[[adding-rtl-support]] -== Adding Right-to-Left Support to Custom Elements - -If you have custom elements or custom styles, there are additional steps to enable right-to-left (RTL) support. - - -=== DirMixin for Custom Elements - -If your element extends Vaadin's `ElementMixin`, no changes are needed. Otherwise, have the element extend `DirMixin` (from `@vaadin/component-base`): - -[source,javascript] ----- -import { LitElement } from 'lit'; -import { DirMixin } from '@vaadin/component-base/src/dir-mixin.js'; -import { PolylitMixin } from '@vaadin/component-base/src/polylit-mixin.js'; - -class MyElement extends DirMixin(PolylitMixin(LitElement)) {} ----- - -`DirMixin` synchronizes the element's `dir` attribute with the document-level `dir` attribute, allowing CSS and JS code to respond to text direction changes. - - -=== Adjusting Styles for RTL - -Review properties like `padding`, `margin`, `text-align`, `float`, and `transform`. For example, if your styles define directional padding: - -[source,css] ----- -:host { - padding-right: 1em; - padding-left: 2em; -} ----- - -Add an RTL override: - -[source,css] +[source,terminal] ---- -:host([dir="rtl"]) { - padding-right: 2em; - padding-left: 1em; -} +mvn jetty:run -Dvaadin.i18n.provider=com.vaadin.example.ui.TranslationProvider ---- -You can replace directional properties with https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Logical_Properties[CSS Logical Properties]. Flex and Grid containers are handled well by the browser and typically don't require adjustments. - -For help adjusting styles, you can use the tools at https://rtlcss.com/playground/#[RTLCSS]. See also this https://rtlstyling.com/posts/rtl-styling/[comprehensive right-to-left styling guide]. - - -=== Icons and Directional Symbols - -If your element uses icons or Unicode symbols that indicate direction (e.g., a _Back_ button arrow), use the appropriate icons for RTL mode. +Or via the `@WebServlet` annotation: - -=== Keyboard Navigation - -If keyboard interactions use arrow keys for navigation, adjust the direction based on the `dir` attribute: - -[source,javascript] +[source,java] ---- -const dirIncrement = this.getAttribute('dir') === 'rtl' ? -1 : 1; - -switch (event.key) { - // ... - case 'ArrowLeft': - idx = currentIdx - dirIncrement; - break; - case 'ArrowRight': - idx = currentIdx + dirIncrement; - break; - // ... +@WebServlet(urlPatterns = "/*", name = "slot", asyncSupported = true, loadOnStartup = 1, + initParams = { @WebInitParam(name = "i18n.provider", value = "com.vaadin.example.ui.TranslationProvider") }) +public class ApplicationServlet extends VaadinServlet { } ---- - -Custom elements that rely on JavaScript calculations for sizing, position, or horizontal scroll may also need adjustments for RTL. - -If you have visual tests, consider adding or updating them to run in RTL mode as well. - - -=== Frontend-Only RTL - -For frontend-only applications, set `document.dir = 'rtl'` to enable right-to-left mode. diff --git a/articles/flow/i18n/index.adoc b/articles/flow/i18n/index.adoc index 13cb8e66b9..9c0f427d8f 100644 --- a/articles/flow/i18n/index.adoc +++ b/articles/flow/i18n/index.adoc @@ -307,7 +307,7 @@ public void localeChange(LocaleChangeEvent event) { } ---- -For details on adding RTL support to custom elements and custom styles, see <>. +For details on adding RTL support to custom elements and custom styles, see <>. == Custom I18N Provider From d87a4922a98ce55f546850f8fb626d685c476311 Mon Sep 17 00:00:00 2001 From: Artur Signell Date: Fri, 10 Apr 2026 16:00:22 +0000 Subject: [PATCH 6/6] docs: reorganize Localization page and clarify locale resolution Lead Translation Strings with view-side getTranslation() usage, then explain default storage and link to Custom I18N Provider for other sources. Use Spanish instead of Finnish in examples for broader familiarity. Rewrite Automatic Locale from Browser to correctly describe how the initial locale is resolved against I18NProvider.getProvidedLocales(), including how the default provider auto-discovers locales from translations_*.properties filenames. --- articles/flow/i18n/index.adoc | 51 +++++++++++++++++------------------ 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/articles/flow/i18n/index.adoc b/articles/flow/i18n/index.adoc index 9c0f427d8f..edd26a872c 100644 --- a/articles/flow/i18n/index.adoc +++ b/articles/flow/i18n/index.adoc @@ -14,11 +14,22 @@ Vaadin has built-in support for localizing applications. This page covers transl == Translation Strings -Store translation properties files in `src/main/resources/vaadin-i18n/` with the filename prefix `translations`: +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`: + +[source,java] +---- +Span title = new Span(getTranslation("app.title")); +Span greeting = new Span(getTranslation("greeting", user.getName())); +---- + + +=== 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_fi.properties` -- Finnish +- `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`). @@ -31,27 +42,19 @@ greeting=Hello, {0}! items.count=You have {0} items. ---- -.translations_fi.properties +.translations_es.properties [source,properties] ---- -app.title=Sovellukseni -greeting=Hei, {0}! -items.count=Sinulla on {0} kohdetta. +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 <> for details. -=== Using Translations in Views - -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`: - -[source,java] ----- -Span title = new Span(getTranslation("app.title")); -Span greeting = new Span(getTranslation("greeting", user.getName())); ----- === Reacting to Locale Changes @@ -85,12 +88,13 @@ Use Copilot to automate extraction of static strings into translation files. See === Automatic Locale from Browser -The initial locale is determined by matching the locales in your translation files against the `Accept-Language` header from the 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 no match, the first provided locale is used. -4. If no locales are provided, `Locale.getDefault()` is used. +3. If neither matches, `I18NProvider.getDefaultLocale()` is used, which by default is the first locale in `getProvidedLocales()`. + +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()`. === Changing the Locale Programmatically @@ -99,7 +103,7 @@ Set the locale for the current session with: [source,java] ---- -UI.getCurrent().setLocale(new Locale("fi")); +UI.getCurrent().setLocale(new Locale("es")); ---- This triggers a `LocaleChangeEvent` on all attached components that implement `LocaleChangeObserver`. @@ -118,7 +122,7 @@ public class LanguageSwitcher extends Select { Locale.ENGLISH, Locale.FRENCH, Locale.GERMAN, - new Locale("fi") + new Locale("es") ); setItemLabelGenerator(locale -> locale.getDisplayLanguage(locale)); setValue(UI.getCurrent().getLocale()); @@ -184,7 +188,7 @@ NumberFormat numberFormat = NumberFormat.getInstance(locale); String formatted = numberFormat.format(1234567.89); // en_US: "1,234,567.89" // de_DE: "1.234.567,89" -// fi_FI: "1 234 567,89" +// es_ES: "1.234.567,89" ---- @@ -309,9 +313,4 @@ public void localeChange(LocaleChangeEvent event) { For details on adding RTL support to custom elements and custom styles, see <>. - -== Custom I18N Provider - -The default translation file mechanism covers most use cases. If you need more control -- for example, loading translations from a database or using a different file structure -- you can implement a custom `I18NProvider`. See <> for details. - [discussion-id]`722E7AE4-191E-4DE8-90F1-CAE8AE6CD3DF`