This package provides a MessageSource interface for translations stored in XLIFF files. It supports XLIFF versions 1.2, 2.0, 2.1 and 2.2, including the PGS Module.
- Dependency
- MessageSource Configuration
- XLIFF Files
- Full Example
- Related MessageSources and Examples
- License
<dependency>
<groupId>io.github.alaugks</groupId>
<artifactId>spring-messagesource-xliff</artifactId>
<version>3.2.1</version>
</dependency>implementation group: 'io.github.alaugks', name: 'spring-messagesource-xliff', version: '3.2.1'
| Method | Default | Description |
|---|---|---|
builder(Locale defaultLocale, String locationPattern)builder(Locale defaultLocale, List<String> locationPatterns) |
— |
Entry point.
defaultLocale is the locale to fall back to when a translation is missing.
locationPatterns selects the XLIFF files (String or List<String>) via Spring's
PathMatchingResourcePatternResolver,
so all its patterns work. Only files ending in xliff or xlf are kept.
|
defaultDomain(String defaultDomain) |
messages |
The default domain; see XLIFF Files. |
fileExtensions(List<String> fileExtensions) |
List.of("xlf", "xliff") |
File extensions recognised as XLIFF files. |
validateSchema(boolean validateSchema) |
false |
Validate each file against its OASIS XSD before reading. validateSchema(true) rejects non-conforming files
(note: strict schemas also reject otherwise-readable files, e.g. XLIFF 1.2 <trans-unit/> without the required id).
For development or testing, it is recommended to enable validation.
|
enableICU4j() |
disabled |
Format messages with ICU4J instead of the default java.text.MessageFormat. The default only understands
numeric argument indices ({0}, {1}); ICU4J additionally supports named arguments and ICU
plural/select/gender patterns (e.g. {count, plural, …}).
|
parentMessageSource(MessageSource parentMessageSource) |
— |
Sets a parent
MessageSource
to delegate to. When a code cannot be resolved in the XLIFF translations, the lookup falls back to the parent source. |
Important
The XLIFF 2.2 PGS module generates ICU patterns with named arguments (e.g. {count, plural, …}). These cannot be resolved by the default java.text.MessageFormat and fail at getMessage() time. When using the PGS module you must enable ICU4J via enableICU4j().
ICU4J is the com.ibm.icu:icu4j dependency, which is shipped transitively with this library. 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.
- Default locale is
en. - The XLIFF files are stored in
src/main/resources/translations.
import io.github.alaugks.spring.messagesource.xliff.XliffResourceMessageSource;
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 XliffResourceMessageSource
.builder(
Locale.forLanguageTag("en"),
"translations/*" // or List.of(...)
)
.build();
}
}- Translations can be split into files by domain (default domain
messages, configurable viadefaultDomain). - Files live in the resource folder with extension
xlifforxlf. - Supported versions:
1.2,2.0,2.1and2.2. - Each file can optionally be validated against its OASIS XSD schema (1.2 →
xliff-core-1.2-transitional.xsd, 2.0/2.1 →xliff-core-2.0.xsd, 2.2 →xliff_core_2.2.xsdwith themetadata.xsdmodule); off by default, enable withvalidateSchema(true). The PGS module attributes are this library's extension and are not part of the OASIS core schema, so they are removed before validation. - SAX parser errors are handled by an ErrorHandler.
- Each unit yields a key (message code) and a value (translated text). The key is always the resource name (
resname/name), never the<source/>text. See Translation Key and Translation Value.
The key is the application-facing resource name. XLIFF separates the internal identifier (id) from the resource name (resname / name); the resource name is the key, with id as fallback when it is absent.
| Version | Element | 1. Key from | 2. Fallback | Not used as key |
|---|---|---|---|---|
| 1.2 | <trans-unit/> |
resname (optional, resource name) |
id (required, document identifier) |
— |
| 2.x | <unit/> |
name (optional, resource name) |
id (required, document identifier) |
segment/@id |
- XLIFF 1.2:
resname→id.resnameis the original resource name (e.g. a properties-file key) and is preferred;idis required and unique within the<file/>but is a tool-internal identifier, used as the key only whenresnameis absent. (Docs: General Identifiers) - XLIFF 2.x:
unit/@name→unit/@id, analogous to 1.2.segment/@idis never the key (optional, only unique within its<unit/>). (Docs: 2.0, 2.1, 2.2) - A unit is skipped when neither attribute is set (1.2: no
resname/id; 2.x: noname/id).
The value is the <target/> text and falls back to the <source/> text when no <target/> is present. It is the element's text content. Embedded markup (e.g. HTML as CDATA or escaped) is kept verbatim, XLIFF inline elements are not interpreted, and the value is trimmed unless xml:space="preserve" is set. See Markup and Whitespace (both apply to XLIFF 1.2 and 2.x).
Each <trans-unit/> has exactly one <source/> and one optional <target/>. The value is taken directly.
<trans-unit id="1" resname="greeting">
<source>Hello World</source>
<target>Hallo Welt</target>
</trans-unit>Result: greeting → Hallo Welt
A <unit/> holds one or more <segment/> elements. A single segment is the common case. Multiple segments are reassembled into one string. An <ignorable/> between them holds non-translatable content, typically whitespace.
The reassembly rules are:
- Each
<segment/>contributes its<target/>text, falling back to<source/>when no<target/>is present. - Each
<ignorable/>contributes its<source/>verbatim. - Parts are concatenated in document order.
<unit id="1" name="disclaimer">
<segment>
<source>All prices include VAT.</source>
<target>Alle Preise inkl. MwSt.</target>
</segment>
<ignorable>
<source> </source>
</ignorable>
<segment>
<source>Errors excepted.</source>
<target>Irrtümer vorbehalten.</target>
</segment>
</unit>Result: disclaimer → Alle Preise inkl. MwSt. Irrtümer vorbehalten.
The order attribute on <target/> defines how target segments are composed. Segments are sorted ascending by their order value. An <ignorable/> element always keeps its document position.
<unit id="1" name="example">
<segment>
<source>First</source>
<target order="2">Zweites</target>
</segment>
<ignorable>
<source> </source>
</ignorable>
<segment>
<source>Second</source>
<target order="1">Erstes</target>
</segment>
</unit>Result: example → Erstes Zweites
XLIFF 2.2 adds the PGS module. It annotates a <unit/> with a pgs:switch, so its <segment/>s become plural, gender or select cases. Such a unit resolves to different text depending on a runtime argument, for example a count or a gender. This requires ICU4J via enableICU4j() (see MessageSource Configuration).
Applies to XLIFF 1.2 and 2.x. The value is the element's text content; embedded markup (e.g. HTML) is kept verbatim, as a CDATA section or escaped. XLIFF inline elements (<g/>, <pc/>, <ph/>, <x/>, …) are not interpreted. Put display markup into the text as CDATA or escaped characters.
Text-wrapping inline elements, most notably the annotation marker <mrk/>, are not processed, but their text is kept: the tag is dropped, the spanned text remains. E.g. Hallo <mrk ...>Welt</mrk>! → Hallo Welt!.
<unit id="1" name="teaser">
<segment>
<source><![CDATA[Read <strong>more</strong>]]></source>
<target><![CDATA[<strong>Mehr</strong> lesen]]></target>
</segment>
</unit>Result: teaser → <strong>Mehr</strong> lesen
Applies to XLIFF 1.2 and 2.x. The value is trimmed by default. Set xml:space="preserve" on the <source/> / <target/> (or an ancestor) to keep leading and trailing whitespace.
<unit id="1" name="separator">
<segment>
<target xml:space="preserve"> · </target>
</segment>
</unit>Result: separator → · (with the surrounding spaces preserved)
# Default language
<domain>.xlf // <domain>_<language>.xlf also works.
# Domain + Language
<domain>[-._]<language>.xlf
# Domain + Language + Region
<domain>[-._]<language>[-_]<region>.xlf
- Default domain is
messages. - Default locale is
enwithout region. - Translations are provided for the locale
en,deanden-US.
[resources]
|-[translations]
|-messages.xliff // Default domain and default language. messages_en.xliff also works.
|-messages_de.xliff
|-messages_en-US.xliff
|-payment_de.xliff
|-payment_en.xliff // Default language. payment.xliff also works.
|-payment_en-US.xliff
XLIFF versions can be mixed. Example using XLIFF 1.2 and 2.1:
<?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2"
xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file original="messages"
datatype="plaintext"
source-language="en"
target-language="en">
<body>
<trans-unit id="1" resname="headline">
<source>Headline</source>
<target>Headline</target>
</trans-unit>
<trans-unit id="2" resname="postcode">
<source>Postcode</source>
<target>Postcode</target>
</trans-unit>
</body>
</file>
</xliff><?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2"
xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file original="messages"
datatype="plaintext"
source-language="en"
target-language="de">
<body>
<trans-unit id="1" resname="headline">
<source>Headline</source>
<target>Überschrift</target>
</trans-unit>
<trans-unit id="2" resname="postcode">
<source>Postcode</source>
<target>Postleitzahl</target>
</trans-unit>
</body>
</file>
</xliff><?xml version="1.0" encoding="utf-8"?>
<xliff version="1.2"
xmlns="urn:oasis:names:tc:xliff:document:1.2">
<file original="messages"
datatype="plaintext"
source-language="en"
target-language="en-US">
<body>
<trans-unit id="2" resname="postcode">
<source>Postcode</source>
<target>Zip code</target>
</trans-unit>
</body>
</file>
</xliff><?xml version="1.0" encoding="UTF-8" ?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0"
version="2.1"
srcLang="en"
trgLang="en">
<file id="payment">
<unit id="1" name="headline">
<segment>
<source>Payment</source>
<target>Payment</target>
</segment>
</unit>
<unit id="2" name="expiry_date">
<segment>
<source>Expiry date</source>
<target>Expiry date</target>
</segment>
</unit>
</file>
</xliff><?xml version="1.0" encoding="UTF-8" ?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0"
version="2.1"
srcLang="en"
trgLang="de">
<file id="payment_de">
<unit id="1" name="headline">
<segment>
<source>Payment</source>
<target>Zahlung</target>
</segment>
</unit>
<unit id="2" name="expiry_date">
<segment>
<source>Expiry date</source>
<target>Ablaufdatum</target>
</segment>
</unit>
</file>
</xliff><?xml version="1.0" encoding="UTF-8" ?>
<xliff xmlns="urn:oasis:names:tc:xliff:document:2.0"
version="2.1"
srcLang="en"
trgLang="en-US">
<file id="payment_en-US">
<unit id="2" name="expiry_date">
<segment>
<source>Expiry date</source>
<target>Expiration date</target>
</segment>
</unit>
</file>
</xliff>Resolving a value by code behaves like Spring's ResourceBundleMessageSource / 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.
This package focuses on reading and displaying translations (key → text), not on editing XLIFF with translation tools. Features that only matter for the authoring round-trip are intentionally not processed: a document using them still loads, the features are ignored, and only the resolved text is returned.
Not supported, relative to the XLIFF 1.2 and 2.x specifications (a — means the version has no such concept):
| Feature | XLIFF 1.2 | XLIFF 2.x | Description |
|---|---|---|---|
| Inline formatting / code elements | <g/>, <x/>, <bx/>, <ex/>, <bpt/>, <ept/>, <ph/>, <it/>, <sub/> |
<pc/>, <ph/>, <sc/>, <ec/>, <cp/> |
Not interpreted. Text-wrapping elements keep their text; standalone placeholders contribute nothing. Use CDATA for display markup (see Markup). |
| Placeholder / original-data fallback text | equiv-text |
equiv, disp, <originalData/> + dataRef |
Ignored; native code is not reconstructed. |
| Annotation markers | <mrk/> (mtype, comment) |
<mrk/>, <sm/> / <em/> |
Tag dropped, wrapped text kept (see Markup). |
| Translation state | state, state-qualifier |
segment state |
<target/> is always used, regardless of state. |
| Notes & alternative translations | <note/>, <alt-trans/> |
<notes/> |
Not exposed. |
| Process metadata | approved, <phase-group/> / phase, tool |
tool / metadata |
Ignored. |
| Skeleton / round-trip structure | <skl/> / external skeleton |
<skeleton/> |
Not read. |
| Grouping & context | <group/>, restype, <context-group/>, <count-group/> |
<group/> |
Structural metadata ignored. |
| Binary content | <bin-unit/>, <bin-source/>, <bin-target/> |
— | Not read. |
| XLIFF 2.x modules | — | Translation Candidates, Glossary, Metadata, Resource Data, Size/Length Restriction, Format Style, Validation, Change Tracking | Not processed (the XLIFF 2.2 PGS module is supported, see XLIFF 2.2 — PGS Module). |
A Full Example using Spring Boot, mixing XLIFF 1.2 and XLIFF 2.x translation files:
Repository: https://github.com/alaugks/spring-messagesource-xliff-example
- JSON MessageSource for Spring
- Example: XLIFF MessageSource for Spring
- Example: JSON MessageSource for Spring
- Example: Custom Database Spring MessageSource
Licensed under the Apache License, Version 2.0.