Skip to content

Commit

Permalink
Merge branch 'release/0.0.2' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
Christoph Linder committed Jan 28, 2024
2 parents 4133a66 + 18e6b28 commit a5335b4
Show file tree
Hide file tree
Showing 21 changed files with 206 additions and 306 deletions.
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,31 @@
# commons-i18n-l10n
Helper methods to facilitate i18n and l10n concerns



## Usage

### assertions

```java
enum TestAppLanguages implements AppLanguage {
DE(ULocale.forLanguageTag("de-CH")),
FR(ULocale.forLanguageTag("fr-CH"));
// code omitted for brevity
}

// business code
public I18nMessage sayHello(String toWho) {
return I18nMessage.of("hello.world", "world", toWho);
}

// test code
@Test
public void has_all_translations_for_hello_world() {
assertThatI18nMessage(sayHello("World"))
.usingTLFactory(tlFactory)
.translatesTo(TestAppLanguages.DE, "Hallo World")
.translatesTo(TestAppLanguages.FR, "Bonjour World")
.hasAllLanguagesTranslated(TestAppLanguages.values());
}
```
4 changes: 3 additions & 1 deletion api/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<parent>
<groupId>ch.dvbern.oss.commons-i18n-l10n</groupId>
<artifactId>dvb-commons-i18n-l10n-parent</artifactId>
<version>0.0.1</version>
<version>0.0.2</version>
</parent>

<artifactId>dvb-commons-i18n-l10n-api</artifactId>
Expand All @@ -20,6 +20,7 @@
<dependency>
<groupId>org.checkerframework</groupId>
<artifactId>checker-qual</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
Expand All @@ -28,6 +29,7 @@
<dependency>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-annotations</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.ibm.icu</groupId>
Expand Down
79 changes: 0 additions & 79 deletions api/src/main/java/ch/dvbern/oss/commons/i18nl10n/AbstractTL.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@

@UtilityClass
@SuppressWarnings("HideUtilityClassConstructor")
class Helpers {
class DebugHelpers {

/**
* Not every Map implementation has a nice toString() :(
*/
static String prettyPrintMap(Map<String, Serializable> args) {
return args.entrySet().stream()
.sorted(Map.Entry.comparingByKey())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.function.Function;

import com.ibm.icu.text.MessageFormat;
import lombok.AccessLevel;
Expand All @@ -13,8 +14,8 @@
import org.slf4j.LoggerFactory;

@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
public class DefaultTLStrategy extends AbstractTL {
private static final Logger LOG = LoggerFactory.getLogger(DefaultTLStrategy.class);
public class DefaultTranslatorStrategy implements Translator {
private static final Logger LOG = LoggerFactory.getLogger(DefaultTranslatorStrategy.class);

@FunctionalInterface
public interface FormatterFactory {
Expand Down Expand Up @@ -42,19 +43,30 @@ public static FormatterFactory defaultMessageFormatFactory() {
)::format;
}

public static DefaultTLStrategy create(
ResourceBundle bundle,
public static DefaultTranslatorStrategy create(
AppLanguage appLanguage,
Function<AppLanguage, ResourceBundle> bundleLoader,
FormatterFactory formatterFactory
) {
return new DefaultTLStrategy(bundle, appLanguage, formatterFactory);
var bundle = bundleLoader.apply(appLanguage);
return new DefaultTranslatorStrategy(bundle, appLanguage, formatterFactory);
}

public static DefaultTLStrategy createDefault(
ResourceBundle bundle,
AppLanguage appLanguage
public static DefaultTranslatorStrategy createDefault(
AppLanguage appLanguage,
Function<AppLanguage, ResourceBundle> bundleLoader
) {
return create(bundle, appLanguage, defaultMessageFormatFactory());
return create(appLanguage, bundleLoader, defaultMessageFormatFactory());
}

public static DefaultTranslatorStrategy createDefault(
AppLanguage appLanguage,
String bundleBaseName
) {
Function<AppLanguage, ResourceBundle> bundleLoader =
language -> ResourceBundle.getBundle(bundleBaseName, language.javaLocale());

return createDefault(appLanguage, bundleLoader);
}

@Override
Expand All @@ -66,10 +78,9 @@ public String translate(I18nMessage message) {
return result;
} catch (MissingResourceException ignored) {
LOG.warn("Translation not found for key/locale: {}/{}", message.key(), appLanguage);
var result = "!%s[%s]!".formatted(message.key().toString(), Helpers.prettyPrintMap(message.args()));
var result = "!%s[%s]!".formatted(message.key().toString(), DebugHelpers.prettyPrintMap(message.args()));

return result;
}
}

}
36 changes: 14 additions & 22 deletions api/src/main/java/ch/dvbern/oss/commons/i18nl10n/I18nKey.java
Original file line number Diff line number Diff line change
@@ -1,43 +1,35 @@
package ch.dvbern.oss.commons.i18nl10n;

import java.io.Serial;
import java.io.Serializable;
import java.util.Objects;
import java.util.regex.Pattern;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.Accessors;

@Value
@RequiredArgsConstructor(access = AccessLevel.PACKAGE)
// mimic record behavior
@Accessors(fluent = true)
public class I18nKey implements Serializable {
@Serial
private static final long serialVersionUID = 956682065123085284L;

// This is a no-brainer pattern, but it should be sufficient for an MVP.
// Most importantly: Components separated by dots, no whitespace and no special characters
// that might break external translation tools like Weblate.
/**
* Represents a key for a translation.
* It is a simple wrapper around a String,
* but it enforces a certain pattern that is compatible with all known translation tools.
*/
public record I18nKey(
String value
) {
@SuppressWarnings("java:S5998")
private static final Pattern PATH_PATTERN = Pattern.compile("^[a-zA-Z][a-zA-Z0-9_-]*(\\.[a-zA-Z0-9_-]+)*$");

private final String value;
public I18nKey(String value) {
this.value = validate(value);
}

public static I18nKey of(String path) {
validate(path);

return new I18nKey(path);
}

private static void validate(String path) {
private static String validate(String path) {
boolean valid = isValid(path);

if (!valid) {
throw new IllegalArgumentException("I18nKey is not valid: >%s<".formatted(path));
}

return path;
}

static boolean isValid(String path) {
Expand Down
25 changes: 9 additions & 16 deletions api/src/main/java/ch/dvbern/oss/commons/i18nl10n/I18nMessage.java
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
package ch.dvbern.oss.commons.i18nl10n;

import java.io.Serial;
import java.io.Serializable;
import java.util.Map;
import java.util.Objects;

import lombok.AccessLevel;
import lombok.RequiredArgsConstructor;
import lombok.Value;
import lombok.experimental.Accessors;
public record I18nMessage(
I18nKey key,
Map<String, Serializable> args
) {

@Value
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
// mimic record behavior
@Accessors(fluent = true)
public class I18nMessage implements Serializable {

@Serial
private static final long serialVersionUID = 361831326473156615L;

private final I18nKey key;
private final Map<String, Serializable> args;
public I18nMessage(I18nKey key, Map<String, Serializable> args) {
this.key = Objects.requireNonNull(key);
this.args = Map.copyOf(Objects.requireNonNull(args));
}

public static I18nMessage of(
I18nKey key,
Expand Down
65 changes: 0 additions & 65 deletions api/src/main/java/ch/dvbern/oss/commons/i18nl10n/TL.java

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
package ch.dvbern.oss.commons.i18nl10n;

/**
* Basic workhorse for translation purposes. See {@link TL} for a more convenient interface.
*/
@SuppressWarnings("InterfaceNeverImplemented")
public interface Translator {
String translate(I18nMessage message);
}
Loading

0 comments on commit a5335b4

Please sign in to comment.