Compare commits

..

No commits in common. "master" and "0.7.0" have entirely different histories.

15 changed files with 69 additions and 326 deletions

View File

@ -1,16 +1,16 @@
plugins { plugins {
id("jfmod") version "1.6-SNAPSHOT" id("jfmod") version "1.5-SNAPSHOT"
} }
allprojects { group = "io.gitlab.jfronny" } allprojects { group = "io.gitlab.jfronny" }
base.archivesName = "google-chat" base.archivesName = "google-chat"
val fabricVersion = "0.83.0+1.20"
jfMod { jfMod {
minecraftVersion = "1.21.1" minecraftVersion = "1.20.1"
yarn("build.3") yarn("build.9")
loaderVersion = "0.16.5" loaderVersion = "0.14.21"
libJfVersion = "3.17.0" libJfVersion = "3.10.2"
fabricApiVersion = "0.104.0+1.21.1"
modrinth { modrinth {
projectId = "google-chat" projectId = "google-chat"
@ -25,19 +25,12 @@ jfMod {
} }
dependencies { dependencies {
modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v2") modImplementation("io.gitlab.jfronny.libjf:libjf-config-core-v1:${jfMod.libJfVersion.get()}")
modImplementation("io.gitlab.jfronny.libjf:libjf-translate-v1") modImplementation("io.gitlab.jfronny.libjf:libjf-translate-v1:${jfMod.libJfVersion.get()}")
include(modImplementation("net.fabricmc.fabric-api:fabric-message-api-v1")!!) include(modImplementation(fabricApi.module("fabric-message-api-v1", fabricVersion))!!)
// Keybind
modCompileOnly("net.fabricmc.fabric-api:fabric-key-binding-api-v1")
modCompileOnly("net.fabricmc.fabric-api:fabric-lifecycle-events-v1")
// Dev env // Dev env
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny") modLocalRuntime("io.gitlab.jfronny.libjf:libjf-config-ui-tiny-v1:${jfMod.libJfVersion.get()}")
modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil") modLocalRuntime("io.gitlab.jfronny.libjf:libjf-devutil:${jfMod.libJfVersion.get()}")
modLocalRuntime("net.fabricmc.fabric-api:fabric-resource-loader-v0") modLocalRuntime("com.terraformersmc:modmenu:7.1.0")
modLocalRuntime("com.terraformersmc:modmenu:11.0.2")
// for modmenu
modLocalRuntime("net.fabricmc.fabric-api:fabric-screen-api-v1")
modLocalRuntime("net.fabricmc.fabric-api:fabric-key-binding-api-v1")
} }

View File

@ -1,7 +1,7 @@
pluginManagement { pluginManagement {
repositories { repositories {
maven("https://maven.frohnmeyer-wds.de/mirrors") maven("https://maven.fabricmc.net/") // FabricMC
maven("https://maven.neoforged.net/releases") maven("https://maven.frohnmeyer-wds.de/artifacts") // scripts
gradlePluginPortal() gradlePluginPortal()
} }
} }

View File

@ -1,78 +0,0 @@
package io.gitlab.jfronny.googlechat.client;
import com.mojang.brigadier.context.CommandContextBuilder;
import com.mojang.brigadier.context.ParsedArgument;
import com.mojang.brigadier.context.ParsedCommandNode;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.mojang.brigadier.tree.RootCommandNode;
import io.gitlab.jfronny.googlechat.GoogleChat;
import io.gitlab.jfronny.googlechat.GoogleChatConfig;
import io.gitlab.jfronny.libjf.config.api.v2.ConfigHolder;
import io.gitlab.jfronny.libjf.config.api.v2.ConfigInstance;
import net.fabricmc.api.ClientModInitializer;
import net.fabricmc.fabric.api.client.event.lifecycle.v1.ClientTickEvents;
import net.fabricmc.fabric.api.client.keybinding.v1.KeyBindingHelper;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.client.option.KeyBinding;
import net.minecraft.client.util.InputUtil;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.MessageArgumentType;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public class GoogleChatClient implements ClientModInitializer {
@Override
public void onInitializeClient() {
ConfigInstance ci = ConfigHolder.getInstance().get(GoogleChat.MOD_ID);
if (ci != null
&& FabricLoader.getInstance().isModLoaded("fabric-key-binding-api-v1")
&& FabricLoader.getInstance().isModLoaded("fabric-lifecycle-events-v1")) {
setupKeybind(ci);
}
}
private static void setupKeybind(@NotNull ConfigInstance ci) {
// Factored out to prevent loading classes if mods are not present
KeyBinding keyBinding = KeyBindingHelper.registerKeyBinding(new KeyBinding(
"key." + GoogleChat.MOD_ID + ".toggle",
InputUtil.Type.KEYSYM,
-1,
KeyBinding.MULTIPLAYER_CATEGORY
));
ClientTickEvents.END_CLIENT_TICK.register(client -> {
if (keyBinding.wasPressed()) {
GoogleChatConfig.General.enabled = !GoogleChatConfig.General.enabled;
ci.write();
}
});
}
public static Optional<String> reconstitute(String source, CommandContextBuilder<CommandSource> results) {
StringBuilder builder = new StringBuilder();
for (ParsedCommandNode<CommandSource> node : results.getNodes()) {
switch (node.getNode()) {
case ArgumentCommandNode<?, ?> arg -> {
ParsedArgument<CommandSource, ?> pa = results.getArguments().get(arg.getName());
String datum = pa.getRange().get(source);
if (pa.getResult() instanceof MessageArgumentType.MessageFormat fmt) builder.append(fmt.contents());
else builder.append(datum);
builder.append(' ');
}
case LiteralCommandNode<?> lit -> {
builder.append(lit.getLiteral());
builder.append(' ');
}
case RootCommandNode<?> root -> {}
default -> {
return Optional.empty();
}
}
}
if (GoogleChatConfig.Advanced.debugLogs) {
GoogleChat.LOGGER.info("Reconstituted command: {0} from {1}", builder.substring(0, builder.length() - 1), source);
}
return Optional.of(builder.substring(0, builder.length() - 1));
}
}

View File

@ -9,7 +9,7 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable;
@Mixin(ChatScreen.class) @Mixin(ChatScreen.class)
public class ChatScreenMixin { public class ChatScreenMixin {
@ModifyVariable(method = "sendMessage(Ljava/lang/String;Z)V", at = @At(value = "HEAD"), argsOnly = true, ordinal = 0) @ModifyVariable(method = "sendMessage(Ljava/lang/String;Z)Z", at = @At(value = "HEAD"), argsOnly = true, ordinal = 0)
String googlechat$translateChatText(String chatText) { String googlechat$translateChatText(String chatText) {
if (chatText.startsWith("/")) return chatText; // Bypass for client-side commands (Carpet, ...) if (chatText.startsWith("/")) return chatText; // Bypass for client-side commands (Carpet, ...)
return GoogleChat.translateIfNeeded(chatText, TranslationDirection.C2S, true); return GoogleChat.translateIfNeeded(chatText, TranslationDirection.C2S, true);

View File

@ -1,56 +0,0 @@
package io.gitlab.jfronny.googlechat.client.mixin;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.context.ParsedArgument;
import io.gitlab.jfronny.googlechat.GoogleChat;
import io.gitlab.jfronny.googlechat.GoogleChatConfig;
import io.gitlab.jfronny.googlechat.TranslationDirection;
import io.gitlab.jfronny.googlechat.client.GoogleChatClient;
import net.minecraft.client.network.ClientPlayNetworkHandler;
import net.minecraft.command.CommandSource;
import net.minecraft.command.argument.MessageArgumentType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.ModifyVariable;
import org.spongepowered.asm.mixin.injection.Redirect;
import java.util.concurrent.atomic.AtomicReference;
@Mixin(ClientPlayNetworkHandler.class)
public class ClientPlayNetworkHandlerMixin {
@Shadow private ParseResults<CommandSource> parse(String command) {
throw new IllegalStateException("Mixin failed to apply");
}
@Unique private final AtomicReference<ParseResults<CommandSource>> googlechat$lastResults = new AtomicReference<>(null);
@ModifyVariable(method = "sendChatCommand(Ljava/lang/String;)V", at = @At("HEAD"), ordinal = 0, argsOnly = true)
public String translateCommand(String command) {
ParseResults<CommandSource> results = parse(command);
if (!GoogleChatConfig.General.enabled || !GoogleChatConfig.Advanced.translateMessageArguments || !results.getExceptions().isEmpty()) return command;
final boolean[] modified = {false};
results.getContext().getArguments().replaceAll((key, value) -> {
if (value.getResult() instanceof MessageArgumentType.MessageFormat fmt) {
if (fmt.selectors().length != 0) return value; // Selectors contain position information, which this would break
fmt = new MessageArgumentType.MessageFormat(GoogleChat.translateIfNeeded(fmt.contents(), TranslationDirection.C2S, true), new MessageArgumentType.MessageSelector[0]);
modified[0] = true;
return new ParsedArgument<>(value.getRange().getStart(), value.getRange().getEnd(), fmt);
}
return value;
});
if (!modified[0]) return command;
return GoogleChatClient.reconstitute(command, results.getContext()).map(s -> {
googlechat$lastResults.set(results);
return s;
}).orElse(command);
}
@Redirect(method = "sendChatCommand(Ljava/lang/String;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayNetworkHandler;parse(Ljava/lang/String;)Lcom/mojang/brigadier/ParseResults;"))
public ParseResults<CommandSource> googlechat$modifyArguments(ClientPlayNetworkHandler handler, String command) {
ParseResults<CommandSource> results = googlechat$lastResults.getAndSet(null);
if (results != null) return results;
return parse(command);
}
}

View File

@ -21,8 +21,6 @@ public abstract class MessageHandlerMixin {
@Shadow @Final private MinecraftClient client; @Shadow @Final private MinecraftClient client;
@Shadow protected abstract void narrate(MessageType.Parameters params, Text message); @Shadow protected abstract void narrate(MessageType.Parameters params, Text message);
//TODO somehow modify applyChatDecoration, since that is the only method that knows the real text
@Unique CompletableFuture<Void> googlechat$currentFuture = CompletableFuture.completedFuture(null); @Unique CompletableFuture<Void> googlechat$currentFuture = CompletableFuture.completedFuture(null);
@Unique ThreadLocal<GameProfile> sender = new ThreadLocal<>(); @Unique ThreadLocal<GameProfile> sender = new ThreadLocal<>();
@ -80,12 +78,10 @@ public abstract class MessageHandlerMixin {
this.sender.remove(); this.sender.remove();
} }
@Unique
private Text googlechat$s2c(Text message) { private Text googlechat$s2c(Text message) {
return GoogleChat.translateIfNeeded(message, TranslationDirection.S2C, true); return GoogleChat.translateIfNeeded(message, TranslationDirection.S2C, true);
} }
@Unique
private void googlechat$schedule(Runnable runnable) { private void googlechat$schedule(Runnable runnable) {
if (!GoogleChatConfig.Advanced.async) runnable.run(); if (!GoogleChatConfig.Advanced.async) runnable.run();
else googlechat$currentFuture.whenCompleteAsync((_1, _2) -> runnable.run()).exceptionally(throwable -> { else googlechat$currentFuture.whenCompleteAsync((_1, _2) -> runnable.run()).exceptionally(throwable -> {
@ -94,7 +90,6 @@ public abstract class MessageHandlerMixin {
}); });
} }
@Unique
private boolean googlechat$shouldTranslate() { private boolean googlechat$shouldTranslate() {
if (!GoogleChatConfig.General.enabled) return false; if (!GoogleChatConfig.General.enabled) return false;
if (client == null || client.player == null) return false; if (client == null || client.player == null) return false;

View File

@ -6,7 +6,6 @@
"client": [ "client": [
"ChatScreenMixin", "ChatScreenMixin",
"ClientPlayerEntityMixin", "ClientPlayerEntityMixin",
"ClientPlayNetworkHandlerMixin",
"MessageHandlerMixin" "MessageHandlerMixin"
], ],
"injectors": { "injectors": {

View File

@ -1,27 +1,20 @@
package io.gitlab.jfronny.googlechat; package io.gitlab.jfronny.googlechat;
import io.gitlab.jfronny.commons.io.cache.FixedSizeMap; import io.gitlab.jfronny.commons.cache.FixedSizeMap;
import io.gitlab.jfronny.commons.logger.SystemLoggerPlus; import io.gitlab.jfronny.commons.log.Logger;
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.Language;
import io.gitlab.jfronny.libjf.translate.api.TranslateService; import io.gitlab.jfronny.libjf.translate.api.TranslateService;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.ModInitializer; import net.fabricmc.api.ModInitializer;
import net.fabricmc.loader.api.FabricLoader;
import net.minecraft.text.*; import net.minecraft.text.*;
import java.util.*; import java.util.*;
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.ForkJoinPool;
import java.util.function.Function; import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class GoogleChat implements ModInitializer { public class GoogleChat implements ModInitializer {
public static final String MOD_ID = "google-chat"; public static final String MOD_ID = "google-chat";
public static final SystemLoggerPlus LOGGER = SystemLoggerPlus.forName(MOD_ID); public static final Logger LOGGER = Logger.forName(MOD_ID);
public static TranslateService<?> TRANSLATE_SERVICE; 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> s2ct = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
private static final Map<Text, Text> c2st = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize); private static final Map<Text, Text> c2st = new FixedSizeMap<>(GoogleChatConfig.Advanced.cacheSize);
@ -32,7 +25,7 @@ public class GoogleChat implements ModInitializer {
@Override @Override
public void onInitialize() { public void onInitialize() {
ForkJoinPool.commonPool().execute(Try.handle(Coerce.runnable(() -> TRANSLATE_SERVICE = TranslateService.getConfigured()), e -> LOGGER.error("Could not initialize translation service", e))); ForkJoinPool.commonPool().execute(() -> TRANSLATE_SERVICE = TranslateService.getConfigured());
} }
public static void onConfigChange() { public static void onConfigChange() {
@ -72,7 +65,7 @@ public class GoogleChat implements ModInitializer {
translated.append(translateIfNeeded(sibling, direction, false)); translated.append(translateIfNeeded(sibling, direction, false));
} }
} }
if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Translated {0} to {1}", sourceString, toString(translated)); if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Translated " + sourceString + " to " + toString(translated));
if (GoogleChatConfig.General.translationTooltip) { if (GoogleChatConfig.General.translationTooltip) {
return t.copy().styled(style -> addHover(style, Text.literal("Translated: ").append(translated))); return t.copy().styled(style -> addHover(style, Text.literal("Translated: ").append(translated)));
} else if (translated.getStyle().getHoverEvent() == null) { } else if (translated.getStyle().getHoverEvent() == null) {
@ -93,7 +86,7 @@ public class GoogleChat implements ModInitializer {
} }
public static TextContent translateIfNeeded(TextContent source, TranslationDirection direction, boolean respectRegex) { public static TextContent translateIfNeeded(TextContent source, TranslationDirection direction, boolean respectRegex) {
if (source == null || source == PlainTextContent.EMPTY) return source; if (source == null) return null;
if (direction.shouldSkipOutright()) return source; if (direction.shouldSkipOutright()) return source;
String sourceString = toString(source); String sourceString = toString(source);
if (respectRegex && direction.failsRegex(sourceString)) return source; if (respectRegex && direction.failsRegex(sourceString)) return source;
@ -110,10 +103,10 @@ public class GoogleChat implements ModInitializer {
else args[i] = args[i]; else args[i] = args[i];
} }
return new TranslatableTextContent(tx.getKey(), translateIfNeeded(tx.getFallback(), direction, false), args); return new TranslatableTextContent(tx.getKey(), translateIfNeeded(tx.getFallback(), direction, false), args);
} else if (t instanceof PlainTextContent.Literal tx) { } else if (t instanceof LiteralTextContent tx) {
return new PlainTextContent.Literal(translateIfNeeded(tx.string(), direction, false)); return new LiteralTextContent(translateIfNeeded(tx.string(), direction, false));
} else { } else {
// LOGGER.info("Unhandled text type: {0} ({1}})", source.getClass().toString(), source.toString()); // LOGGER.info("Unhandled text type: " + source.getClass() + " (" + source + ")");
return t; return t;
} }
}); });
@ -132,31 +125,28 @@ public class GoogleChat implements ModInitializer {
return style.withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_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) { public static String translateIfNeeded(String source, TranslationDirection direction, boolean respectRegex) {
if (source == null) return null; if (source == null) return null;
if (direction.shouldSkipOutright()) return source; if (direction.shouldSkipOutright()) return source;
if (respectRegex && direction.failsRegex(source)) return source; if (respectRegex && direction.failsRegex(source)) return source;
return computeIfAbsent2(direction == TranslationDirection.C2S ? c2ss : s2cs, source, t -> { return computeIfAbsent2(direction == TranslationDirection.C2S ? c2ss : s2cs, source, t -> {
try { try {
Matcher m = SURROUNDING_SPACE_PATTERN.matcher(source);
if (!m.find()) return source;
// Ignore generics since this is apparently not something java supports // Ignore generics since this is apparently not something java supports
@SuppressWarnings("rawtypes") TranslateService svc = GoogleChat.TRANSLATE_SERVICE; @SuppressWarnings("rawtypes") TranslateService svc = GoogleChat.TRANSLATE_SERVICE;
if (svc == null) throw new NullPointerException("Translate service uninitialized"); if (svc == null) throw new NullPointerException("Translate service uninitialized");
Language sourceLang = svc.parseLang(direction.source()); Language sourceLang = svc.parseLang(direction.source());
Language targetLang = svc.parseLang(direction.target()); Language targetLang = svc.parseLang(direction.target());
//noinspection unchecked //noinspection unchecked
return m.group(1) + svc.translate(m.group(2), sourceLang, targetLang) + m.group(3); return svc.translate(source, sourceLang, targetLang);
} catch (Throwable e) { } catch (Throwable e) {
LOGGER.error("Could not translate text: {0}", e, source); LOGGER.error("Could not translate text: " + source, e);
return source; return source;
} }
}); });
} }
private static <K, V> V computeIfAbsent2(Map<K, V> map, K key, Function<K, V> compute) { 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); if (!GoogleChatConfig.Advanced.async) return map.computeIfAbsent(key, compute);
synchronized (map) { synchronized (map) {
if (map.containsKey(key)) return map.get(key); if (map.containsKey(key)) return map.get(key);
V value = compute.apply(key); V value = compute.apply(key);

View File

@ -1,13 +1,11 @@
package io.gitlab.jfronny.googlechat; package io.gitlab.jfronny.googlechat;
import io.gitlab.jfronny.commons.serialize.annotations.Ignore; import io.gitlab.jfronny.commons.serialize.gson.api.v1.Ignore;
import io.gitlab.jfronny.libjf.config.api.v2.*; import io.gitlab.jfronny.libjf.config.api.v1.*;
import io.gitlab.jfronny.libjf.config.api.v2.dsl.ConfigBuilder; import io.gitlab.jfronny.libjf.config.api.v1.dsl.ConfigBuilder;
import net.fabricmc.api.*; import net.fabricmc.api.*;
import net.fabricmc.loader.api.*; import net.fabricmc.loader.api.*;
import static io.gitlab.jfronny.libjf.config.api.v2.dsl.Migration.of;
@JfConfig(tweaker = GoogleChatConfig.class) @JfConfig(tweaker = GoogleChatConfig.class)
public class GoogleChatConfig { public class GoogleChatConfig {
@Category(referencedConfigs = "libjf-translate-v1") @Category(referencedConfigs = "libjf-translate-v1")
@ -49,7 +47,6 @@ public class GoogleChatConfig {
public static class Advanced { public static class Advanced {
@Entry(min = 1, max = 1024) public static int cacheSize = 256; @Entry(min = 1, max = 1024) public static int cacheSize = 256;
@Entry public static boolean async = true; @Entry public static boolean async = true;
@Entry public static boolean translateMessageArguments = true;
@Entry public static boolean debugLogs = FabricLoader.getInstance().isDevelopmentEnvironment(); @Entry public static boolean debugLogs = FabricLoader.getInstance().isDevelopmentEnvironment();
} }
@ -67,17 +64,17 @@ public class GoogleChatConfig {
public static ConfigBuilder<?> tweak(ConfigBuilder<?> builder) { public static ConfigBuilder<?> tweak(ConfigBuilder<?> builder) {
return builder return builder
.addMigration("enabled", of(reader -> General.enabled = reader.nextBoolean())) .addMigration("enabled", reader -> General.enabled = reader.nextBoolean())
.addMigration("serverLanguage", of(reader -> General.serverLanguage = reader.nextString())) .addMigration("serverLanguage", reader -> General.serverLanguage = reader.nextString())
.addMigration("clientLanguage", of(reader -> General.clientLanguage = reader.nextString())) .addMigration("clientLanguage", reader -> General.clientLanguage = reader.nextString())
.addMigration("translationTooltip", of(reader -> General.translationTooltip = reader.nextBoolean())) .addMigration("translationTooltip", reader -> General.translationTooltip = reader.nextBoolean())
.addMigration("desugar", of(reader -> Processing.desugar = reader.nextBoolean())) .addMigration("desugar", reader -> Processing.desugar = reader.nextBoolean())
.addMigration("receivingRegex", of(reader -> Processing.receivingRegex = reader.nextString())) .addMigration("receivingRegex", reader -> Processing.receivingRegex = reader.nextString())
.addMigration("receivingRegexIsBlacklist", of(reader -> Processing.receivingRegexIsBlacklist = reader.nextBoolean())) .addMigration("receivingRegexIsBlacklist", reader -> Processing.receivingRegexIsBlacklist = reader.nextBoolean())
.addMigration("sendingRegex", of(reader -> Processing.sendingRegex = reader.nextString())) .addMigration("sendingRegex", reader -> Processing.sendingRegex = reader.nextString())
.addMigration("sendingRegexIsBlacklist", of(reader -> Processing.sendingRegexIsBlacklist = reader.nextBoolean())) .addMigration("sendingRegexIsBlacklist", reader -> Processing.sendingRegexIsBlacklist = reader.nextBoolean())
.addMigration("cacheSize", of(reader -> Advanced.cacheSize = reader.nextInt())) .addMigration("cacheSize", reader -> Advanced.cacheSize = reader.nextInt())
.addMigration("debugLogs", of(reader -> Advanced.debugLogs = reader.nextBoolean())); .addMigration("debugLogs", reader -> Advanced.debugLogs = reader.nextBoolean());
} }
static { static {

View File

@ -4,56 +4,41 @@ import io.gitlab.jfronny.googlechat.*;
import net.fabricmc.api.DedicatedServerModInitializer; import net.fabricmc.api.DedicatedServerModInitializer;
import net.fabricmc.fabric.api.event.Event; import net.fabricmc.fabric.api.event.Event;
import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent; import net.fabricmc.fabric.api.message.v1.ServerMessageDecoratorEvent;
import net.minecraft.network.message.MessageDecorator;
import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.text.Text; import net.minecraft.text.Text;
import org.jetbrains.annotations.Nullable;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class GoogleChatServer implements DedicatedServerModInitializer, MessageDecorator { import static io.gitlab.jfronny.libjf.LibJf.LOGGER;
public class GoogleChatServer implements DedicatedServerModInitializer {
@Override @Override
public void onInitializeServer() { public void onInitializeServer() {
// Default phase is executed between CONTENT and STYLING // Default phase is executed between CONTENT and STYLING
// Perform translation there instead of during CONTENT to better support other mods (such as chat-transform) // Perform translation there instead of during CONTENT to better support other mods (such as chat-transform)
// If this causes an incompatibility, I'll add my own phase // If this causes an incompatibility, I'll add my own phase
ServerMessageDecoratorEvent.EVENT.register(Event.DEFAULT_PHASE, this); ServerMessageDecoratorEvent.EVENT.register(Event.DEFAULT_PHASE, (sender, originalMessage) -> {
} CompletableFuture<Text> futureMessage = CompletableFuture.completedFuture(originalMessage);
if (!GoogleChatConfig.General.enabled) return futureMessage; // fast fallthrough
@Override if (sender != null) { // Client messages should first be translated to the server language
public Text decorate(@Nullable ServerPlayerEntity sender, Text original) { if (TranslationDirection.C2S.hasTarget()) {
if (!GoogleChatConfig.General.enabled) return original; if (TranslationDirection.S2C.hasTarget()) {
if (!GoogleChatConfig.Advanced.async) { // Do not translate back and forth
return decorate(sender, new TranslatableContainer.Sync(original)).text(); return futureMessage;
} }
try {
return decorate(sender, new TranslatableContainer.Async(CompletableFuture.completedFuture(original)))
.text()
.exceptionally(e -> {
GoogleChat.LOGGER.error("Could not compute translation", e);
return original;
})
.get();
} catch (InterruptedException | ExecutionException e) {
GoogleChat.LOGGER.error("Could not synchronize async translation for synchronous decorator", e);
return original;
}
}
private <K, T extends TranslatableContainer<K, T>> T decorate(@Nullable ServerPlayerEntity sender, T original) {
T message = original;
if (sender != null) { // Client messages should first be translated to the server language
if (TranslationDirection.C2S.hasTarget()) {
if (TranslationDirection.S2C.hasTarget()) {
// Do not translate back and forth
return message;
} }
futureMessage = futureMessage.thenApplyAsync(msg -> {
var translated = GoogleChat.translateIfNeeded(msg, TranslationDirection.C2S, true);
if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Applied C2S translation from " + msg + " to " + translated);
return translated;
});
} }
message = message.translate(TranslationDirection.C2S); // All messages should be translated to the client language before sending
} futureMessage = futureMessage.thenApplyAsync(msg -> {
// All messages should be translated to the client language before sending var translated = GoogleChat.translateIfNeeded(msg, TranslationDirection.S2C, true);
message = message.translate(TranslationDirection.S2C); if (GoogleChatConfig.Advanced.debugLogs) LOGGER.info("Applied S2C translation from " + msg + " to " + translated);
return message; return translated;
});
return futureMessage;
});
} }
} }

View File

@ -1,30 +0,0 @@
package io.gitlab.jfronny.googlechat.server;
import io.gitlab.jfronny.googlechat.*;
import net.minecraft.text.Text;
import java.util.concurrent.CompletableFuture;
public sealed interface TranslatableContainer<T, S extends TranslatableContainer<T, S>> {
S translate(TranslationDirection direction);
record Sync(Text text) implements TranslatableContainer<Text, Sync> {
@Override
public Sync translate(TranslationDirection direction) {
return new Sync(translateAndLog(text, direction));
}
}
record Async(CompletableFuture<Text> text) implements TranslatableContainer<CompletableFuture<Text>, Async> {
@Override
public Async translate(TranslationDirection direction) {
return new Async(text.thenApplyAsync(msg -> translateAndLog(msg, direction)));
}
}
static Text translateAndLog(final Text source, final TranslationDirection direction) {
var translated = GoogleChat.translateIfNeeded(source, direction, true);
if (GoogleChatConfig.Advanced.debugLogs) GoogleChat.LOGGER.info("Applied C2S translation from {0} to {1}", source, translated);
return translated;
}
}

View File

@ -1,34 +0,0 @@
package io.gitlab.jfronny.googlechat.server.mixin;
import com.mojang.brigadier.context.CommandContext;
import io.gitlab.jfronny.googlechat.GoogleChatConfig;
import io.gitlab.jfronny.googlechat.TranslationDirection;
import io.gitlab.jfronny.googlechat.server.TranslatableContainer;
import net.minecraft.command.argument.MessageArgumentType;
import net.minecraft.server.command.ServerCommandSource;
import net.minecraft.text.Text;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(MessageArgumentType.class)
public class MessageArgumentTypeMixin {
@Inject(method = "getMessage(Lcom/mojang/brigadier/context/CommandContext;Ljava/lang/String;)Lnet/minecraft/text/Text;", at = @At("TAIL"), cancellable = true)
private static void modifyMessage(CommandContext<ServerCommandSource> context, String name, CallbackInfoReturnable<Text> cir) {
if (!GoogleChatConfig.General.enabled || !GoogleChatConfig.Advanced.translateMessageArguments) return;
Text message = cir.getReturnValue();
if (context.getSource().getPlayer() != null) { // Client messages should first be translated to the server language
if (TranslationDirection.C2S.hasTarget()) {
if (TranslationDirection.S2C.hasTarget()) {
// Do not translate back and forth
return;
}
}
message = TranslatableContainer.translateAndLog(message, TranslationDirection.C2S);
}
// All messages should be translated to the client language before sending
message = TranslatableContainer.translateAndLog(message, TranslationDirection.S2C);
cir.setReturnValue(message);
}
}

View File

@ -25,13 +25,9 @@
"google-chat.jfconfig.advanced.cacheSize.tooltips": "The size of each message cache. Since there are six caches, the actual size will be six times this.", "google-chat.jfconfig.advanced.cacheSize.tooltips": "The size of each message cache. Since there are six caches, the actual size will be six times this.",
"google-chat.jfconfig.advanced.async": "Async", "google-chat.jfconfig.advanced.async": "Async",
"google-chat.jfconfig.advanced.async.tooltips": "Whether to asynchronously process messages. Should prevent some stutters, but might cause other issues. Disable if you no longer receive messages", "google-chat.jfconfig.advanced.async.tooltips": "Whether to asynchronously process messages. Should prevent some stutters, but might cause other issues. Disable if you no longer receive messages",
"google-chat.jfconfig.advanced.translateMessageArguments": "Translate MessageArguments",
"google-chat.jfconfig.advanced.translateMessageArguments.tooltips": "Whether to translate arguments of commands designated as being messages",
"google-chat.jfconfig.advanced.debugLogs": "Debug Logs", "google-chat.jfconfig.advanced.debugLogs": "Debug Logs",
"google-chat.jfconfig.advanced.debugLogs.tooltips": "Log additional information about message processing. Useful for debugging", "google-chat.jfconfig.advanced.debugLogs.tooltips": "Log additional information about message processing. Useful for debugging",
"google-chat.jfconfig.client": "Client", "google-chat.jfconfig.client": "Client",
"google-chat.jfconfig.server": "Server", "google-chat.jfconfig.server": "Server"
"key.google-chat.toggle": "Toggle GoogleChat"
} }

View File

@ -17,11 +17,9 @@
"entrypoints": { "entrypoints": {
"libjf:config": ["io.gitlab.jfronny.googlechat.JFC_GoogleChatConfig"], "libjf:config": ["io.gitlab.jfronny.googlechat.JFC_GoogleChatConfig"],
"server": ["io.gitlab.jfronny.googlechat.server.GoogleChatServer"], "server": ["io.gitlab.jfronny.googlechat.server.GoogleChatServer"],
"main": ["io.gitlab.jfronny.googlechat.GoogleChat"], "main": ["io.gitlab.jfronny.googlechat.GoogleChat"]
"client": ["io.gitlab.jfronny.googlechat.client.GoogleChatClient"]
}, },
"mixins": [ "mixins": [
"google-chat.mixins.json",
{ {
"config": "google-chat.client.mixins.json", "config": "google-chat.client.mixins.json",
"environment": "client" "environment": "client"
@ -30,7 +28,7 @@
"depends": { "depends": {
"fabricloader": ">=0.12.12", "fabricloader": ">=0.12.12",
"minecraft": "*", "minecraft": "*",
"libjf-config-core-v2": "*", "libjf-config-core-v1": "*",
"libjf-translate-v1": "*", "libjf-translate-v1": "*",
"fabric-message-api-v1": "*" "fabric-message-api-v1": "*"
} }

View File

@ -1,12 +0,0 @@
{
"required": true,
"minVersion": "0.8",
"package": "io.gitlab.jfronny.googlechat.server.mixin",
"compatibilityLevel": "JAVA_17",
"mixins": [
"MessageArgumentTypeMixin"
],
"injectors": {
"defaultRequire": 1
}
}