From bf4c9ea893424f32579e681c9170db810566b1b3 Mon Sep 17 00:00:00 2001 From: Artik Date: Fri, 16 Aug 2024 18:42:17 +0200 Subject: [PATCH 1/2] Added translation system. --- .../koralix/oneforall/ServerOneForAll.java | 2 + .../oneforall/lang/TranslationUnit.java | 106 ++++++++++++++++++ ...rverLoginNetworkAddonRegisterReceiver.java | 2 +- .../protocol/ClientConnectionMixin.java | 22 ++++ .../server/protocol/PacketByteBufMixin.java | 25 +++++ .../ServerLoginNetworkHandlerMixin.java | 31 +++++ .../ServerPlayNetworkHandlerMixin.java | 56 +++++++++ .../network/ActOnPlayPacketAction.java | 10 ++ .../network/ActOnPlayPacketHandler.java | 27 +++++ .../oneforall/network/ClientSession.java | 30 +++++ .../network/ClientSessionWrapper.java | 6 + .../oneforall/network/ServerLoginManager.java | 48 ++++++-- .../oneforall/settings/ServerSettings.java | 9 +- src/main/resources/assets/lang/es_es.json | 7 ++ src/main/resources/assets/lang/gl_es.json | 7 ++ src/main/resources/fabric.mod.json | 4 + src/main/resources/oneforall.mixins.json | 1 - .../resources/oneforall.server.mixins.json | 16 +++ 18 files changed, 397 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/koralix/oneforall/lang/TranslationUnit.java rename src/main/java/com/koralix/oneforall/mixin/{ => server}/fapi/FixServerLoginNetworkAddonRegisterReceiver.java (94%) create mode 100644 src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java create mode 100644 src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java create mode 100644 src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java create mode 100644 src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java create mode 100644 src/main/java/com/koralix/oneforall/network/ActOnPlayPacketAction.java create mode 100644 src/main/java/com/koralix/oneforall/network/ActOnPlayPacketHandler.java create mode 100644 src/main/java/com/koralix/oneforall/network/ClientSession.java create mode 100644 src/main/java/com/koralix/oneforall/network/ClientSessionWrapper.java create mode 100644 src/main/resources/assets/lang/es_es.json create mode 100644 src/main/resources/assets/lang/gl_es.json create mode 100644 src/main/resources/oneforall.server.mixins.json diff --git a/src/main/java/com/koralix/oneforall/ServerOneForAll.java b/src/main/java/com/koralix/oneforall/ServerOneForAll.java index a8fed7e..b1f158c 100644 --- a/src/main/java/com/koralix/oneforall/ServerOneForAll.java +++ b/src/main/java/com/koralix/oneforall/ServerOneForAll.java @@ -1,9 +1,11 @@ package com.koralix.oneforall; +import com.koralix.oneforall.lang.TranslationUnit; import com.koralix.oneforall.network.ServerLoginManager; import com.koralix.oneforall.platform.Platform; import com.koralix.oneforall.settings.ServerSettings; import com.koralix.oneforall.settings.SettingsManager; +import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; public class ServerOneForAll extends OneForAll { diff --git a/src/main/java/com/koralix/oneforall/lang/TranslationUnit.java b/src/main/java/com/koralix/oneforall/lang/TranslationUnit.java new file mode 100644 index 0000000..7d96f5b --- /dev/null +++ b/src/main/java/com/koralix/oneforall/lang/TranslationUnit.java @@ -0,0 +1,106 @@ +package com.koralix.oneforall.lang; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.koralix.oneforall.OneForAll; +import com.koralix.oneforall.network.ClientSession; +import com.koralix.oneforall.network.ServerLoginManager; +import com.koralix.oneforall.settings.ServerSettings; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.stream.Stream; + +import static net.minecraft.datafixer.fix.BlockEntitySignTextStrictJsonFix.GSON; + +public class TranslationUnit { + private final static Map> TRANSLATIONS = new HashMap<>(); + private final static String DEFAULT_LANGUAGE = "es_es"; + private static Optional TARGET_PLAYER = Optional.empty(); + + static { + Stream.of( + "es_es", + "gl_es" + ).forEach(TranslationUnit::load); + } + + public static void load(String lang) { + OneForAll.getInstance().getLogger().info("Loading translations from {}", lang); + + Map translationMap = new HashMap<>(); + String languageFile = "/assets/lang/" + lang + ".json"; + + try (InputStream inputStream = TranslationUnit.class.getResourceAsStream(languageFile)) { + JsonObject jsonObject = GSON.fromJson(new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonObject.class); + + for (Map.Entry entry : jsonObject.entrySet()) { + translationMap.put(entry.getKey(), entry.getValue().getAsString()); + } + + TRANSLATIONS.put(lang, translationMap); + } catch (Exception e) { + OneForAll.getInstance().getLogger().error("Failed to load translations from {}", lang, e); + } + } + + public static String translate(String lang, String code) { + if (TRANSLATIONS.containsKey(lang) && TRANSLATIONS.get(lang).containsKey(code)) { + return TRANSLATIONS.get(lang).get(code); + } else if (TRANSLATIONS.containsKey(ServerSettings.DEFAULT_LANGUAGE.value()) && TRANSLATIONS.get(ServerSettings.DEFAULT_LANGUAGE.value()).containsKey(code)) { + return TRANSLATIONS.get(ServerSettings.DEFAULT_LANGUAGE.value()).get(code); + } else { + return TRANSLATIONS.get(DEFAULT_LANGUAGE).get(code); + } + } + + public static void prepare(UUID uuid) { + TARGET_PLAYER = Optional.of(uuid); + } + + public static Text adaptText(Text text) { + Text result = text; + + ClientSession session = null; + if (TARGET_PLAYER.isPresent()) { + session = ServerLoginManager.SESSIONS.get(TARGET_PLAYER.get()); + } + + if (session == null || session.modVersion() == null) { + if (!text.getSiblings().isEmpty()) { + ListIterator iterator = text.getSiblings().listIterator(); + + while (iterator.hasNext()) { + iterator.set(adaptText(iterator.next())); + } + } + + if (text.getContent() instanceof TranslatableTextContent textContent) { + if (TRANSLATIONS.get(DEFAULT_LANGUAGE).containsKey(textContent.getKey())) { + String userLanguage = session != null + ? (session.language() != null ? session.language() : ServerSettings.DEFAULT_LANGUAGE.value()) + : ServerSettings.DEFAULT_LANGUAGE.value(); + String translation = translate(userLanguage, textContent.getKey()); + MutableText content = Text.translatableWithFallback(textContent.getKey(), translation); + for (Text sibling: text.getSiblings()) { + content.append(sibling); + } + + result = content; + } + } + } + + return result; + } + + public static boolean hasLanguage(String language) { + return TRANSLATIONS.containsKey(language); + } + +} diff --git a/src/main/java/com/koralix/oneforall/mixin/fapi/FixServerLoginNetworkAddonRegisterReceiver.java b/src/main/java/com/koralix/oneforall/mixin/server/fapi/FixServerLoginNetworkAddonRegisterReceiver.java similarity index 94% rename from src/main/java/com/koralix/oneforall/mixin/fapi/FixServerLoginNetworkAddonRegisterReceiver.java rename to src/main/java/com/koralix/oneforall/mixin/server/fapi/FixServerLoginNetworkAddonRegisterReceiver.java index 21e743b..b160f4b 100644 --- a/src/main/java/com/koralix/oneforall/mixin/fapi/FixServerLoginNetworkAddonRegisterReceiver.java +++ b/src/main/java/com/koralix/oneforall/mixin/server/fapi/FixServerLoginNetworkAddonRegisterReceiver.java @@ -1,4 +1,4 @@ -package com.koralix.oneforall.mixin.fapi; +package com.koralix.oneforall.mixin.server.fapi; import net.fabricmc.fabric.impl.networking.GlobalReceiverRegistry; import net.fabricmc.fabric.impl.networking.server.ServerLoginNetworkAddon; diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java new file mode 100644 index 0000000..54cf3fe --- /dev/null +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java @@ -0,0 +1,22 @@ +package com.koralix.oneforall.mixin.server.protocol; + +import com.koralix.oneforall.network.ClientSessionWrapper; +import com.koralix.oneforall.network.ClientSession; +import net.minecraft.network.ClientConnection; +import org.spongepowered.asm.mixin.Mixin; + +@Mixin(ClientConnection.class) +public class ClientConnectionMixin implements ClientSessionWrapper { + private ClientSession session; + + @Override + public ClientSession session() { + return session; + } + + @Override + public void session(ClientSession session) { + this.session = session; + } + +} diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java new file mode 100644 index 0000000..362009a --- /dev/null +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java @@ -0,0 +1,25 @@ +package com.koralix.oneforall.mixin.server.protocol; + +import com.koralix.oneforall.lang.TranslationUnit; +import net.minecraft.network.PacketByteBuf; +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.ModifyArg; +import org.spongepowered.asm.mixin.injection.ModifyArgs; + +@Mixin(PacketByteBuf.class) +public class PacketByteBufMixin { + @ModifyArg( + method = "Lnet/minecraft/network/PacketByteBuf;writeText(Lnet/minecraft/text/Text;)Lnet/minecraft/network/PacketByteBuf;", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/text/Text$Serializer;toJson(Lnet/minecraft/text/Text;)Ljava/lang/String;" + ), + index = 0 + ) + private Text writeTextInjector(Text text) { + return TranslationUnit.adaptText(text); + } +} diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java new file mode 100644 index 0000000..9c3ba13 --- /dev/null +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java @@ -0,0 +1,31 @@ +package com.koralix.oneforall.mixin.server.protocol; + +import com.koralix.oneforall.network.ClientSessionWrapper; +import com.koralix.oneforall.network.ClientSession; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ServerLoginNetworkHandler; +import org.jetbrains.annotations.NotNull; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; + +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.function.Consumer; + +@Mixin(ServerLoginNetworkHandler.class) +public class ServerLoginNetworkHandlerMixin implements ClientSessionWrapper { + @Shadow private ClientConnection connection; + + @Override + public ClientSession session() { + throw new UnsupportedOperationException(); + } + + @Override + public void session(@NotNull ClientSession session) { + ClientSessionWrapper modConnection = (ClientSessionWrapper) Objects.requireNonNull(connection); + + modConnection.session(session); + } +} diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java new file mode 100644 index 0000000..4b5fc9f --- /dev/null +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java @@ -0,0 +1,56 @@ +package com.koralix.oneforall.mixin.server.protocol; + +import com.koralix.oneforall.lang.TranslationUnit; +import com.koralix.oneforall.network.*; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.encryption.PublicPlayerSession; +import net.minecraft.network.packet.Packet; +import net.minecraft.network.packet.c2s.play.*; +import net.minecraft.server.network.ServerPlayNetworkHandler; +import net.minecraft.server.network.ServerPlayerEntity; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(ServerPlayNetworkHandler.class) +public class ServerPlayNetworkHandlerMixin implements ClientSessionWrapper { + @Shadow + ServerPlayerEntity player; + @Shadow + ClientConnection connection; + + + @Shadow private @Nullable PublicPlayerSession session; + + @Inject( + method = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/PacketCallbacks;)V", + at = @At("HEAD") + ) + private void setTranslationTargetUUID(Packet packet, @Nullable PacketCallbacks callbacks, CallbackInfo ci) { + TranslationUnit.prepare(player.getUuid()); + } + + @Override + public ClientSession session() { + return ((ClientSessionWrapper) connection).session(); + } + + @Override + public void session(ClientSession session) { + ((ClientSessionWrapper) connection).session(session); + } + + @Inject( + method = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;onClientSettings(Lnet/minecraft/network/packet/c2s/play/ClientSettingsC2SPacket;)V", + at = @At("HEAD") + ) + private void onClientSettings(ClientSettingsC2SPacket packet, CallbackInfo ci) { + for (ActOnPlayPacketAction action: ActOnPlayPacketHandler.getActionsFor(this.session(), ClientSettingsC2SPacket.class)) { + action.execute(connection, player, packet); + } + } +} \ No newline at end of file diff --git a/src/main/java/com/koralix/oneforall/network/ActOnPlayPacketAction.java b/src/main/java/com/koralix/oneforall/network/ActOnPlayPacketAction.java new file mode 100644 index 0000000..1244723 --- /dev/null +++ b/src/main/java/com/koralix/oneforall/network/ActOnPlayPacketAction.java @@ -0,0 +1,10 @@ +package com.koralix.oneforall.network; + +import net.minecraft.network.ClientConnection; +import net.minecraft.network.packet.Packet; +import net.minecraft.server.network.ServerPlayerEntity; + +@FunctionalInterface +public interface ActOnPlayPacketAction> { + void execute(ClientConnection connection, ServerPlayerEntity player, T packet); +} diff --git a/src/main/java/com/koralix/oneforall/network/ActOnPlayPacketHandler.java b/src/main/java/com/koralix/oneforall/network/ActOnPlayPacketHandler.java new file mode 100644 index 0000000..c974392 --- /dev/null +++ b/src/main/java/com/koralix/oneforall/network/ActOnPlayPacketHandler.java @@ -0,0 +1,27 @@ +package com.koralix.oneforall.network; + +import net.minecraft.network.packet.Packet; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ActOnPlayPacketHandler { + private static final Map>, List>>> REGISTRY = new HashMap<>(); + + public static > void register(Class clazz, ClientSession session, ActOnPlayPacketAction action) { + if (!REGISTRY.containsKey(session)) REGISTRY.put(session, new HashMap<>()); + if (!REGISTRY.get(session).containsKey(clazz)) REGISTRY.get(session).put((Class>) clazz, new ArrayList<>()); + + REGISTRY.get(session).get(clazz).add(action); + } + + public static > List> getActionsFor(ClientSession session, Class clazz) { + if (REGISTRY.containsKey(session) && REGISTRY.get(session).containsKey(clazz)) { + // This cast should be safe since T extends Packet + return (List>)(Object)REGISTRY.get(session).get(clazz); + } + return new ArrayList<>(); + } +} diff --git a/src/main/java/com/koralix/oneforall/network/ClientSession.java b/src/main/java/com/koralix/oneforall/network/ClientSession.java new file mode 100644 index 0000000..56431d6 --- /dev/null +++ b/src/main/java/com/koralix/oneforall/network/ClientSession.java @@ -0,0 +1,30 @@ +package com.koralix.oneforall.network; + +public class ClientSession { + private String language; + private String modVersion; + + public String language() { + return language; + } + + public void language(String language) { + this.language = language; + } + + public String modVersion() { + return modVersion; + } + + public void modVersion(String modVersion) { + this.modVersion = modVersion; + } + + @Override + public String toString() { + return "ClientSession{" + + "language='" + language + '\'' + + ", modVersion='" + modVersion + '\'' + + '}'; + } +} diff --git a/src/main/java/com/koralix/oneforall/network/ClientSessionWrapper.java b/src/main/java/com/koralix/oneforall/network/ClientSessionWrapper.java new file mode 100644 index 0000000..ef8d041 --- /dev/null +++ b/src/main/java/com/koralix/oneforall/network/ClientSessionWrapper.java @@ -0,0 +1,6 @@ +package com.koralix.oneforall.network; + +public interface ClientSessionWrapper { + ClientSession session(); + void session(ClientSession session); +} diff --git a/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java b/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java index 7c15aff..5f7425f 100644 --- a/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java +++ b/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java @@ -3,21 +3,24 @@ import com.koralix.oneforall.OneForAll; import com.koralix.oneforall.serde.Serde; import com.koralix.oneforall.settings.ServerSettings; -import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; -import net.fabricmc.fabric.api.networking.v1.PacketSender; -import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; -import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; +import net.fabricmc.fabric.api.networking.v1.*; import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.c2s.play.ClientSettingsC2SPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.network.ServerLoginNetworkHandler; import net.minecraft.text.Text; import net.minecraft.util.Identifier; +import java.util.HashMap; +import java.util.Map; import java.util.Optional; +import java.util.UUID; import java.util.concurrent.CompletableFuture; +import java.util.function.Function; public class ServerLoginManager { + public static final Map SESSIONS = new HashMap<>(); public static void init() { ServerLoginConnectionEvents.QUERY_START.register(ServerLoginManager::onQueryStart); } @@ -49,8 +52,13 @@ private static void onQueryStart( CompletableFuture future = new CompletableFuture<>(); ServerLoginNetworking.registerReceiver(handler, getChannel("hello"), (server1, handler1, understood, buf, synchronizer1, responseSender) -> { - Optional errorMessage = onQueryResponse(server1, handler1, understood, buf, synchronizer1, responseSender); - errorMessage.ifPresent(s -> handler.disconnect(Text.of("Disconnected by OneForAll\n\nReason: " + s))); + Optional errorMessage = onQueryResponse(server1, handler1, understood, buf, synchronizer1, responseSender); + + errorMessage.ifPresent(s -> { + Text disconnectMessage = Text.translatable("text.oneforall.disconnected").append(s); + + handler.disconnect(disconnectMessage); + }); future.complete(null); }); @@ -61,7 +69,7 @@ private static void onQueryStart( synchronizer.waitFor(future); } - private static Optional onQueryResponse( + private static Optional onQueryResponse( MinecraftServer server, ServerLoginNetworkHandler handler, boolean understood, @@ -69,16 +77,38 @@ private static Optional onQueryResponse( ServerLoginNetworking.LoginSynchronizer synchronizer, PacketSender responseSender ) { + ClientSession session = new ClientSession(); + ((ClientSessionWrapper) handler).session(session); + ActOnPlayPacketHandler.register(ClientSettingsC2SPacket.class, session, (connection, player, packet) -> { + ClientSession sessionWrapper = ((ClientSessionWrapper) connection).session(); + sessionWrapper.language(packet.language()); + + ((ClientSessionWrapper) connection).session(sessionWrapper); + ServerLoginManager.SESSIONS.put(player.getUuid(), sessionWrapper); + }); if (!understood && ServerSettings.ENFORCE_PROTOCOL.value()) { - return Optional.of("One-For-All Mod Protocol is required to connect to the server."); + return Optional.of(Text.translatable("text.oneforall.disconnected.notUsingProtocol")); } else if (!understood) { return Optional.empty(); } // If understood - String version = buf.readString(); + Optional version = readSafe(buf, PacketByteBuf::readString); + Optional language = readSafe(buf, PacketByteBuf::readString); OneForAll.getInstance().getLogger().debug("Client connected with version: {}", version); + session.modVersion(version.orElse(null)); + session.language(language.orElse(null)); + ((ClientSessionWrapper) handler).session(session); + return Optional.empty(); } + + private static Optional readSafe(PacketByteBuf buf, Function function) { + try { + return Optional.of(function.apply(buf)); + } catch (Exception e) { + return Optional.empty(); + } + } } \ No newline at end of file diff --git a/src/main/java/com/koralix/oneforall/settings/ServerSettings.java b/src/main/java/com/koralix/oneforall/settings/ServerSettings.java index 73ad5b6..13971ce 100644 --- a/src/main/java/com/koralix/oneforall/settings/ServerSettings.java +++ b/src/main/java/com/koralix/oneforall/settings/ServerSettings.java @@ -1,5 +1,7 @@ package com.koralix.oneforall.settings; +import com.koralix.oneforall.lang.TranslationUnit; + import java.util.Objects; @SettingsRegistry(id = "server_settings", env = SettingsRegistry.Env.SERVER) @@ -13,8 +15,13 @@ private ServerSettings() { .permission(source -> source.hasPermissionLevel(4)) .build(); - public static final ConfigValue ENFORCE_PROTOCOL = ConfigValue.of(false) + public static final ConfigValue ENFORCE_PROTOCOL = ConfigValue.of(true) .test(Objects::nonNull) .permission(source -> source.hasPermissionLevel(4)) .build(); + + public static final ConfigValue DEFAULT_LANGUAGE = ConfigValue.of("gl_es") + .test(TranslationUnit::hasLanguage) + .permission(source -> source.hasPermissionLevel(4)) + .build(); } diff --git a/src/main/resources/assets/lang/es_es.json b/src/main/resources/assets/lang/es_es.json new file mode 100644 index 0000000..256832b --- /dev/null +++ b/src/main/resources/assets/lang/es_es.json @@ -0,0 +1,7 @@ +{ + "text.oneforall.disconnected": "Desconectado por OneForAll\n\nRazón: ", + "text.oneforall.disconnected.notUsingProtocol": "El Protocolo del Mod One-For-All es necesario para conectarse al servidor", + "settings.oneforall.server_settings.protocol_enabled": "Protocolo activado", + "settings.oneforall.server_settings.enforce_protocol": "Protocolo requerido", + "settings.oneforall.server_settings.default_language": "Idioma por defecto" +} \ No newline at end of file diff --git a/src/main/resources/assets/lang/gl_es.json b/src/main/resources/assets/lang/gl_es.json new file mode 100644 index 0000000..00fa2e2 --- /dev/null +++ b/src/main/resources/assets/lang/gl_es.json @@ -0,0 +1,7 @@ +{ + "text.oneforall.disconnected": "Desconectado por OneForAll\n\nRazón: ", + "text.oneforall.disconnected.notUsingProtocol": "O Protocolo do Mod One-All é necesario para conectarse ao servidor.", + "settings.oneforall.server_settings.protocol_enabled": "Protocolo activado", + "settings.oneforall.server_settings.enforce_protocol": "Protocolo requirido", + "settings.oneforall.server_settings.default_language": "Linguaxe por defecto" +} diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index e991e2c..bd275d1 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -24,6 +24,10 @@ { "config": "${id}.client.mixins.json", "environment": "client" + }, + { + "config": "${id}.server.mixins.json", + "environment": "server" } ], "depends": { diff --git a/src/main/resources/oneforall.mixins.json b/src/main/resources/oneforall.mixins.json index 72f6cea..e270712 100644 --- a/src/main/resources/oneforall.mixins.json +++ b/src/main/resources/oneforall.mixins.json @@ -4,7 +4,6 @@ "package": "com.koralix.oneforall.mixin", "compatibilityLevel": "JAVA_21", "mixins": [ - "fapi.FixServerLoginNetworkAddonRegisterReceiver" ], "injectors": { "defaultRequire": 1 diff --git a/src/main/resources/oneforall.server.mixins.json b/src/main/resources/oneforall.server.mixins.json new file mode 100644 index 0000000..0d0705a --- /dev/null +++ b/src/main/resources/oneforall.server.mixins.json @@ -0,0 +1,16 @@ +{ + "required": true, + "minVersion": "0.8", + "package": "com.koralix.oneforall.mixin.server", + "compatibilityLevel": "JAVA_21", + "server": [ + "fapi.FixServerLoginNetworkAddonRegisterReceiver", + "protocol.ClientConnectionMixin", + "protocol.ServerPlayNetworkHandlerMixin", + "protocol.PacketByteBufMixin", + "protocol.ServerLoginNetworkHandlerMixin" + ], + "injectors": { + "defaultRequire": 1 + } +} \ No newline at end of file From 23d9fdd09ff6d60a6b264486ce4914fe8f5498d6 Mon Sep 17 00:00:00 2001 From: JohanVonElectrum Date: Sun, 1 Sep 2024 16:08:30 +0200 Subject: [PATCH 2/2] Resolving #12 conversation --- .../koralix/oneforall/ServerOneForAll.java | 2 - .../com/koralix/oneforall/lang/Language.java | 74 ++++++++++++++ .../oneforall/lang/TranslationUnit.java | 97 ++++++------------- .../protocol/ClientConnectionMixin.java | 2 +- .../server/protocol/PacketByteBufMixin.java | 2 - .../ServerLoginNetworkHandlerMixin.java | 5 +- .../ServerPlayNetworkHandlerMixin.java | 11 +-- .../oneforall/network/ClientSession.java | 8 +- .../oneforall/network/ServerLoginManager.java | 10 +- .../oneforall/settings/ServerSettings.java | 7 +- .../assets/{ => oneforall}/lang/es_es.json | 0 .../assets/{ => oneforall}/lang/gl_es.json | 0 .../resources/oneforall.server.mixins.json | 4 +- 13 files changed, 126 insertions(+), 96 deletions(-) create mode 100644 src/main/java/com/koralix/oneforall/lang/Language.java rename src/main/resources/assets/{ => oneforall}/lang/es_es.json (100%) rename src/main/resources/assets/{ => oneforall}/lang/gl_es.json (100%) diff --git a/src/main/java/com/koralix/oneforall/ServerOneForAll.java b/src/main/java/com/koralix/oneforall/ServerOneForAll.java index b1f158c..a8fed7e 100644 --- a/src/main/java/com/koralix/oneforall/ServerOneForAll.java +++ b/src/main/java/com/koralix/oneforall/ServerOneForAll.java @@ -1,11 +1,9 @@ package com.koralix.oneforall; -import com.koralix.oneforall.lang.TranslationUnit; import com.koralix.oneforall.network.ServerLoginManager; import com.koralix.oneforall.platform.Platform; import com.koralix.oneforall.settings.ServerSettings; import com.koralix.oneforall.settings.SettingsManager; -import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; public class ServerOneForAll extends OneForAll { diff --git a/src/main/java/com/koralix/oneforall/lang/Language.java b/src/main/java/com/koralix/oneforall/lang/Language.java new file mode 100644 index 0000000..63ac934 --- /dev/null +++ b/src/main/java/com/koralix/oneforall/lang/Language.java @@ -0,0 +1,74 @@ +package com.koralix.oneforall.lang; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.koralix.oneforall.OneForAll; + +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +import static net.minecraft.datafixer.fix.BlockEntitySignTextStrictJsonFix.GSON; + +public enum Language { + SPANISH("es_es"), + GALICIAN("gl_es"); + + private static final Map LANGUAGES; + + static { + Map languages = new HashMap<>(); + for (Language language : values()) { + languages.put(language.code, language); + } + LANGUAGES = Map.copyOf(languages); + } + + private final String code; + private final Map translations; + + Language(String code) throws IllegalArgumentException { + this.code = code; + + OneForAll.getInstance().getLogger().info("Loading translations from {}", code); + + Map translationMap = new HashMap<>(); + String languageFile = "/assets/oneforall/lang/" + code + ".json"; + + try (InputStream inputStream = TranslationUnit.class.getResourceAsStream(languageFile)) { + JsonObject jsonObject = GSON.fromJson(new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonObject.class); + + for (Map.Entry entry : jsonObject.entrySet()) { + translationMap.put(entry.getKey(), entry.getValue().getAsString()); + } + + this.translations = Map.copyOf(translationMap); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to load translations from " + code, e); + } + } + + public String getCode() { + return code; + } + + public boolean containsKey(String code) { + return translations.containsKey(code); + } + + public Optional translate(String code) { + return Optional.ofNullable(translations.get(code)); + } + + @Override + public String toString() { + return code; + } + + public static Language fromCode(String s) { + return LANGUAGES.get(s); + } +} diff --git a/src/main/java/com/koralix/oneforall/lang/TranslationUnit.java b/src/main/java/com/koralix/oneforall/lang/TranslationUnit.java index 7d96f5b..1a7ef2f 100644 --- a/src/main/java/com/koralix/oneforall/lang/TranslationUnit.java +++ b/src/main/java/com/koralix/oneforall/lang/TranslationUnit.java @@ -1,8 +1,5 @@ package com.koralix.oneforall.lang; -import com.google.gson.JsonElement; -import com.google.gson.JsonObject; -import com.koralix.oneforall.OneForAll; import com.koralix.oneforall.network.ClientSession; import com.koralix.oneforall.network.ServerLoginManager; import com.koralix.oneforall.settings.ServerSettings; @@ -10,53 +7,19 @@ import net.minecraft.text.Text; import net.minecraft.text.TranslatableTextContent; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.stream.Stream; - -import static net.minecraft.datafixer.fix.BlockEntitySignTextStrictJsonFix.GSON; +import java.util.ListIterator; +import java.util.Optional; +import java.util.UUID; public class TranslationUnit { - private final static Map> TRANSLATIONS = new HashMap<>(); - private final static String DEFAULT_LANGUAGE = "es_es"; + private final static Language DEFAULT_LANGUAGE = Language.SPANISH; private static Optional TARGET_PLAYER = Optional.empty(); - static { - Stream.of( - "es_es", - "gl_es" - ).forEach(TranslationUnit::load); - } - - public static void load(String lang) { - OneForAll.getInstance().getLogger().info("Loading translations from {}", lang); - - Map translationMap = new HashMap<>(); - String languageFile = "/assets/lang/" + lang + ".json"; - - try (InputStream inputStream = TranslationUnit.class.getResourceAsStream(languageFile)) { - JsonObject jsonObject = GSON.fromJson(new InputStreamReader(inputStream, StandardCharsets.UTF_8), JsonObject.class); - - for (Map.Entry entry : jsonObject.entrySet()) { - translationMap.put(entry.getKey(), entry.getValue().getAsString()); - } - - TRANSLATIONS.put(lang, translationMap); - } catch (Exception e) { - OneForAll.getInstance().getLogger().error("Failed to load translations from {}", lang, e); - } - } - - public static String translate(String lang, String code) { - if (TRANSLATIONS.containsKey(lang) && TRANSLATIONS.get(lang).containsKey(code)) { - return TRANSLATIONS.get(lang).get(code); - } else if (TRANSLATIONS.containsKey(ServerSettings.DEFAULT_LANGUAGE.value()) && TRANSLATIONS.get(ServerSettings.DEFAULT_LANGUAGE.value()).containsKey(code)) { - return TRANSLATIONS.get(ServerSettings.DEFAULT_LANGUAGE.value()).get(code); - } else { - return TRANSLATIONS.get(DEFAULT_LANGUAGE).get(code); - } + public static String translate(Language lang, String code) { + return lang.translate(code) + .or(() -> ServerSettings.DEFAULT_LANGUAGE.value().translate(code)) + .or(() -> DEFAULT_LANGUAGE.translate(code)) + .orElse(code); } public static void prepare(UUID uuid) { @@ -71,36 +34,32 @@ public static Text adaptText(Text text) { session = ServerLoginManager.SESSIONS.get(TARGET_PLAYER.get()); } - if (session == null || session.modVersion() == null) { - if (!text.getSiblings().isEmpty()) { - ListIterator iterator = text.getSiblings().listIterator(); + if (session != null && session.modVersion() != null) { // TODO: Version dependent translations + return result; + } - while (iterator.hasNext()) { - iterator.set(adaptText(iterator.next())); - } - } + if (!text.getSiblings().isEmpty()) { + ListIterator iterator = text.getSiblings().listIterator(); - if (text.getContent() instanceof TranslatableTextContent textContent) { - if (TRANSLATIONS.get(DEFAULT_LANGUAGE).containsKey(textContent.getKey())) { - String userLanguage = session != null - ? (session.language() != null ? session.language() : ServerSettings.DEFAULT_LANGUAGE.value()) - : ServerSettings.DEFAULT_LANGUAGE.value(); - String translation = translate(userLanguage, textContent.getKey()); - MutableText content = Text.translatableWithFallback(textContent.getKey(), translation); - for (Text sibling: text.getSiblings()) { - content.append(sibling); - } + while (iterator.hasNext()) { + iterator.set(adaptText(iterator.next())); + } + } - result = content; - } + if (text.getContent() instanceof TranslatableTextContent textContent) { + String key = textContent.getKey(); + Language userLanguage = session == null + ? ServerSettings.DEFAULT_LANGUAGE.value() + : (session.language() != null ? session.language() : ServerSettings.DEFAULT_LANGUAGE.value()); + String translation = translate(userLanguage, key); + MutableText content = Text.translatableWithFallback(key, translation); + for (Text sibling: text.getSiblings()) { + content.append(sibling); } + result = content; } return result; } - public static boolean hasLanguage(String language) { - return TRANSLATIONS.containsKey(language); - } - } diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java index 54cf3fe..dc1556e 100644 --- a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ClientConnectionMixin.java @@ -1,7 +1,7 @@ package com.koralix.oneforall.mixin.server.protocol; -import com.koralix.oneforall.network.ClientSessionWrapper; import com.koralix.oneforall.network.ClientSession; +import com.koralix.oneforall.network.ClientSessionWrapper; import net.minecraft.network.ClientConnection; import org.spongepowered.asm.mixin.Mixin; diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java index 362009a..eb39e24 100644 --- a/src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/PacketByteBufMixin.java @@ -5,9 +5,7 @@ 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.ModifyArg; -import org.spongepowered.asm.mixin.injection.ModifyArgs; @Mixin(PacketByteBuf.class) public class PacketByteBufMixin { diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java index 9c3ba13..a2ad1e0 100644 --- a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerLoginNetworkHandlerMixin.java @@ -1,17 +1,14 @@ package com.koralix.oneforall.mixin.server.protocol; -import com.koralix.oneforall.network.ClientSessionWrapper; import com.koralix.oneforall.network.ClientSession; +import com.koralix.oneforall.network.ClientSessionWrapper; import net.minecraft.network.ClientConnection; -import net.minecraft.network.packet.Packet; import net.minecraft.server.network.ServerLoginNetworkHandler; import org.jetbrains.annotations.NotNull; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; import java.util.Objects; -import java.util.function.BiConsumer; -import java.util.function.Consumer; @Mixin(ServerLoginNetworkHandler.class) public class ServerLoginNetworkHandlerMixin implements ClientSessionWrapper { diff --git a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java index 4b5fc9f..fffd254 100644 --- a/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java +++ b/src/main/java/com/koralix/oneforall/mixin/server/protocol/ServerPlayNetworkHandlerMixin.java @@ -1,12 +1,14 @@ package com.koralix.oneforall.mixin.server.protocol; import com.koralix.oneforall.lang.TranslationUnit; -import com.koralix.oneforall.network.*; +import com.koralix.oneforall.network.ActOnPlayPacketAction; +import com.koralix.oneforall.network.ActOnPlayPacketHandler; +import com.koralix.oneforall.network.ClientSession; +import com.koralix.oneforall.network.ClientSessionWrapper; import net.minecraft.network.ClientConnection; import net.minecraft.network.PacketCallbacks; -import net.minecraft.network.encryption.PublicPlayerSession; import net.minecraft.network.packet.Packet; -import net.minecraft.network.packet.c2s.play.*; +import net.minecraft.network.packet.c2s.play.ClientSettingsC2SPacket; import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; import org.jetbrains.annotations.Nullable; @@ -23,9 +25,6 @@ public class ServerPlayNetworkHandlerMixin implements ClientSessionWrapper { @Shadow ClientConnection connection; - - @Shadow private @Nullable PublicPlayerSession session; - @Inject( method = "Lnet/minecraft/server/network/ServerPlayNetworkHandler;sendPacket(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/PacketCallbacks;)V", at = @At("HEAD") diff --git a/src/main/java/com/koralix/oneforall/network/ClientSession.java b/src/main/java/com/koralix/oneforall/network/ClientSession.java index 56431d6..f2be9ee 100644 --- a/src/main/java/com/koralix/oneforall/network/ClientSession.java +++ b/src/main/java/com/koralix/oneforall/network/ClientSession.java @@ -1,14 +1,16 @@ package com.koralix.oneforall.network; +import com.koralix.oneforall.lang.Language; + public class ClientSession { - private String language; + private Language language; private String modVersion; - public String language() { + public Language language() { return language; } - public void language(String language) { + public void language(Language language) { this.language = language; } diff --git a/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java b/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java index 5f7425f..8f9ea7c 100644 --- a/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java +++ b/src/main/java/com/koralix/oneforall/network/ServerLoginManager.java @@ -1,9 +1,13 @@ package com.koralix.oneforall.network; import com.koralix.oneforall.OneForAll; +import com.koralix.oneforall.lang.Language; import com.koralix.oneforall.serde.Serde; import com.koralix.oneforall.settings.ServerSettings; -import net.fabricmc.fabric.api.networking.v1.*; +import net.fabricmc.fabric.api.networking.v1.PacketByteBufs; +import net.fabricmc.fabric.api.networking.v1.PacketSender; +import net.fabricmc.fabric.api.networking.v1.ServerLoginConnectionEvents; +import net.fabricmc.fabric.api.networking.v1.ServerLoginNetworking; import net.minecraft.network.PacketByteBuf; import net.minecraft.network.packet.c2s.play.ClientSettingsC2SPacket; import net.minecraft.server.MinecraftServer; @@ -81,7 +85,7 @@ private static Optional onQueryResponse( ((ClientSessionWrapper) handler).session(session); ActOnPlayPacketHandler.register(ClientSettingsC2SPacket.class, session, (connection, player, packet) -> { ClientSession sessionWrapper = ((ClientSessionWrapper) connection).session(); - sessionWrapper.language(packet.language()); + sessionWrapper.language(Language.fromCode(packet.language())); ((ClientSessionWrapper) connection).session(sessionWrapper); ServerLoginManager.SESSIONS.put(player.getUuid(), sessionWrapper); @@ -94,7 +98,7 @@ private static Optional onQueryResponse( // If understood Optional version = readSafe(buf, PacketByteBuf::readString); - Optional language = readSafe(buf, PacketByteBuf::readString); + Optional language = readSafe(buf, PacketByteBuf::readString).map(Language::fromCode); OneForAll.getInstance().getLogger().debug("Client connected with version: {}", version); session.modVersion(version.orElse(null)); diff --git a/src/main/java/com/koralix/oneforall/settings/ServerSettings.java b/src/main/java/com/koralix/oneforall/settings/ServerSettings.java index 13971ce..35dc34d 100644 --- a/src/main/java/com/koralix/oneforall/settings/ServerSettings.java +++ b/src/main/java/com/koralix/oneforall/settings/ServerSettings.java @@ -1,6 +1,6 @@ package com.koralix.oneforall.settings; -import com.koralix.oneforall.lang.TranslationUnit; +import com.koralix.oneforall.lang.Language; import java.util.Objects; @@ -15,13 +15,12 @@ private ServerSettings() { .permission(source -> source.hasPermissionLevel(4)) .build(); - public static final ConfigValue ENFORCE_PROTOCOL = ConfigValue.of(true) + public static final ConfigValue ENFORCE_PROTOCOL = ConfigValue.of(false) .test(Objects::nonNull) .permission(source -> source.hasPermissionLevel(4)) .build(); - public static final ConfigValue DEFAULT_LANGUAGE = ConfigValue.of("gl_es") - .test(TranslationUnit::hasLanguage) + public static final ConfigValue DEFAULT_LANGUAGE = ConfigValue.of(Language.SPANISH) .permission(source -> source.hasPermissionLevel(4)) .build(); } diff --git a/src/main/resources/assets/lang/es_es.json b/src/main/resources/assets/oneforall/lang/es_es.json similarity index 100% rename from src/main/resources/assets/lang/es_es.json rename to src/main/resources/assets/oneforall/lang/es_es.json diff --git a/src/main/resources/assets/lang/gl_es.json b/src/main/resources/assets/oneforall/lang/gl_es.json similarity index 100% rename from src/main/resources/assets/lang/gl_es.json rename to src/main/resources/assets/oneforall/lang/gl_es.json diff --git a/src/main/resources/oneforall.server.mixins.json b/src/main/resources/oneforall.server.mixins.json index 0d0705a..a7900ad 100644 --- a/src/main/resources/oneforall.server.mixins.json +++ b/src/main/resources/oneforall.server.mixins.json @@ -6,9 +6,9 @@ "server": [ "fapi.FixServerLoginNetworkAddonRegisterReceiver", "protocol.ClientConnectionMixin", - "protocol.ServerPlayNetworkHandlerMixin", "protocol.PacketByteBufMixin", - "protocol.ServerLoginNetworkHandlerMixin" + "protocol.ServerLoginNetworkHandlerMixin", + "protocol.ServerPlayNetworkHandlerMixin" ], "injectors": { "defaultRequire": 1