From a10d22bd14c26bf1df7d74d3eafcda58c57666e1 Mon Sep 17 00:00:00 2001
From: Teddy Li <tedexe_work.top@foxmail.com>
Date: Sun, 11 Dec 2022 22:10:27 +0800
Subject: [PATCH] Load default translations from all namespaces on the server
 (#2709)

* Introduce fabric:server-language-namespaces custom meta to fabric-resource-loader-v0 (extension of FabricMC/fabric#2668)

* Fix testmod assertion message

* Automatically detect every en_us.json files in mod container
Remove fabric:server-language-namespaces custom meta detection

* Remove unused custom field in fabric.mod.json

* Update license

* Update style

* cleanup format

* Fix checkstyle, move constructor

Co-authored-by: modmuss50 <modmuss50@gmail.com>
---
 .../resource/loader/ModNioResourcePack.java   |  2 +-
 .../resource/loader/ServerLanguageUtil.java   | 56 +++++++++++++++++++
 .../resource/loader/server/LanguageMixin.java | 16 ++----
 .../test/resource/loader/LanguageTestMod.java | 11 +++-
 .../lang/en_us.json                           |  3 +
 .../lang/en_us.json                           |  3 +
 6 files changed, 75 insertions(+), 16 deletions(-)
 create mode 100644 fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ServerLanguageUtil.java
 create mode 100644 fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod-test1/lang/en_us.json
 create mode 100644 fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod/lang/en_us.json

diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java
index bd5ba9713..ce2d60f2a 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ModNioResourcePack.java
@@ -107,7 +107,7 @@ public class ModNioResourcePack implements ResourcePack, ModResourcePack {
 		this.namespaces = readNamespaces(paths, modInfo.getId());
 	}
 
-	private static Map<ResourceType, Set<String>> readNamespaces(List<Path> paths, String modId) {
+	static Map<ResourceType, Set<String>> readNamespaces(List<Path> paths, String modId) {
 		Map<ResourceType, Set<String>> ret = new EnumMap<>(ResourceType.class);
 
 		for (ResourceType type : ResourceType.values()) {
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ServerLanguageUtil.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ServerLanguageUtil.java
new file mode 100644
index 000000000..a38a8efbe
--- /dev/null
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/impl/resource/loader/ServerLanguageUtil.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.impl.resource.loader;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+import net.minecraft.resource.ResourceType;
+import net.minecraft.util.Language;
+
+import net.fabricmc.loader.api.FabricLoader;
+import net.fabricmc.loader.api.ModContainer;
+
+public final class ServerLanguageUtil {
+	private static final String ASSETS_PREFIX = ResourceType.CLIENT_RESOURCES.getDirectory() + '/';
+
+	private ServerLanguageUtil() {
+	}
+
+	public static Collection<Path> getModLanguageFiles() {
+		Set<Path> paths = new LinkedHashSet<>();
+
+		for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
+			if (mod.getMetadata().getType().equals("builtin")) continue;
+
+			final Map<ResourceType, Set<String>> map = ModNioResourcePack.readNamespaces(mod.getRootPaths(), mod.getMetadata().getId());
+
+			for (String ns : map.get(ResourceType.CLIENT_RESOURCES)) {
+				mod.findPath(ASSETS_PREFIX + ns + "/lang/" + Language.DEFAULT_LANGUAGE + ".json")
+						.filter(Files::isRegularFile)
+						.ifPresent(paths::add);
+			}
+		}
+
+		return Collections.unmodifiableCollection(paths);
+	}
+}
diff --git a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java
index ca443ad69..b2008d5af 100644
--- a/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java
+++ b/fabric-resource-loader-v0/src/main/java/net/fabricmc/fabric/mixin/resource/loader/server/LanguageMixin.java
@@ -35,8 +35,7 @@ import org.spongepowered.asm.mixin.injection.Redirect;
 
 import net.minecraft.util.Language;
 
-import net.fabricmc.loader.api.FabricLoader;
-import net.fabricmc.loader.api.ModContainer;
+import net.fabricmc.fabric.impl.resource.loader.ServerLanguageUtil;
 
 @Mixin(Language.class)
 class LanguageMixin {
@@ -44,25 +43,18 @@ class LanguageMixin {
 	@Final
 	private static Logger LOGGER;
 
-	@Shadow
-	@Final
-	public static String DEFAULT_LANGUAGE;
-
 	@Redirect(method = "create", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/ImmutableMap$Builder;build()Lcom/google/common/collect/ImmutableMap;"))
 	private static ImmutableMap<String, String> create(ImmutableMap.Builder<String, String> cir) {
 		Map<String, String> map = new HashMap<>(cir.buildOrThrow());
 
-		for (ModContainer mod : FabricLoader.getInstance().getAllMods()) {
-			if (!mod.getMetadata().getType().equals("builtin")) loadModLanguage(mod, map::put);
+		for (Path path : ServerLanguageUtil.getModLanguageFiles()) {
+			loadFromPath(path, map::put);
 		}
 
 		return ImmutableMap.copyOf(map);
 	}
 
-	private static void loadModLanguage(ModContainer container, BiConsumer<String, String> entryConsumer) {
-		Path path = container.findPath("assets/" + container.getMetadata().getId() + "/lang/" + DEFAULT_LANGUAGE + ".json").orElse(null);
-		if (path == null || !Files.isRegularFile(path)) return;
-
+	private static void loadFromPath(Path path, BiConsumer<String, String> entryConsumer) {
 		try (InputStream stream = Files.newInputStream(path)) {
 			LOGGER.debug("Loading translations from {}", path);
 			load(stream, entryConsumer);
diff --git a/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/LanguageTestMod.java b/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/LanguageTestMod.java
index ae13bdfe1..ff59f36ad 100644
--- a/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/LanguageTestMod.java
+++ b/fabric-resource-loader-v0/src/testmod/java/net/fabricmc/fabric/test/resource/loader/LanguageTestMod.java
@@ -27,11 +27,16 @@ public class LanguageTestMod implements DedicatedServerModInitializer {
 	}
 
 	private static void testTranslationLoaded() {
-		String expected = "Fabric mod";
-		String actual = Text.translatable("pack.source.fabricmod").getString();
+		testTranslationLoaded("pack.source.fabricmod", "Fabric mod");
+		testTranslationLoaded("text.fabric-resource-loader-v0-testmod.server.lang.test0", "Test from fabric-resource-loader-v0-testmod");
+		testTranslationLoaded("text.fabric-resource-loader-v0-testmod.server.lang.test1", "Test from fabric-resource-loader-v0-testmod-test1");
+	}
+
+	private static void testTranslationLoaded(String key, String expected) {
+		String actual = Text.translatable(key).getString();
 
 		if (!expected.equals(actual)) {
-			throw new AssertionError("Expected 'pack.source.fabricmod' to translate to " + expected + ", but translated to " + actual);
+			throw new AssertionError("Expected " + key + " to translate to " + expected + ", but translated to " + actual);
 		}
 	}
 }
diff --git a/fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod-test1/lang/en_us.json b/fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod-test1/lang/en_us.json
new file mode 100644
index 000000000..2b38e51f2
--- /dev/null
+++ b/fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod-test1/lang/en_us.json
@@ -0,0 +1,3 @@
+{
+  "text.fabric-resource-loader-v0-testmod.server.lang.test1": "Test from fabric-resource-loader-v0-testmod-test1"
+}
diff --git a/fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod/lang/en_us.json b/fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod/lang/en_us.json
new file mode 100644
index 000000000..30fada154
--- /dev/null
+++ b/fabric-resource-loader-v0/src/testmod/resources/assets/fabric-resource-loader-v0-testmod/lang/en_us.json
@@ -0,0 +1,3 @@
+{
+  "text.fabric-resource-loader-v0-testmod.server.lang.test0": "Test from fabric-resource-loader-v0-testmod"
+}