Skip to content

i18n & Translations

commons-rest-server auto-configures an AcceptHeaderLocaleResolver, so every request carries a resolved locale (used e.g. for translating validation messages):

locale:
resolver:
enabled: true # default
default: en # fallback locale
supported: de, en-US # optional whitelist

Inside your code the current locale is available as usual via LocaleContextHolder.getLocale().

Translation (in commons-rest-api) holds a value in multiple languages — a Map<Locale, String> with smart JSON behavior and fluent builders:

Translation name = Translation.of(Locale.GERMAN, "Farbe")
.english("color")
.french("couleur");
String current = name.getTranslated(); // resolved via LocaleContextHolder
String german = name.getTranslated(Locale.GERMAN);
boolean has = name.hasLocale(Locale.FRENCH);

Locale lookup is fuzzy: exact match first, then language-only (de-ATde), then root — so a de translation answers a de-CH request.

By default a Translation serializes as the full object:

{ "name": { "de": "Farbe", "en": "color", "fr": "couleur" } }

Annotate the field with @Translated and the serializer flattens it to a single string in the request’s locale — ideal for Read DTOs where the client only needs “their” language:

public class ArticleRead {
@Translated
private Translation name; // → "name": "Farbe" (for a German caller)
@Translated("en")
private Translation slug; // → always English
}

Deserialization accepts both forms: a full {languageTag: value} object or a plain string (stored under the current locale).

The bean-validation constraint @HasDefaultLocale ensures a Translation in a Write DTO carries at least a value for the current locale or English:

public class ArticleWrite {
@NotNull @HasDefaultLocale
private Translation name;
}

The same fuzzy matching used by Translation is available standalone via LocaleFilter — useful when you keep Map<Locale, T> structures yourself:

T value = LocaleFilter.findClosest(LocaleContextHolder.getLocale(), configByLocale, fallback);