diff --git a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/TranslationLibLoader.java b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/TranslationLibLoader.java index 1123959..74d2734 100644 --- a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/TranslationLibLoader.java +++ b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/TranslationLibLoader.java @@ -42,7 +42,7 @@ public static TranslationLibLoader get() { private final Map translationTerms = new ConcurrentHashMap<>(); private final Set> activeStrings = Collections.synchronizedSet(new HashSet<>()); private final Set loadedTags = Collections.synchronizedSet(new HashSet<>()); - private final List updateQueue = Collections.synchronizedList(new ArrayList<>()); + private final Set updateQueue = Collections.synchronizedSet(new HashSet<>()); private ScheduledFuture refreshFuture; @@ -56,21 +56,30 @@ protected TranslationLibLoader(LoaderSettings settings) { } public void refresh() { - List terms = new ArrayList<>(this.updateQueue); + this.sendTermUpdates(); try { + this.loadTermsByTag(new HashSet<>(this.loadedTags)); + this.refreshStrings(); + } catch (HttpException e) { + log.error("Exception caught while updating translations: {}", e.statusCode(), e); + } catch (Exception e) { + log.error("Exception caught while updating translations", e); + } + } + + public void sendTermUpdates() { + try { + List terms = new ArrayList<>(this.updateQueue); for (TranslationTerm term : terms) { RestStatus status = this.getTranslationLibRestApi().termUpdate(term, this.settings.aggressiveUpdates()); if (status.isSuccess()) { - this.updateQueue.clear(); + this.updateQueue.remove(term); } else { log.warn("Service responded to an update with error: {} cause={}", status.getMessage(), status.getError()); } } - - this.loadTermsByTag(new HashSet<>(this.loadedTags)); - this.refreshStrings(); } catch (HttpException e) { - log.error("Exception caught while updating translations: {}", e.statusCode(), e); + log.error("Exception caught while sending term updates: {}", e.statusCode(), e); } catch (Exception e) { log.error("Exception caught while updating translations", e); } @@ -80,9 +89,13 @@ public void loadTermsByTag(Collection tags) { this.clearAllTerms(); for (String tag : tags) { log.info("Loading terms with tag {}...", tag); - Collection terms = Arrays.asList(this.getTranslationLibRestApi().exportTerms(tag)); - this.loadTerms(terms, false); - this.loadedTags.add(tag); + try { + TranslationTerm[] terms = this.getTranslationLibRestApi().exportTerms(tag); + this.loadTerms(Arrays.asList(terms), false); + this.loadedTags.add(tag); + } catch (HttpException e) { + throw new IllegalStateException("Failed to load terms with tag " + tag, e); + } } } diff --git a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/client/RestClient.java b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/client/RestClient.java index 504e72e..83bfedd 100644 --- a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/client/RestClient.java +++ b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/client/RestClient.java @@ -4,6 +4,7 @@ import io.avaje.http.client.HttpClient; import io.avaje.http.client.HttpClientRequest; import io.avaje.http.client.RequestIntercept; +import io.avaje.http.client.SimpleRetryHandler; import io.avaje.http.client.gson.GsonBodyAdapter; import lombok.RequiredArgsConstructor; @@ -27,6 +28,7 @@ public void beforeRequest(HttpClientRequest request) { request.header("auth", token); } }) + .retryHandler(new SimpleRetryHandler(3, 1000, 0)) .build(); } diff --git a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalString.java b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalString.java index 48a3f48..1cb9531 100644 --- a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalString.java +++ b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalString.java @@ -2,51 +2,17 @@ import eu.mizerak.alemiz.translationlib.common.TranslationLibLoader; import eu.mizerak.alemiz.translationlib.common.structure.TranslationTerm; -import lombok.Getter; -import java.util.*; -import java.util.concurrent.ConcurrentHashMap; +import java.util.Locale; import java.util.function.Function; -public class LocalString { - private static final Map formatters = new ConcurrentHashMap<>(); - private static final Map, TranslationContext.Factory> contextFactories = new ConcurrentHashMap<>(); +public interface LocalString { - public static void registerFormatter(StringFormatter formatter) { - formatters.put(formatter, new FormatterHandler(formatter)); - } - - public static void unregisterFormatter(StringFormatter formatter) { - formatters.remove(formatter); - } - - public static void registerContextFactory(Class clazz, TranslationContext.Factory factory) { - contextFactories.put(clazz, factory); - } - - public static void unregisterContextFactory(Class clazz) { - contextFactories.remove(clazz); - } - - - @Getter - private final String key; - private final TranslationLibLoader loader; - - @Getter - private final String fallbackMessage; - - @Getter - private TranslationTerm term; - private final Map formatted = new ConcurrentHashMap<>(); - - private final Map, String>> arguments = new TreeMap<>(); - - public static LocalString from(String key, String defaultText) { + static LocalString from(String key, String defaultText) { return from(key, defaultText, TranslationLibLoader.get()); } - public static LocalString from(String key, String defaultText, TranslationLibLoader loader) { + static LocalString from(String key, String defaultText, TranslationLibLoader loader) { TranslationTerm term = loader.getTranslationterm(key); if (term == null) { term = TranslationTerm.createEmpty(key, defaultText, loader.getDefaultLocale()); @@ -57,104 +23,35 @@ public static LocalString from(String key, String defaultText, Translatio throw new IllegalStateException("Term " + key + " has no default translation"); } } - return new LocalString<>(key, term, loader, defaultText); + LocalStringBase string = new LocalStringImpl<>(key, term, loader, defaultText); + string.enableReload(true); + return string; } - private LocalString(String key, TranslationTerm term, TranslationLibLoader loader, String fallbackMessage) { - this.key = key; - this.loader = loader; - this.term = term; - this.fallbackMessage = fallbackMessage; - this.enableReload(true); + static LocalString immutable(String text) { + TranslationTerm term = TranslationTerm.createEmpty("static", text, StaticLocalString.DEFAULT_LOCALE); + return new StaticLocalString<>(term); } - public LocalString reload() { - TranslationTerm term = this.loader.getTranslationterm(key); - if (term == null) { - return this; - } + String getKey(); - if (term.hasLocale(this.loader.getDefaultLocale())) { - this.term = term; - this.formatted.clear(); - } - return this; - } + LocalString reload(); - public LocalString enableReload(boolean enable) { - if (enable) { - this.loader.onStringSubscribe(this); - } else { - this.loader.onStringUnsubscribe(this); - } - return this; - } + LocalString enableReload(boolean enable); - public LocalString withArgument(String name, Object argument) { + default LocalString withArgument(String name, Object argument) { return this.withArgument(name, ctx -> String.valueOf(argument)); } - public LocalString withArgument(String name, Function, String> mapper) { - this.arguments.put(name, mapper); - return this; - } + LocalString withArgument(String name, Function, String> mapper); - public LocalString clearArguments() { - this.arguments.clear(); - return this; - } - - protected Map, String>> getArguments() { - return this.arguments; - } - - public Collection getArgumentNames() { - return Collections.unmodifiableCollection(this.arguments.keySet()); - } - - public TranslationContext getTranslated(T object) { - TranslationContext.Factory factory = contextFactories.get(object.getClass()); - if (factory == null) { - factory = contextFactories.get(object.getClass().getSuperclass()); - if (factory == null) { - throw new IllegalStateException("Unregistered factory for type " + object.getClass()); - } - } - return ((TranslationContext.Factory) factory).create(object, this); - } - - public String getText(T object) { - return this.getTranslated(object).getText(); - } - - public String getFormatted(Locale locale) { - String formatted = this.formatted.get(locale); - if (formatted != null) { - return formatted; - } - - String text = term.getText(locale); - if (text == null || text.trim().isEmpty()) { - text = term.getText(loader.getDefaultLocale()); - } + LocalString clearArguments(); - this.formatted.put(locale, formatted = this.format(text)); - return formatted; - } + String getFormatted(); - private String format(String text) { - for (FormatterHandler formatter : formatters.values()) { - text = formatter.format(text); - } - return text; - } + String getFormatted(Locale locale); - public void uploadFallbackMessage() { - this.loader.addTermUpdate(TranslationTerm.createEmpty(key, this.fallbackMessage, loader.getDefaultLocale())); - } + String getText(T object); - @Override - public String toString() { - return "LocalString(key=" + this.key + ")"; - } + void uploadFallbackMessage(); } diff --git a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalStringBase.java b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalStringBase.java new file mode 100644 index 0000000..c88c7fb --- /dev/null +++ b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalStringBase.java @@ -0,0 +1,120 @@ +package eu.mizerak.alemiz.translationlib.common.string; + +import eu.mizerak.alemiz.translationlib.common.structure.TranslationTerm; +import lombok.Getter; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +public abstract class LocalStringBase implements LocalString { + private static final Map formatters = new ConcurrentHashMap<>(); + private static final Map, TranslationContext.Factory> contextFactories = new ConcurrentHashMap<>(); + + public static void registerFormatter(StringFormatter formatter) { + formatters.put(formatter, new FormatterHandler(formatter)); + } + + public static void unregisterFormatter(StringFormatter formatter) { + formatters.remove(formatter); + } + + public static void registerContextFactory(Class clazz, TranslationContext.Factory factory) { + contextFactories.put(clazz, factory); + } + + public static void unregisterContextFactory(Class clazz) { + contextFactories.remove(clazz); + } + + + private final String key; + + @Getter + private TranslationTerm term; + + protected final Map formatted = new ConcurrentHashMap<>(); + protected final Map, String>> arguments = new TreeMap<>(); + + protected LocalStringBase(String key, TranslationTerm term) { + this.key = key; + this.term = term; + } + + @Override + public LocalStringBase withArgument(String name, Object argument) { + return this.withArgument(name, ctx -> String.valueOf(argument)); + } + + @Override + public LocalStringBase withArgument(String name, Function, String> mapper) { + this.arguments.put(name, mapper); + return this; + } + + @Override + public LocalStringBase clearArguments() { + this.arguments.clear(); + return this; + } + + public Collection getArgumentNames() { + return Collections.unmodifiableCollection(this.arguments.keySet()); + } + + @Override + public String getText(T object) { + TranslationContext.Factory factory = contextFactories.get(object.getClass()); + if (factory == null) { + factory = contextFactories.get(object.getClass().getSuperclass()); + if (factory == null) { + throw new IllegalStateException("Unregistered factory for type " + object.getClass()); + } + } + + TranslationContext ctx = ((TranslationContext.Factory) factory).create(object); + + String formatted = this.getFormatted(ctx.getLocale()); + if (!this.arguments.isEmpty()) { + for (Map.Entry, String>> entry : this.arguments.entrySet()) { + formatted = formatted.replaceAll("\\{" + entry.getKey() + "\\}", entry.getValue().apply(ctx)); + } + } + return formatted; + } + + @Override + public String getFormatted(Locale locale) { + String formatted = this.formatted.get(locale); + if (formatted != null) { + return formatted; + } + + String text = term.getText(locale); + if (text == null || text.trim().isEmpty()) { + text = term.getText(this.getDefaultLocale()); + } + + this.formatted.put(locale, formatted = this.format(text)); + return formatted; + } + + private String format(String text) { + for (FormatterHandler formatter : formatters.values()) { + text = formatter.format(text); + } + return text; + } + + public abstract Locale getDefaultLocale(); + + @Override + public String getKey() { + return this.key; + } + + @Override + public String toString() { + return "LocalString(key=" + this.key + ")"; + } +} diff --git a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalStringImpl.java b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalStringImpl.java new file mode 100644 index 0000000..49b3134 --- /dev/null +++ b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/LocalStringImpl.java @@ -0,0 +1,65 @@ +package eu.mizerak.alemiz.translationlib.common.string; + +import eu.mizerak.alemiz.translationlib.common.TranslationLibLoader; +import eu.mizerak.alemiz.translationlib.common.structure.TranslationTerm; +import lombok.Getter; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + +public class LocalStringImpl extends LocalStringBase { + + private final TranslationLibLoader loader; + + @Getter + private final String fallbackMessage; + + @Getter + private TranslationTerm term; + + protected LocalStringImpl(String key, TranslationTerm term, TranslationLibLoader loader, String fallbackMessage) { + super(key, term); + this.loader = loader; + this.fallbackMessage = fallbackMessage; + } + + @Override + public LocalStringImpl reload() { + TranslationTerm term = this.loader.getTranslationterm(this.getKey()); + if (term == null) { + return this; + } + + if (term.hasLocale(this.loader.getDefaultLocale())) { + this.term = term; + this.formatted.clear(); + } + return this; + } + + @Override + public LocalStringImpl enableReload(boolean enable) { + if (enable) { + this.loader.onStringSubscribe(this); + } else { + this.loader.onStringUnsubscribe(this); + } + return this; + } + + @Override + public String getFormatted() { + return this.getFormatted(this.loader.getDefaultLocale()); + } + + @Override + public void uploadFallbackMessage() { + this.loader.addTermUpdate(TranslationTerm.createEmpty(this.getKey(), this.fallbackMessage, loader.getDefaultLocale())); + } + + @Override + public Locale getDefaultLocale() { + return this.loader.getDefaultLocale(); + } +} diff --git a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/StaticLocalString.java b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/StaticLocalString.java new file mode 100644 index 0000000..4fb3ff6 --- /dev/null +++ b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/StaticLocalString.java @@ -0,0 +1,48 @@ +package eu.mizerak.alemiz.translationlib.common.string; + +import eu.mizerak.alemiz.translationlib.common.structure.TranslationTerm; + +import java.util.Locale; + +public class StaticLocalString extends LocalStringBase { + public static final Locale DEFAULT_LOCALE = Locale.ENGLISH; + + protected StaticLocalString(TranslationTerm term) { + super(term.getKey(), term); + } + + @Override + public LocalStringBase reload() { + throw new UnsupportedOperationException("Static string does not support reload"); + } + + @Override + public LocalStringBase enableReload(boolean enable) { + throw new UnsupportedOperationException("Static string does not support reload"); + } + + @Override + public String getFormatted() { + return this.getFormatted(DEFAULT_LOCALE); + } + + @Override + public void uploadFallbackMessage() { + throw new UnsupportedOperationException("Static can not be uploaded"); + } + + @Override + public String getFormatted(Locale locale) { + return super.getFormatted(DEFAULT_LOCALE); + } + + @Override + public Locale getDefaultLocale() { + return DEFAULT_LOCALE; + } + + @Override + public String toString() { + return "StaticLocalString(text=" + this.getFormatted() + ")"; + } +} diff --git a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/TranslationContext.java b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/TranslationContext.java index f5d8e0d..bf4b6ac 100644 --- a/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/TranslationContext.java +++ b/common/src/main/java/eu/mizerak/alemiz/translationlib/common/string/TranslationContext.java @@ -1,36 +1,21 @@ package eu.mizerak.alemiz.translationlib.common.string; import java.util.Locale; -import java.util.Map; -import java.util.function.Function; public abstract class TranslationContext { - - private final LocalString string; private final T object; - public TranslationContext(T object, LocalString string) { + public TranslationContext(T object) { this.object = object; - this.string = string; } public abstract Locale getLocale(); - public String getText() { - String formatted = this.string.getFormatted(this.getLocale()); - if (!this.string.getArguments().isEmpty()) { - for (Map.Entry, String>> entry : this.string.getArguments().entrySet()) { - formatted = formatted.replaceAll("\\{" + entry.getKey() + "\\}", entry.getValue().apply(this)); - } - } - return formatted; - } - public T getObject() { return this.object; } public interface Factory { - TranslationContext create(T object, LocalString string); + TranslationContext create(T object); } } diff --git a/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TestBase.java b/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TestBase.java index 41ce28c..250183b 100644 --- a/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TestBase.java +++ b/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TestBase.java @@ -1,6 +1,6 @@ package eu.mizerak.alemiz.translationlib.common; -import eu.mizerak.alemiz.translationlib.common.string.LocalString; +import eu.mizerak.alemiz.translationlib.common.string.LocalStringBase; import eu.mizerak.alemiz.translationlib.common.string.StringFormatter; import eu.mizerak.alemiz.translationlib.common.string.TranslationContext; import eu.mizerak.alemiz.translationlib.common.structure.TranslationTerm; @@ -35,7 +35,7 @@ public void init() { TranslationLibLoader.setDefault(loader); - LocalString.registerContextFactory(User.class, (TranslationContext.Factory) (object, string) -> new TranslationContext(object, string) { + LocalStringBase.registerContextFactory(User.class, (TranslationContext.Factory) (object) -> new TranslationContext<>(object) { @Override public Locale getLocale() { return this.getObject().getLocale(); @@ -45,7 +45,7 @@ public Locale getLocale() { @AfterEach public void finish() { - this.formatters.forEach(LocalString::unregisterFormatter); + this.formatters.forEach(LocalStringBase::unregisterFormatter); } public TranslationTerm addTerm(String key, String tag, Locale locale, String translation) { @@ -63,7 +63,7 @@ public TranslationTerm addTerm(TranslationTerm term) { public void registerFormatter(StringFormatter formatter) { this.formatters.add(formatter); - LocalString.registerFormatter(formatter); + LocalStringBase.registerFormatter(formatter); } public TranslationLibLoader loader() { diff --git a/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TranslateTest.java b/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TranslateTest.java index 23a8edf..d296a54 100644 --- a/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TranslateTest.java +++ b/common/src/test/java/eu/mizerak/alemiz/translationlib/common/TranslateTest.java @@ -6,7 +6,6 @@ import eu.mizerak.alemiz.translationlib.common.structure.User; import org.junit.jupiter.api.Test; -import java.util.Collections; import java.util.Locale; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -18,7 +17,7 @@ public void testFormatting() { this.registerFormatter(new SimpleColorFormatter()); LocalString string = LocalString.from("test_string", "Hello world!"); - assertEquals("&bHello &cworld!", string.getTranslated(User.ENGLISH).getText()); + assertEquals("&bHello &cworld!", string.getText(User.ENGLISH)); } @Test @@ -41,4 +40,17 @@ public void testUserArgs() { assertEquals("&bHello " + User.ENGLISH.getName(), string.getText(User.ENGLISH)); } + + @Test + public void testStatic() { + LocalString string = LocalString.immutable("Hello {user}") + .withArgument("user", ctx -> ctx.getObject().getName()); + + assertEquals("Hello " + User.ENGLISH.getName(), string.getText(User.ENGLISH)); + + LocalString string1 = LocalString.immutable("Hi {user}") + .withArgument("user", ctx -> ctx.getObject().getName()); + + assertEquals("Hi " + User.ENGLISH.getName(), string1.getText(User.ENGLISH)); + } } diff --git a/service/src/main/java/eu/mizerak/alemiz/translationlib/service/TranslationLibService.java b/service/src/main/java/eu/mizerak/alemiz/translationlib/service/TranslationLibService.java index 8d3dedc..1948908 100644 --- a/service/src/main/java/eu/mizerak/alemiz/translationlib/service/TranslationLibService.java +++ b/service/src/main/java/eu/mizerak/alemiz/translationlib/service/TranslationLibService.java @@ -135,15 +135,17 @@ public TranslationLibService(Configuration configuration) { this.server.get("/", ctx -> ctx.result("Hello World")); this.server.routes(() -> scope.list(WebRoutes.class).forEach(WebRoutes::registerRoutes)); + this.server.before("/*", ctx -> log.info("Accessed: {}, {}", ctx.req().getRequestURI(), ctx.method())); + this.server.exception(HttpException.class, (e, ctx) -> { // Handle general http exceptions ctx.json(RestStatus.create(RestStatus.Status.ERROR, e.getClass().getSimpleName(), e.getMessage() + ": " + e.bodyAsString())); - log.error("Exception in route {}: status={} msg={}", ctx.url(), e.statusCode(), e.bodyAsString(), e); + log.error("Exception in route {}: status={} msg={}", ctx.req().getRequestURI(), e.statusCode(), e.bodyAsString(), e); }); this.server.exception(Exception.class, (e, ctx) -> { ctx.json(RestStatus.create(e)); - log.error("Exception in route {}", ctx.url(), e); + log.error("Exception in route {}", ctx.req().getRequestURI(), e); }); // Start server