168 lines
8.0 KiB
Java
168 lines
8.0 KiB
Java
package io.gitlab.jfronny.googlechat;
|
|
|
|
import io.gitlab.jfronny.commons.io.cache.FixedSizeMap;
|
|
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus;
|
|
import io.gitlab.jfronny.commons.throwable.Coerce;
|
|
import io.gitlab.jfronny.commons.throwable.Try;
|
|
import io.gitlab.jfronny.libjf.translate.api.Language;
|
|
import io.gitlab.jfronny.libjf.translate.api.TranslateService;
|
|
import net.fabricmc.api.EnvType;
|
|
import net.fabricmc.api.ModInitializer;
|
|
import net.fabricmc.loader.api.FabricLoader;
|
|
import net.minecraft.text.*;
|
|
|
|
import java.util.*;
|
|
import java.util.concurrent.ForkJoinPool;
|
|
import java.util.function.Function;
|
|
import java.util.regex.Matcher;
|
|
import java.util.regex.Pattern;
|
|
|
|
public class GoogleChat implements ModInitializer {
|
|
public static final String MOD_ID = "google-chat";
|
|
public static final SystemLoggerPlus LOGGER = SystemLoggerPlus.forName(MOD_ID);
|
|
public static TranslateService<?> TRANSLATE_SERVICE;
|
|
private static final boolean IS_SERVER = FabricLoader.getInstance().getEnvironmentType() == EnvType.SERVER;
|
|
|
|
private static final Map<Text, Text> s2ct = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
|
|
private static final Map<Text, Text> c2st = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
|
|
private static final Map<TextContent, TextContent> s2cc = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
|
|
private static final Map<TextContent, TextContent> c2sc = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
|
|
private static final Map<String, String> s2cs = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
|
|
private static final Map<String, String> c2ss = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
|
|
|
|
@Override
|
|
public void onInitialize() {
|
|
ForkJoinPool.commonPool().execute(Try.handle(Coerce.runnable(() -> TRANSLATE_SERVICE = TranslateService.getConfigured()), e -> LOGGER.error("Could not initialize translation service", e)));
|
|
}
|
|
|
|
public static void onConfigChange() {
|
|
synchronized (s2ct) {
|
|
s2ct.clear();
|
|
}
|
|
synchronized (c2st) {
|
|
c2st.clear();
|
|
}
|
|
synchronized (s2cc) {
|
|
s2cc.clear();
|
|
}
|
|
synchronized (c2sc) {
|
|
c2sc.clear();
|
|
}
|
|
synchronized (s2cs) {
|
|
s2cs.clear();
|
|
}
|
|
synchronized (c2ss) {
|
|
c2ss.clear();
|
|
}
|
|
}
|
|
|
|
public static Text translateIfNeeded(Text source, TranslationDirection direction, boolean respectRegex) {
|
|
if (source == null) return null;
|
|
if (direction.shouldSkipOutright()) return source;
|
|
String sourceString = toString(source);
|
|
if (respectRegex && direction.failsRegex(sourceString)) return source;
|
|
return computeIfAbsent2(direction == TranslationDirection.C2S ? c2st : s2ct, source, t -> {
|
|
MutableText translated;
|
|
if (GoogleChatConfig.Processing.desugar) {
|
|
translated = Text.literal(translateIfNeeded(sourceString, direction, true));
|
|
} else {
|
|
translated = MutableText.of(translateIfNeeded(t.getContent(), direction, false))
|
|
.setStyle(t.getStyle());
|
|
for (Text sibling : t.getSiblings()) {
|
|
translated.append(translateIfNeeded(sibling, direction, false));
|
|
}
|
|
}
|
|
if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Translated " + sourceString + " to " + toString(translated));
|
|
if (GoogleChatConfig.General.translationTooltip) {
|
|
return t.copy().styled(style -> addHover(style, Text.literal("Translated: ").append(translated)));
|
|
} else if (translated.getStyle().getHoverEvent() == null) {
|
|
return translated.styled(style -> addHover(style, Text.literal("Original: ").append(t)));
|
|
} else {
|
|
return translated;
|
|
}
|
|
});
|
|
}
|
|
|
|
private static String toString(Text text) {
|
|
StringBuilder sb = new StringBuilder();
|
|
text.asOrderedText().accept((index, style, codePoint) -> {
|
|
sb.append((char)codePoint);
|
|
return true;
|
|
});
|
|
return sb.toString();
|
|
}
|
|
|
|
public static TextContent translateIfNeeded(TextContent source, TranslationDirection direction, boolean respectRegex) {
|
|
if (source == null || source == PlainTextContent.EMPTY) return source;
|
|
if (direction.shouldSkipOutright()) return source;
|
|
String sourceString = toString(source);
|
|
if (respectRegex && direction.failsRegex(sourceString)) return source;
|
|
return computeIfAbsent2(direction == TranslationDirection.C2S ? c2sc : s2cc, source, t -> {
|
|
//TODO This method (and the check for translatable args) should be converted to a switch pattern when available
|
|
if (t instanceof TranslatableTextContent tx) {
|
|
Object[] args = tx.getArgs();
|
|
args = Arrays.copyOf(args, args.length);
|
|
// We're not translating TranslatableText, but are translating arguments
|
|
for (int i = 0; i < args.length; i++) {
|
|
if (args[i] instanceof Text tx1) args[i] = translateIfNeeded(tx1, direction, false);
|
|
else if (args[i] instanceof TextContent tx1) args[i] = translateIfNeeded(tx1, direction, false);
|
|
else if (args[i] instanceof String tx1) args[i] = translateIfNeeded(tx1, direction, false);
|
|
else args[i] = args[i];
|
|
}
|
|
return new TranslatableTextContent(tx.getKey(), translateIfNeeded(tx.getFallback(), direction, false), args);
|
|
} else if (t instanceof PlainTextContent.Literal tx) {
|
|
return new PlainTextContent.Literal(translateIfNeeded(tx.string(), direction, false));
|
|
} else {
|
|
// LOGGER.info("Unhandled text type: " + source.getClass() + " (" + source + ")");
|
|
return t;
|
|
}
|
|
});
|
|
}
|
|
|
|
private static String toString(TextContent text) {
|
|
StringBuilder sb = new StringBuilder();
|
|
text.visit(asString -> {
|
|
sb.append(asString);
|
|
return Optional.empty();
|
|
});
|
|
return sb.toString();
|
|
}
|
|
|
|
private static Style addHover(Style style, Text hoverText) {
|
|
return style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, hoverText));
|
|
}
|
|
|
|
private static final Pattern SURROUNDING_SPACE_PATTERN = Pattern.compile("^(\\s*)(.*\\S+)(\\s*)$", Pattern.MULTILINE);
|
|
public static String translateIfNeeded(String source, TranslationDirection direction, boolean respectRegex) {
|
|
if (source == null) return null;
|
|
if (direction.shouldSkipOutright()) return source;
|
|
if (respectRegex && direction.failsRegex(source)) return source;
|
|
return computeIfAbsent2(direction == TranslationDirection.C2S ? c2ss : s2cs, source, t -> {
|
|
try {
|
|
Matcher m = SURROUNDING_SPACE_PATTERN.matcher(source);
|
|
if (!m.find()) return source;
|
|
// Ignore generics since this is apparently not something java supports
|
|
@SuppressWarnings("rawtypes") TranslateService svc = GoogleChat.TRANSLATE_SERVICE;
|
|
if (svc == null) throw new NullPointerException("Translate service uninitialized");
|
|
Language sourceLang = svc.parseLang(direction.source());
|
|
Language targetLang = svc.parseLang(direction.target());
|
|
//noinspection unchecked
|
|
return m.group(1) + svc.translate(m.group(2), sourceLang, targetLang) + m.group(3);
|
|
} catch (Throwable e) {
|
|
LOGGER.error("Could not translate text: " + source, e);
|
|
return source;
|
|
}
|
|
});
|
|
}
|
|
|
|
private static <K, V> V computeIfAbsent2(Map<K, V> map, K key, Function<K, V> compute) {
|
|
if (!GoogleChatConfig.Advanced.async && !IS_SERVER) return map.computeIfAbsent(key, compute);
|
|
synchronized (map) {
|
|
if (map.containsKey(key)) return map.get(key);
|
|
V value = compute.apply(key);
|
|
map.put(key, value);
|
|
return value;
|
|
}
|
|
}
|
|
}
|