This package provides a MessageSource for using translations from JSON files.
- Dependency
- MessageSource Configuration
- JSON Files
- Message Formatting
- Full Example
- Related MessageSources and Examples
- License
<dependency>
<groupId>io.github.alaugks</groupId>
<artifactId>spring-messagesource-json</artifactId>
<version>0.2.1</version>
</dependency>implementation group: 'io.github.alaugks', name: 'spring-messagesource-json', version: '0.2.1'
builder(Locale defaultLocale, LocationPattern locationPattern) (required)
- Argument
Locale defaultLocale: Defines the default locale. - Argument
LocationPattern locationPattern:- Defines the pattern used to select the JSON files.
- The package uses the PathMatchingResourcePatternResolver to select the JSON files. So you can use the supported patterns.
- Files with the extension
jsonare filtered from the result list.
defaultDomain(String defaultDomain)
- Defines the default domain. Default is
messages. Codes stored under this domain are also accessible without the domain prefix; codes stored under any other domain require the<domain>.<code>prefix. For more information, see JSON Files.
enableICU4j()
- Formats messages with ICU4J's
com.ibm.icu.text.MessageFormatinstead of the defaultjava.text.MessageFormat. This adds support for named arguments and ICUplural/selectpatterns. Disabled by default. For more information, see Message Formatting.
parentMessageSource(MessageSource messageSource)
- Sets a parent
MessageSourceto delegate to. When a code cannot be resolved from the JSON files, the lookup falls back to the parent source. See Parent MessageSource for usage in either order.
- Default locale is
en. - The JSON files are stored in
src/main/resources/translations.
import io.github.alaugks.spring.messagesource.json.JsonResourceMessageSource;
import io.github.alaugks.spring.messagesource.catalog.resources.LocationPattern;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Locale;
@Configuration
public class MessageSourceConfig {
@Bean
public MessageSource messageSource() {
return JsonResourceMessageSource
.builder(
Locale.forLanguageTag("en"),
new LocationPattern("translations/*")
)
.build();
}
}- Translations can be separated into different files (domains). The default domain is
messages. - The default domain can be defined.
- Translation files must be stored in the resource folder and have the extension
json.
# Default language
<domain>.json // <domain>_<language>.json also works.
# Domain + Language
<domain>[-_]<language>.json
# Domain + Language + Region
<domain>[-_]<language>[-_]<region>.json
-
Default domain is
messages. -
Default locale is
enwithout region. -
Translations are provided for the locale
en,deanden-US.
[resources]
|-[translations]
|-messages.json // Default domain and default language. messages_en.json also works.
|-messages_de.json
|-messages_en-US.json
|-payment.json // Default language. payment_en.json also works.
|-payment_de.json
|-payment_en-US.json
Mixing JSON versions is possible. Here is an example using JSON 1.2 and JSON 2.1.
{
"headline": "Headline",
"postcode": "Postcode"
}{
"headline": "Überschrift",
"postcode": "Postleitzahl"
}{
"postcode": "Zip code"
}{
"headline": "Payment",
"expiry_date": "Expire date"
}{
"headline": "Zahlung",
"expiry_date": "Ablaufdatum"
}{
"expiry_date": "Expiration date"
}The behaviour of resolving the target value based on the code is equivalent to the ResourceBundleMessageSource or ReloadableResourceBundleMessageSource.
| id (code) | en | en-US | de | jp*** |
|---|---|---|---|---|
| headline* messages.headline |
Headline | Headline** | Überschrift | Headline |
| postcode* messages.postcode |
Postcode | Zip code | Postleitzahl | Postcode |
| payment.headline | Payment | Payment** | Zahlung | Payment |
| payment.expiry_date | Expiry date | Expiration date | Ablaufdatum | Expiry date |
*Default domain is
messages.**Example of a fallback from Language_Region (
en-US) to Language (en). Theiddoes not exist inen-US, so it tries to select the translation with localeen.***There is no translation for Japanese (
jp). The default locale translations (en) are selected.
A resolved value is formatted before it is returned, applying the arguments passed to getMessage(...) to the message pattern. Two formatters are available: the default java.text.MessageFormat and, once enableICU4j() is set, com.ibm.icu.text.MessageFormat.
Important
Named arguments and ICU plural/select patterns (e.g. {count, plural, …}) cannot be resolved by the default java.text.MessageFormat and fail at getMessage() time. To use them you must enable ICU4J via enableICU4j().
ICU4J is the com.ibm.icu:icu4j dependency, which is shipped transitively with this package — no extra dependency is required. Its com.ibm.icu.text.MessageFormat is a syntax superset of java.text.MessageFormat, so existing numeric-index patterns keep working.
Note that the two are not fully output-compatible: ICU4J uses Unicode CLDR locale data, so the formatted result for a given locale can differ from the JDK's — for example the decimal and grouping separators in numbers (. vs ,). Verify locale-sensitive output after enabling ICU4J.
Without enableICU4j(), values are formatted with java.text.MessageFormat — the same formatter Spring's ResourceBundleMessageSource uses. It only understands numeric argument indices ({0}, {1}, …), passed positionally as an Object[]. Numbers are formatted locale-aware (grouping separators differ per locale).
{
"files": "There are {0,number,integer} files."
}{
"files": "Es gibt {0,number,integer} Dateien."
}messageSource.getMessage(
"files",
new Object[] { 10000 },
Locale.forLanguageTag("de")
);Result: Es gibt 10.000 Dateien.
Enable ICU4J on the builder to format with ICU4J's MessageFormat:
@Bean
public MessageSource messageSource() {
return JsonResourceMessageSource
.builder(
Locale.forLanguageTag("en"),
new LocationPattern("translations/*")
)
.enableICU4j() // required for named arguments and plural/select
.build();
}With ICU4J enabled, patterns can use named arguments and the ICU plural/select constructs. Named arguments are passed as a single Map (not as positional {0} / {1} arguments); the underlying catalog detects a lone Map argument and formats the pattern with it.
A plural switch selects a variant based on a number. Each case is either an exact number — matched as =N — or a CLDR plural keyword (zero, one, two, few, many, other) that the locale's plural rules select from the number. The number itself is inserted into a case by referencing the argument name, {count}.
Which keywords a language uses, and how each number maps to one, is defined per language in the Unicode CLDR Language Plural Rules.
{
"file_deleted": "{count, plural, =0 {You deleted no files.} =1 {You deleted one file.} other {You deleted {count} files.}}"
}{
"file_deleted": "{count, plural, =0 {Sie haben keine Dateien gelöscht.} =1 {Sie haben eine Datei gelöscht.} other {Sie haben {count} Dateien gelöscht.}}"
}messageSource.getMessage(
"file_deleted",
new Object[] { Map.of("count", 1000) },
Locale.forLanguageTag("de")
);Result: Sie haben 1.000 Dateien gelöscht.
A select switch picks the case whose value matches the argument. Use it for any value-based choice such as grammatical gender; a final other case acts as the fallback.
{
"greeting": "{recipient_gender, select, feminine {How is she?} masculine {How is he?} other {How are they?}}"
}{
"greeting": "{recipient_gender, select, feminine {Wie geht es ihr?} masculine {Wie geht es ihm?} other {Wie geht es ihnen?}}"
}messageSource.getMessage(
"greeting",
new Object[] { Map.of("recipient_gender", "feminine") },
Locale.forLanguageTag("de")
);Result: Wie geht es ihr?
https://github.com/alaugks/spring-messagesource-json-example
- XLIFF MessageSource for Spring
- Example: XLIFF MessageSource for Spring
- Example: JSON MessageSource for Spring
- Example: Database MessageSource for Spring
Licensed under the Apache License, Version 2.0.