Skip to content

Commit

Permalink
Add tests for references
Browse files Browse the repository at this point in the history
  • Loading branch information
pmendelski committed Jan 1, 2023
1 parent 1080556 commit 166cd11
Show file tree
Hide file tree
Showing 27 changed files with 401 additions and 221 deletions.
12 changes: 6 additions & 6 deletions README-FORMAT.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,8 @@ with syntax `{key, type, format}` or `{key, type, ::skeleton}`
locale agnostic wat to format values

```
You have {count, number} messages.
{unread, number, percent} of them are new.
You have {count, number} new messages.
{spamPercent, number, percent} of them is spam.
```

## Format type: number
Expand Down Expand Up @@ -135,7 +135,7 @@ It's often used to format message based on a gender.
male {He}
female {She}
other {They}
} will respond shortly.
} sent you a message.
```

You can also nest arguments like:
Expand Down Expand Up @@ -168,9 +168,9 @@ all [plural categories](http://cldr.unicode.org/index/cldr-spec/plural-rules).

```
You have {itemCount, plural,
=0 {no items}
one {1 item}
other {# items}
=0 {no messages}
one {1 message}
other {# messages}
}.
```

Expand Down
20 changes: 13 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@

- [ICU message formatting](#message-formatting)
- [Message references](#message-references)
- Messages resolution with [indexed](./README_FORMAT.md#indexed-argument) and [named](./README_FORMAT.md##named-argument) arguments
- Messages resolution with [indexed](./README_FORMAT.md#indexed-argument)
and [named](./README_FORMAT.md##named-argument) arguments
- [Type based formatters](#type-based-formatters)
- [Missing message detection](#devmode)
- [Missing message detection](#missing-message-detection)
- [Whitespace normalization](#whitespace-normalization)
- Splitting big file into multiple smaller ones
- ...or use single file to define messages for multiple locales
- Supports multiple file formats: yaml, properties, json
Expand Down Expand Up @@ -40,17 +42,17 @@ Some examples:

```
# Simple argument (indexed and named)
Hello, Your friend {0} is now online.
Hello, Your friend {friend} is now online.
{0} sent you a message.
{name} sent you a message.
# Argument formatting
Order was sent on {0,date}.
Message was sent on {0,date}.
# Select statement
{gender, select, female {She} male {He} other {They}} received you email.
{gender, select, female {She} male {He} other {They}} sent you a message.
# Pluralization
I bought {0, plural, one {# book} other {# books}}
You have {0, plural, zero {no new messages}, one {one new message} other {# new messages}}
```

For more examples go to [advanced message formatting examples](./README-FORMAT.md)
Expand All @@ -61,6 +63,8 @@ For more examples go to [advanced message formatting examples](./README-FORMAT.m

## Missing message detection

## Whitespace normalization

## Files structure

## DevMode
Expand All @@ -74,3 +78,5 @@ For more examples go to [advanced message formatting examples](./README-FORMAT.m
- Update readme
- Release
- Trim new lines?
- add option to disable references
- entries -> bundles
22 changes: 11 additions & 11 deletions src/main/java/com/coditory/quark/i18n/AggregatedI18nLoader.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.coditory.quark.i18n;

import com.coditory.quark.i18n.loader.I18nLoader;
import com.coditory.quark.i18n.loader.I18nTemplates;
import com.coditory.quark.i18n.loader.I18nTemplatesBundle;
import com.coditory.quark.i18n.loader.WatchableI18nLoader;
import org.jetbrains.annotations.NotNull;

Expand All @@ -18,7 +18,7 @@
final class AggregatedI18nLoader implements WatchableI18nLoader {
private final List<I18nLoader> loaders = new ArrayList<>();
private final Map<I18nKey, String> currentEntries = new LinkedHashMap<>();
private final ConcurrentHashMap<I18nLoader, List<I18nTemplates>> cachedResults = new ConcurrentHashMap<>();
private final ConcurrentHashMap<I18nLoader, List<I18nTemplatesBundle>> cachedResults = new ConcurrentHashMap<>();
private final Set<I18nLoaderChangeListener> listeners = new LinkedHashSet<>();
private boolean watching = false;

Expand Down Expand Up @@ -60,14 +60,14 @@ public synchronized void startWatching() {
for (I18nLoader loader : loaders) {
if (loader instanceof WatchableI18nLoader watchableLoader) {
watchableLoader.startWatching();
watchableLoader.addChangeListener(entries -> onEntriesChange(loader, entries));
watchableLoader.addChangeListener(entries -> onBundlesChange(loader, entries));
}
}
}

private synchronized void onEntriesChange(I18nLoader loader, List<I18nTemplates> entries) {
cachedResults.put(loader, entries);
List<I18nTemplates> result = loaders.stream()
private synchronized void onBundlesChange(I18nLoader loader, List<I18nTemplatesBundle> bundles) {
cachedResults.put(loader, bundles);
List<I18nTemplatesBundle> result = loaders.stream()
.map(l -> cachedResults.getOrDefault(l, List.of()))
.reduce(new ArrayList<>(), (m, e) -> {
m.addAll(e);
Expand All @@ -92,7 +92,7 @@ public synchronized void stopWatching() {

@Override
@NotNull
public synchronized List<I18nTemplates> load() {
public synchronized List<I18nTemplatesBundle> load() {
appendCurrentEntries();
return loaders.stream()
.map(this::load)
Expand All @@ -102,17 +102,17 @@ public synchronized List<I18nTemplates> load() {
});
}

private List<I18nTemplates> load(I18nLoader loader) {
List<I18nTemplates> entries = loader.load();
private List<I18nTemplatesBundle> load(I18nLoader loader) {
List<I18nTemplatesBundle> entries = loader.load();
cachedResults.put(loader, entries);
return entries;
}

private void appendCurrentEntries() {
if (!currentEntries.isEmpty()) {
Map<I18nKey, String> copy = new LinkedHashMap<>(currentEntries);
I18nTemplates templates = new I18nTemplates(copy);
List<I18nTemplates> result = List.of(templates);
I18nTemplatesBundle templates = new I18nTemplatesBundle(copy);
List<I18nTemplatesBundle> result = List.of(templates);
I18nLoader loader = () -> result;
loaders.add(loader);
cachedResults.put(loader, result);
Expand Down
96 changes: 53 additions & 43 deletions src/main/java/com/coditory/quark/i18n/I18nMessagePackBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@

import com.coditory.quark.i18n.loader.I18nFileLoaderFactory;
import com.coditory.quark.i18n.loader.I18nLoader;
import com.coditory.quark.i18n.loader.I18nTemplates;
import com.coditory.quark.i18n.loader.I18nTemplatesBundle;
import org.jetbrains.annotations.NotNull;

import java.nio.file.FileSystem;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
Expand All @@ -22,7 +23,8 @@ public final class I18nMessagePackBuilder {
private final List<I18nPath> referenceFallbackPaths = new ArrayList<>();
private final List<I18nPath> messageFallbackPaths = new ArrayList<>();
private final List<I18nArgTransformer<?>> argTransformers = new ArrayList<>();
private boolean useJava8ArgumentTransformers = true;
private boolean transformJava8TimeTypes = true;
private boolean normalizeWhitespaces = true;
private I18nUnresolvedMessageHandler unresolvedMessageHandler = I18nUnresolvedMessageHandler.throwError();
private Locale defaultLocale;

Expand Down Expand Up @@ -79,7 +81,13 @@ public <T> I18nMessagePackBuilder addArgumentTransformer(Class<T> type, Function

@NotNull
public I18nMessagePackBuilder disableJava8ArgumentTransformers() {
this.useJava8ArgumentTransformers = false;
this.transformJava8TimeTypes = false;
return this;
}

@NotNull
public I18nMessagePackBuilder disableWhiteSpaceNormalization() {
this.normalizeWhitespaces = false;
return this;
}

Expand Down Expand Up @@ -136,81 +144,81 @@ public I18nMessagePackBuilder setDefaultLocale(@NotNull Locale defaultLocale) {
}

@NotNull
public I18nMessagePackBuilder setReferenceFallbackPaths(@NotNull List<String> paths) {
expectNonNull(paths, "paths");
public I18nMessagePackBuilder setReferenceFallbackKeyPrefixes(@NotNull List<String> keyPrefixes) {
expectNonNull(keyPrefixes, "keyPrefixes");
this.referenceFallbackPaths.clear();
paths.forEach(this::addReferenceFallbackPath);
keyPrefixes.forEach(this::addReferenceFallbackKeyPrefix);
return this;
}

@NotNull
public I18nMessagePackBuilder setReferenceFallbackPaths(@NotNull String... paths) {
expectNonNull(paths, "paths");
public I18nMessagePackBuilder setReferenceFallbackKeyPrefixes(@NotNull String... keyPrefixes) {
expectNonNull(keyPrefixes, "keyPrefixes");
this.referenceFallbackPaths.clear();
Arrays.stream(paths).forEach(this::addReferenceFallbackPath);
Arrays.stream(keyPrefixes).forEach(this::addReferenceFallbackKeyPrefix);
return this;
}

@NotNull
public I18nMessagePackBuilder addReferenceFallbackPath(@NotNull String path) {
expectNonBlank(path, "paths");
I18nPath i18nPath = I18nPath.of(path);
public I18nMessagePackBuilder addReferenceFallbackKeyPrefix(@NotNull String keyPrefix) {
expectNonBlank(keyPrefix, "keyPrefix");
I18nPath i18nPath = I18nPath.of(keyPrefix);
this.referenceFallbackPaths.add(i18nPath);
return this;
}

@NotNull
public I18nMessagePackBuilder setMessageFallbackPaths(@NotNull List<String> paths) {
expectNonNull(paths, "paths");
public I18nMessagePackBuilder setMessageFallbackKeyPrefixes(@NotNull List<String> keyPrefixes) {
expectNonNull(keyPrefixes, "keyPrefixes");
this.messageFallbackPaths.clear();
paths.forEach(this::addMessageFallbackPath);
keyPrefixes.forEach(this::addMessageFallbackKeyPrefix);
return this;
}

@NotNull
public I18nMessagePackBuilder setMessageFallbackPaths(@NotNull String... paths) {
expectNonNull(paths, "paths");
public I18nMessagePackBuilder setMessageFallbackKeyPrefixes(@NotNull String... keyPrefixes) {
expectNonNull(keyPrefixes, "keyPrefixes");
this.messageFallbackPaths.clear();
Arrays.stream(paths).forEach(this::addMessageFallbackPath);
Arrays.stream(keyPrefixes).forEach(this::addMessageFallbackKeyPrefix);
return this;
}

@NotNull
public I18nMessagePackBuilder addMessageFallbackPath(@NotNull String path) {
expectNonBlank(path, "paths");
I18nPath i18nPath = I18nPath.of(path);
public I18nMessagePackBuilder addMessageFallbackKeyPrefix(@NotNull String keyPrefix) {
expectNonBlank(keyPrefix, "keyPrefix");
I18nPath i18nPath = I18nPath.of(keyPrefix);
this.messageFallbackPaths.add(i18nPath);
return this;
}

@NotNull
public I18nMessagePackBuilder setFallbackPaths(@NotNull List<String> paths) {
expectNonNull(paths, "paths");
setMessageFallbackPaths(paths);
setReferenceFallbackPaths(paths);
public I18nMessagePackBuilder setFallbackKeyPrefixes(@NotNull List<String> keyPrefixes) {
expectNonNull(keyPrefixes, "keyPrefixes");
setMessageFallbackKeyPrefixes(keyPrefixes);
setReferenceFallbackKeyPrefixes(keyPrefixes);
return this;
}

@NotNull
public I18nMessagePackBuilder setFallbackPaths(@NotNull String... paths) {
expectNonNull(paths, "paths");
setMessageFallbackPaths(paths);
setReferenceFallbackPaths(paths);
public I18nMessagePackBuilder setFallbackKeyPrefixes(@NotNull String... keyPrefixes) {
expectNonNull(keyPrefixes, "keyPrefixes");
setMessageFallbackKeyPrefixes(keyPrefixes);
setReferenceFallbackKeyPrefixes(keyPrefixes);
return this;
}

@NotNull
public I18nMessagePackBuilder addFallbackPaths(@NotNull String path) {
expectNonBlank(path, "paths");
addMessageFallbackPath(path);
addReferenceFallbackPath(path);
public I18nMessagePackBuilder addFallbackKeyPrefix(@NotNull String keyPrefix) {
expectNonBlank(keyPrefix, "keyPrefix");
addMessageFallbackKeyPrefix(keyPrefix);
addReferenceFallbackKeyPrefix(keyPrefix);
return this;
}

@NotNull
public I18nMessagePack build() {
List<I18nTemplates> entries = loader.load();
return build(entries);
List<I18nTemplatesBundle> bundles = loader.load();
return build(bundles);
}

@NotNull
Expand All @@ -227,24 +235,26 @@ public Reloadable18nMessagePack buildAndWatchForChanges() {
return messagePack;
}

private I18nMessagePack build(List<I18nTemplates> entries) {
LocaleResolver localeResolver = LocaleResolver.of(defaultLocale, entries);
private I18nMessagePack build(List<I18nTemplatesBundle> bundles) {
bundles = TemplatesBundlePrefixes.prefix(bundles);
LocaleResolver localeResolver = LocaleResolver.of(defaultLocale, bundles);
I18nKeyGenerator messageKeyGenerator = new I18nKeyGenerator(defaultLocale, messageFallbackPaths, localeResolver);
MessageTemplateParser parser = buildMessageTemplateParser(entries, localeResolver);
Map<I18nKey, MessageTemplate> templates = parser.parseTemplates(entries);
MessageTemplateParser parser = buildMessageTemplateParser(bundles, localeResolver);
Map<I18nKey, MessageTemplate> templates = parser.parseTemplates(bundles);
return new ImmutableI18nMessagePack(templates, parser, unresolvedMessageHandler, messageKeyGenerator);
}

private MessageTemplateParser buildMessageTemplateParser(List<I18nTemplates> entries, LocaleResolver localeResolver) {
private MessageTemplateParser buildMessageTemplateParser(List<I18nTemplatesBundle> bundles, LocaleResolver localeResolver) {
I18nKeyGenerator referenceKeyGenerator = new I18nKeyGenerator(defaultLocale, referenceFallbackPaths, localeResolver);
ReferenceResolver referenceResolver = new ReferenceResolver(entries, referenceKeyGenerator);
ReferenceResolver referenceResolver = new ReferenceResolver(bundles, referenceKeyGenerator);
ArgumentResolver argumentResolver = buildArgumentResolver();
return new MessageTemplateParser(referenceResolver, argumentResolver);
MessageTemplateNormalizer messageTemplateNormalizer = new MessageTemplateNormalizer(normalizeWhitespaces);
return new MessageTemplateParser(referenceResolver, argumentResolver, messageTemplateNormalizer);
}

private ArgumentResolver buildArgumentResolver() {
List<I18nArgTransformer<?>> result = new ArrayList<>();
if (useJava8ArgumentTransformers) {
if (transformJava8TimeTypes) {
result.addAll(javaTimeI18nArgTransformers());
}
result.addAll(argTransformers);
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/coditory/quark/i18n/LocaleResolver.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.coditory.quark.i18n;

import com.coditory.quark.i18n.loader.I18nTemplates;
import com.coditory.quark.i18n.loader.I18nTemplatesBundle;

import java.util.ArrayList;
import java.util.Collections;
Expand All @@ -13,7 +13,7 @@
import static java.util.stream.Collectors.toSet;

final class LocaleResolver {
static LocaleResolver of(Locale defaultLocale, List<I18nTemplates> templates) {
static LocaleResolver of(Locale defaultLocale, List<I18nTemplatesBundle> templates) {
Set<Locale> availableLocales = templates.stream()
.flatMap(t -> t.templates().keySet().stream())
.map(I18nKey::locale)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.coditory.quark.i18n;

final class MessageTemplateNormalizer {
private final boolean normalizeWhiteSpaces;

MessageTemplateNormalizer(boolean normalizeWhiteSpaces) {
this.normalizeWhiteSpaces = normalizeWhiteSpaces;
}

String normalize(String template) {
if (!normalizeWhiteSpaces) {
return template;
}
return template.trim()
.replaceAll("\\s+", " ");
}
}
Loading

0 comments on commit 166cd11

Please sign in to comment.