From a1dc46e86bf47a3a105910d4f80b69a0071714f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Laugks?= Date: Fri, 14 Jul 2023 19:16:25 +0200 Subject: [PATCH] Add version 1.1.0 - Add handling translation unit identifiers attributes - Improvement sourcecode - Improvement and extended README.md - Extended tests (spy tests for caching vs. create catalog) --- .github/workflows/maven-test.yml | 1 + README.md | 285 ++++++++++++++---- pom.xml | 22 +- .../xliff/XliffTranslationMessageSource.java | 9 +- .../xliff/catalog/CatalogWrapper.java | 15 +- .../xliff/catalog/xliff/Xliff12.java | 16 +- .../xliff/catalog/xliff/Xliff2.java | 29 ++ .../xliff/catalog/xliff/Xliff20.java | 22 -- .../xliff/catalog/xliff/Xliff21.java | 20 -- .../xliff/catalog/xliff/XliffAbstract.java | 27 +- .../catalog/xliff/XliffCatalogBuilder.java | 11 +- .../xliff/catalog/xliff/XliffInterface.java | 3 + .../catalog/xliff/XliffParserUtility.java | 45 ++- .../xliff/catalog/xliff/XliffReader.java | 3 +- ...tchingResourcePatternResolverAbstract.java | 22 +- ...chingResourcePatternResolverCasesTest.java | 50 ++- ...gResourcePatternResolverInitCacheTest.java | 14 +- ...ffMatchingResourcePatternResolverTest.java | 12 +- .../xliff/catalog/CatalogCacheTest.java | 12 +- .../xliff/catalog/CatalogWrapperTest.java | 147 ++++++++- .../xliff/catalog/xliff/Xliff20Test.java | 2 +- .../xliff/catalog/xliff/Xliff21Test.java | 2 +- .../catalog/xliff/XliffParserUtilityTest.java | 72 ++++- .../xliff/catalog/xliff/XliffReaderTest.java | 4 +- .../fixtures/xliff-parser-utility-test.xliff | 3 + .../resources/translations/messages.xliff | 16 + 26 files changed, 657 insertions(+), 207 deletions(-) create mode 100644 src/main/java/io/github/alaugks/spring/messagesource/xliff/catalog/xliff/Xliff2.java delete mode 100644 src/main/java/io/github/alaugks/spring/messagesource/xliff/catalog/xliff/Xliff20.java delete mode 100644 src/main/java/io/github/alaugks/spring/messagesource/xliff/catalog/xliff/Xliff21.java diff --git a/.github/workflows/maven-test.yml b/.github/workflows/maven-test.yml index 96433bc..d77438d 100644 --- a/.github/workflows/maven-test.yml +++ b/.github/workflows/maven-test.yml @@ -6,6 +6,7 @@ on: - main - stage - dev + - feature/* # workflow_dispatch: jobs: tests: diff --git a/README.md b/README.md index e1d7bb6..8497189 100644 --- a/README.md +++ b/README.md @@ -2,27 +2,29 @@ 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** +With the implementation and use of the MessageSource interface, the translations are also available in [Thymeleaf](https://www.thymeleaf.org/). -1. [Version](#1-Versions) -2. [Dependency](#2-Dependency) -3. [MessageSource Configuration](#3-MessageSource-Configuration) -4. [Minimal CacheManager Configuration](#4-Minimal-CacheManager-Configuration) -5. [CacheManager with Supported Cache Providers](#5-CacheManager-with-Supported-Cache-Providers) -6. [Cache warming with an ApplicationRunner (recommended)](#6-Cache-warming-with-an-ApplicationRunner-recommended) -7. [Xliff Translations Files](#7-XLIFF-Translation-Files) -8. [Example with Translations Files](#8-Example-with-Translations-Files) -9. [Full Example](#9-Full-Example) -10. [Support](#10-Support) +**Table of content** -## 1. Versions +1. [Version](#Versions) +2. [Dependency](#Dependency) +3. [MessageSource Configuration](#MessageSource-Configuration) +4. [Minimal CacheManager Configuration](#Minimal-CacheManager-configuration) +5. [CacheManager with supported Cache Providers](#CacheManager-with-supported-Cache-Providers) +6. [Cache warming with an ApplicationRunner (recommended)](#Cache-warming-with-an-ApplicationRunner-recommended) +7. [Xliff Translations files](#Xliff-Translations-files) +8. [Example with Translations files](#Example-with-Translations-files) +9. [Full Example](#Full-Example) +10. [Support](#Support) +## Versions | Version | Description | -|:------- |:-------------------- | +|:--------|:---------------------| +| 1.1.0 | Release note | | 1.0.0 | First public version | +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=alaugks_spring-xliff-translation&metric=alert_status)](https://sonarcloud.io/summary/overall?id=alaugks_spring-xliff-translation) [![Maven Central](https://img.shields.io/maven-central/v/io.github.alaugks/spring-messagesource-xliff.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.github.alaugks/spring-messagesource-xliff/1.1.0) -[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=alaugks_spring-xliff-translation&metric=alert_status)](https://sonarcloud.io/summary/overall?id=alaugks_spring-xliff-translation) [![Maven Central](https://img.shields.io/maven-central/v/io.github.alaugks/spring-messagesource-xliff.svg?label=Maven%20Central)](https://central.sonatype.com/artifact/io.github.alaugks/spring-messagesource-xliff/1.0.0) ## 2. Dependency @@ -31,32 +33,39 @@ This package provides a **MessageSource** for using translations from XLIFF file io.github.alaugks spring-messagesource-xliff - 1.0.0 + 1.1.0 ``` **Gradle** ```text -implementation group: 'io.github.alaugks', name: 'spring-messagesource-xliff', version: '1.0.0' +implementation group: 'io.github.alaugks', name: 'spring-messagesource-xliff', version: '1.1.0' ``` + ## 3. MessageSource Configuration The class XliffTranslationMessageSource implements the [MessageSource](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/MessageSource.html) interface. An instance of the [CacheManager](https://docs.spring.io/spring-boot/docs/2.1.6.RELEASE/reference/html/boot-features-caching.html#boot-features-caching-provider) is required for caching the translations. ### XliffTranslationMessageSource -`setBasenamePattern(String basename)` or `setBasenamesPattern(Iterable basenames)` (*mandatory*) +`setBasenamePattern(String basename)` or `setBasenamesPattern(Iterable basenames)` (*required*) * Defines the pattern used to select the XLIFF files. * The package uses the [PathMatchingResourcePatternResolver](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/core/io/support/PathMatchingResourcePatternResolver.html) to select the XLIFF files. So you can use the supported patterns. * Files with the extension `xliff` and `xlf` are filtered from the result list. -`setDefaultLocale(Locale locale)` (*mandatory*) +`setDefaultLocale(Locale locale)` (*required*) * Defines the default language. `setDefaultDomain(String defaultDomain)` -* Defines the default domain. Default is `messages`. For more information, see [Xliff Translations Files](#7-XLIFF-Translation-Files). +* Defines the default domain. Default is `messages`. For more information, see [XlIFF Translations Files](#7-XLIFF-Translation-Files). + +`setTranslationUnitIdentifiersOrdering(List translationUnitIdentifiers)` +* The Identifiers can be defined with attributes on a translation unit. The attribute `id` is required for XLIFF 1.2 and 2.*. For XLIFF 1.2, the attribute `resname` can also be defined. + * **XLIFF 2.1**: Default definition is `"resname", "id"`. If the attribute `resname` does not exist, the attribute `id` is used to determine the identifier for the catalogue. + * **XLIFF 2.\***: Default definition is `"id"`. The `id` defines the identifier for the catalogue. The attribute `id` is optional by standard in XLIFF 2.*. However, this package requires the `id` on a translation unit. +* Documentation Identifiers: [XLIFF 1.2](http://docs.oasis-open.org/xliff/v1.2/xliff-profile-html/xliff-profile-html-1.2.html#General_Identifiers), [XLIFF 2.0](https://docs.oasis-open.org/xliff/xliff-core/v2.0/csprd01/xliff-core-v2.0-csprd01.html#segment) and [XLIFF 2.1](https://docs.oasis-open.org/xliff/xliff-core/v2.1/os/xliff-core-v2.1-os.html#segment) > Please note the [Minimal CacheManager Configuration](#Minimal-CacheManager-configuration). @@ -84,11 +93,12 @@ public class MessageConfig { } ``` + ## 4. 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. +The CacheName must be set with the constant `CatalogCache.CACHE_NAME`. The specific cache identifier is stored in the constant. [ConcurrentMapCacheManager](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/cache/concurrent/ConcurrentMapCacheManager.html) is the default cache in Spring Boot and Spring. @@ -107,22 +117,25 @@ 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; } + } ``` + ## 5. CacheManager with Supported Cache Providers -[Supported Cache Providers](https://docs.spring.io/spring-boot/docs/3.1.1/reference/html/io.html#io.caching.provider) can also be used. The following Example using [Caffeine](https://github.com/ben-manes/caffeine): +[Supported Cache Providers](https://docs.spring.io/spring-boot/docs/3.1.1/reference/html/io.html#io.caching.provider) can also be used. The following example using [Caffeine](https://github.com/ben-manes/caffeine): ### CacheConfig with Caffeine -The CacheName must be set with the constant `CatalogCache.CACHE_NAME`. No ExpireDate should be set for the XLIFF Translations cache. +The CacheName must be set with the constant `CatalogCache.CACHE_NAME`. No ExpireDate should be set for the XLIFF translations cache. ```java import com.github.benmanes.caffeine.cache.Caffeine; @@ -139,6 +152,7 @@ import java.util.List; @Configuration @EnableCaching class CacheConfig { + @Bean public Caffeine caffeineConfig() { return Caffeine.newBuilder(); @@ -152,16 +166,17 @@ class CacheConfig { caffeineCacheManager.setCacheNames(cacheNames); return caffeineCacheManager; } + } ``` + ## 6. Cache warming with an ApplicationRunner (recommended) In the following example, the cache of translations is warmed up after the application starts. ```java 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; @@ -170,26 +185,30 @@ import org.springframework.stereotype.Component; @Component public class AppStartupRunner implements ApplicationRunner { - @Autowired - MessageSource messageSource; + private final MessageSource messageSource; - @Override - public void run(ApplicationArguments args) { - if (this.messageSource instanceof XliffTranslationMessageSource) { - ((XliffTranslationMessageSource) this.messageSource).initCache(); - } + public AppStartupRunner(MessageSource messageSource) { + this.messageSource = messageSource; + } + + @Override + public void run(ApplicationArguments args) { + if (this.messageSource instanceof XliffTranslationMessageSource) { + ((XliffTranslationMessageSource) this.messageSource).initCache(); } + } + } ``` + ## 7. XLIFF Translation 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` or `xlf`. -* In the XLIFF files, the `` is fetched in a `` (XLIFF 1.2) or `` (XLIFF 2.*). -* For performance reasons, there is no validation of XLIFF files with an XMLSchema. If there is any broken XML in an XLIFF file, the SAX parser will throw a [Fatal Error]. - +* In the XLIFF files, the `` is retrieved in a `` (XLIFF 1.2) or `` (XLIFF 2.*). +* For performance reasons, there is no validation of XLIFF files with an XMLSchema. If there is any corrupt XML in an XLIFF file, the SAX parser will throw a [Fatal Error]. ### Structure of the Translation Filename @@ -204,7 +223,8 @@ public class AppStartupRunner implements ApplicationRunner { [-_][-_].xlf ``` -## 8. Example with Translations Files + +### Example with Translations Files * Default domain is `messages`. * Default locale is `en` without region. @@ -221,11 +241,11 @@ public class AppStartupRunner implements ApplicationRunner { |-payment_en-US.xliff ``` -### Translations files +#### Translations files Mixing XLIFF versions is possible. Here is an example using XLIFF 1.2 and XLIFF 2.1. -#### messages.xliff +##### messages.xliff ```xml Postcode Postcode + + Examples + Examples + + + Translation with param + Translation with param + + + Your email {0} has been registered. + Your email {0} has been registered. + + + This is a default message. + This is a default message. + ``` -#### messages_de.xliff +##### messages_de.xliff ```xml Postcode Postleitzahl + + Examples + Beispiele + + + Translation with param + Übersetzung mit Parameter + + + Your email {0} has been registered. + Ihre E-Mail {0} wurde registriert. + + + This is a default message. + Das ist ein Standardtext. + ``` -#### messages_en-US.xliff +##### messages_en-US.xliff ```xml ``` -#### payment.xliff +##### payment.xliff ```xml ``` -#### payment_de.xliff +##### payment_de.xliff ```xml ``` -#### payment_en-US.xliff +##### payment_en-US.xliff ```xml ``` -#### Target value - -| 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 | +##### Target value + +| id | en | de | en-US | +|---------------------------------|-------------------------------------|------------------------------------|-------------------------------------| +| headline* | Headline | Überschrift | Headline** | +| messages.headline | Headline | Überschrift | Headline** | +| postcode* | Postcode | Postleitzahl | Zip code | +| messages.postcode | Postcode | Postleitzahl | Zip code | +| headline-examples | Examples | Beispiele | Examples | +| messages.headline-examples | Examples | Beispiele | Examples | +| translation-args-label | Translation with param | Übersetzung mit Parameter | Translation with param | +| messages.translation-args-label | Translation with param | Übersetzung mit Parameter | Translation with param | +| email-notice | Your email {0} has been registered. | Ihre E-Mail {0} wurde registriert. | Your email {0} has been registered. | +| messages.email-notice | Your email {0} has been registered. | Ihre E-Mail {0} wurde registriert. | Your email {0} has been registered. | +| default-message | This is a default message. | Das ist ein Standardtext. | This is a default message. | +| messages.default-message | This is a default message. | Das ist ein Standardtext. | This is a default message. | +| 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 id `headline` in messages_en-US. The id `headline` does not exist, so it tries to select the translation with locale `en` in messages. + +## 8. Using the MessageSource in Thymeleaf or as Service + +### Thymeleaf + +With the configured MessageSource, the translations are available in Thymeleaf. See the example in the [Full Example](#9-Full-Example). + +```html + +

+ + +

+ + +