Skip to content

Latest commit

 

History

125 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

XLIFF MessageSource for Spring

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.

Quality Gate Status Maven Central

Table of Contents

Dependency

Maven

<dependency>
    <groupId>io.github.alaugks</groupId>
    <artifactId>spring-messagesource-xliff</artifactId>
    <version>3.2.1</version>
</dependency>

Gradle

implementation group: 'io.github.alaugks', name: 'spring-messagesource-xliff', version: '3.2.1'

MessageSource Configuration

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.

⚠️ See Parent MessageSource for usage in either order.

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.

Example

  • 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();
    }

}

XLIFF Files

  • Translations can be split into files by domain (default domain messages, configurable via defaultDomain).
  • Files live in the resource folder with extension xliff or xlf.
  • Supported versions: 1.2, 2.0, 2.1 and 2.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.xsd with the metadata.xsd module); off by default, enable with validateSchema(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.

Translation Key

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: resnameid. resname is the original resource name (e.g. a properties-file key) and is preferred; id is required and unique within the <file/> but is a tool-internal identifier, used as the key only when resname is absent. (Docs: General Identifiers)
  • XLIFF 2.x: unit/@nameunit/@id, analogous to 1.2. segment/@id is 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: no name/id).

Translation Value

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).

XLIFF 1.2

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: greetingHallo Welt

XLIFF 2.x — Segmentation

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: disclaimerAlle Preise inkl. MwSt. Irrtümer vorbehalten.

XLIFF 2.x — Segments Order

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: exampleErstes Zweites

XLIFF 2.2 — PGS Module (Plural, Gender and Select)

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).

⚠️ See XLIFF 2.2 — PGS Module for the annotation, all switch types and examples.

Markup

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

Whitespace

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"> &#183; </target>
    </segment>
</unit>

Result: separator · (with the surrounding spaces preserved)

Structure of the Translation Filename

# Default language
<domain>.xlf    // <domain>_<language>.xlf also works.

# Domain + Language
<domain>[-._]<language>.xlf

# Domain + Language + Region
<domain>[-._]<language>[-_]<region>.xlf

Example with XLIFF Files

  • Default domain is messages.
  • Default locale is en without region.
  • Translations are provided for the locale en, de and en-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 Files

XLIFF versions can be mixed. Example using XLIFF 1.2 and 2.1:

messages.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">
        <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>
messages_de.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>
messages_en-US.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>
payment.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>
payment_de.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>
payment_en-US.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>

Target value

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). The id does not exist in en-US, so it tries to select the translation with locale en.

***There is no translation for Japanese (jp). The default locale translations (en) are selected.

Unsupported XLIFF Features

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).

Full Example

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

Related MessageSources and Examples

License

Licensed under the Apache License, Version 2.0.

About

Provides a Spring MessageSource for XLIFF files. The package support XLIFF versions 1.2, 2.0, 2.1 and 2.2 (include PGS Module).

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Used by

Contributors

Languages