XLIFF translation support for Spring Boot and Spring
This package provides a MessageSource for using translations from XLIFF files. The package support XLIFF versions 1.2, 2.0 and 2.1.
Table of content
- Version
- Dependency
- MessageSource Configuration
- Minimal CacheManager Configuration
- CacheManager with supported Cache Providers
- Cache warming with an ApplicationRunner (recommended)
- Xliff Translations files
- Example with Translations files
- Full Example
- Support
Versions
Version | Description |
---|---|
1.0.0 | First public version |
Dependency
Maven
<dependency>
<groupId>io.github.alaugks</groupId>
<artifactId>spring-messagesource-xliff</artifactId>
<version>1.0.0</version>
</dependency>
Gradle
implementation group: 'io.github.alaugks', name: 'spring-messagesource-xliff', version: '1.0.0'
MessageSource Configuration
The class XliffTranslationMessageSource implements the MessageSource interface. An instance of the CacheManager is required for caching the translations.
XliffTranslationMessageSource
setBasenamePattern(String basename)
or setBasenamesPattern(Iterable<String> basenames)
(mandatory)
- Defines the pattern used to select the XLIFF files.
- The package uses the PathMatchingResourcePatternResolver to select the XLIFF files. So you can use the supported patterns.
- Files with the extension
xliff
andxlf
are filtered from the result list.
setDefaultLocale(Locale locale)
(mandatory)
- Defines the default language.
setDefaultDomain(String defaultDomain)
- Defines the default domain. Default is 'messages'. For more information, see Xliff translations files.
Please note the Minimal CacheManager Configuration.
import de.alaugks.spring.XliffTranslationMessageSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.CacheManager;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Locale;
@Configuration
public class MessageConfig {
@Bean("messageSource")
public MessageSource messageSource(CacheManager cacheManager) {
XliffMessageSourcePatternResolver messageSource = new XliffTranslationMessageSource(cacheManager);
messageSource.setDefaultLocale(Locale.forLanguageTag("en"));
messageSource.setBasenamePattern("translations/*");
return messageSource;
}
}
Minimal CacheManager Configuration
You may already have an existing CacheManager configuration. If not, the following minimum CacheManager configuration is required.
The CacheName must be set with the constant CatalogCache.CACHE_NAME
. The specific cache identifier is stored in the constant. Currently you cannot set a custom cache name.
ConcurrentMapCacheManager is the default cache in Spring Boot and Spring.
CacheConfig
import io.github.alaugks.spring.messagesource.xliff.catalog.CatalogCache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.concurrent.ConcurrentMapCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager() {
ConcurrentMapCacheManager cacheManager = new ConcurrentMapCacheManager();
cacheManager.setCacheNames(List.of(CatalogCache.CACHE_NAME));
return cacheManager;
}
}
CacheManager with supported Cache Providers
A supported Cache Providers can also be used. Here is an example using Caffeine:
CacheConfig
The CacheName must be set with the constant CatalogCache.CACHE_NAME
. No ExpireDate should be set for the XLIFF Translations cache.
import com.github.benmanes.caffeine.cache.Caffeine;
import io.github.alaugks.spring.messagesource.xliff.catalog.CatalogCache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.caffeine.CaffeineCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Collection;
import java.util.List;
@Configuration
@EnableCaching
class CacheConfig {
@Bean
public Caffeine<Object, Object> caffeineConfig() {
return Caffeine.newBuilder();
}
@Bean
public CacheManager cacheManager(Caffeine<Object, Object> caffeine) {
Collection<String> cacheNames = List.of(CatalogCache.CACHE_NAME);
CaffeineCacheManager caffeineCacheManager = new CaffeineCacheManager();
caffeineCacheManager.setCaffeine(caffeine);
caffeineCacheManager.setCacheNames(cacheNames);
return caffeineCacheManager;
}
}
Cache warming with an ApplicationRunner (recommended)
In the following example, the cache of translations is warmed up after the application starts.
import io.github.alaugks.spring.messagesource.xliff.XliffMessageSourcePatternResolver;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.MessageSource;
import org.springframework.stereotype.Component;
@Component
public class AppStartupRunner implements ApplicationRunner {
@Autowired
MessageSource messageSource;
@Override
public void run(ApplicationArguments args) {
if (this.messageSource instanceof XliffTranslationMessageSource) {
((XliffTranslationMessageSource) this.messageSource).initCache();
}
}
}
XLIFF Translations files
- 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
xliff
orxlf
. - For performance reasons, there is no validation of XLIFF files against an xml schema. If there is any broken XML in an XLIFF file, the SAX parser will throw a [Fatal Error].
Structure of the translation filename
# Default language
<domain>.xlf
# Domain + Language
<domain>[-_]<language>.xlf
# Domain + Language + Region
<domain>[-_]<language>[-_]<region>.xlf
Example with Translations files
- Default domain is
messages
. - Default locale is
en
without region. - Translations are provided for the locale
de
(without region) anden-US
.
[resources]
|-[translations]
|-messages.xliff // Default domain and default language
|-messages_de.xliff
|-messages_en-US.xliff
|-payment.xliff // Default language
|-payment_de.xliff
|-payment_en-US.xliff
Translations files
messages.xliff
<?xml version="1.0" encoding="utf-8"?>
<xliff ...>
<file ...>
<unit ...>
<segment id="headline">
<source>Headline</source>
<target>Headline</target>
</segment>
<segment id="postcode">
<source>Postcode</source>
<target>Postcode</target>
</segment>
</unit>
</file>
</xliff>
messages_de.xliff
<?xml version="1.0" encoding="utf-8"?>
<xliff ...>
<file ...>
<unit ...>
<segment id="headline">
<source>Headline</source>
<target>Überschrift</target>
</segment>
<segment id="postcode">
<source>Postcode</source>
<target>Postleitzahl</target>
</segment>
</unit>
</file>
</xliff>
messages_en-US.xliff
<?xml version="1.0" encoding="utf-8"?>
<xliff ...>
<file ...>
<unit ...>
<segment id="postcode">
<source>Postcode</source>
<target>Zip code</target>
</segment>
</unit>
</file>
</xliff>
payment.xliff
<?xml version="1.0" encoding="utf-8"?>
<xliff ...>
<file ...>
<unit ...>
<segment id="headline">
<source>Payment</source>
<target>Payment</target>
</segment>
<segment id="expiry_date">
<source>Expiry date</source>
<target>Expiry date</target>
</segment>
</unit>
</file>
</xliff>
payment_de.xliff
<?xml version="1.0" encoding="utf-8"?>
<xliff ...>
<file ...>
<unit ...>
<segment id="headline">
<source>Payment</source>
<target>Zahlung</target>
</segment>
<segment id="expiry_date">
<source>Expiry date</source>
<target>Ablaufdatum</target>
</segment>
</unit>
</file>
</xliff>
payment_en-US.xliff
<?xml version="1.0" encoding="utf-8"?>
<xliff ...>
<file ...>
<unit ...>
<segment id="headline">
<source>Payment</source>
<target>Payment</target>
</segment>
<segment id="expiry_date">
<source>Expiry date</source>
<target>Expiration date</target>
</segment>
</unit>
</file>
</xliff>
Output translation
id | en | de | en-US |
---|---|---|---|
postcode* | Postcode | Postleitzahl | Zip code |
messages.postcode | Postcode | Postleitzahl | Zip code |
headline* | Headline | Überschrift | Headline** |
messages.headline | Headline | Überschrift | Headline** |
payment.headline | Payment | Zahlung | Payment |
payment.expiry_date | Expiry date | Ablaufdatum | Expiration date |
*Default domain is
messages
.**Example of a fallback. With locale
en-US
it tries to select the translation with idheadline
in messages_en-US. The idheadline
does not exist, so it tries to select the translation with localeen
in messages.
Full Example
A complete example using Spring Boot, including the use of XLIFF 1.2 and XLIFF 2.1 translation files: https://github.com/alaugks/spring-xliff-translation-example-spring-boot
Support
If you have any questions, comments or feature requests, please use the Discussions section to contact me.