diff --git a/fabric-data-generation-api-v1/build.gradle b/fabric-data-generation-api-v1/build.gradle
index 71fa09556..8b335766b 100644
--- a/fabric-data-generation-api-v1/build.gradle
+++ b/fabric-data-generation-api-v1/build.gradle
@@ -5,7 +5,8 @@ moduleDependencies(project, [
'fabric-api-base',
'fabric-registry-sync-v0',
'fabric-networking-api-v1',
- 'fabric-resource-conditions-api-v1'
+ 'fabric-resource-conditions-api-v1',
+ 'fabric-item-groups-v0'
])
dependencies {
diff --git a/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLanguageProvider.java b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLanguageProvider.java
new file mode 100644
index 000000000..473563276
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/main/java/net/fabricmc/fabric/api/datagen/v1/provider/FabricLanguageProvider.java
@@ -0,0 +1,214 @@
+/*
+ * 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.api.datagen.v1.provider;
+
+import java.io.IOException;
+import java.io.Reader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Map;
+import java.util.Objects;
+import java.util.TreeMap;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import org.jetbrains.annotations.ApiStatus;
+
+import net.minecraft.block.Block;
+import net.minecraft.data.DataProvider;
+import net.minecraft.data.DataWriter;
+import net.minecraft.enchantment.Enchantment;
+import net.minecraft.entity.EntityType;
+import net.minecraft.entity.attribute.EntityAttribute;
+import net.minecraft.entity.effect.StatusEffect;
+import net.minecraft.item.Item;
+import net.minecraft.item.ItemGroup;
+import net.minecraft.stat.StatType;
+import net.minecraft.util.Identifier;
+
+import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
+
+/**
+ * Extend this class and implement {@link FabricLanguageProvider#generateTranslations(TranslationBuilder)}.
+ * Make sure to use {@link FabricLanguageProvider#FabricLanguageProvider(FabricDataGenerator, String)} FabricLanguageProvider} to declare what language code is being generated if it isn't {@code en_us}.
+ *
+ *
Register an instance of the class with {@link FabricDataGenerator#addProvider} in a {@link net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint}
+ */
+public abstract class FabricLanguageProvider implements DataProvider {
+ protected final FabricDataGenerator dataGenerator;
+ private final String languageCode;
+
+ protected FabricLanguageProvider(FabricDataGenerator dataGenerator) {
+ this(dataGenerator, "en_us");
+ }
+
+ protected FabricLanguageProvider(FabricDataGenerator dataGenerator, String languageCode) {
+ this.dataGenerator = dataGenerator;
+ this.languageCode = languageCode;
+ }
+
+ /**
+ * Implement this method to register languages.
+ *
+ *
Call {@link TranslationBuilder#add(String, String)} to add a translation.
+ */
+ public abstract void generateTranslations(TranslationBuilder translationBuilder);
+
+ @Override
+ public void run(DataWriter writer) throws IOException {
+ TreeMap translationEntries = new TreeMap<>();
+
+ generateTranslations((String key, String value) -> {
+ Objects.requireNonNull(key);
+ Objects.requireNonNull(value);
+
+ if (translationEntries.containsKey(key)) {
+ throw new RuntimeException("Existing translation key found - " + key + " - Duplicate will be ignored.");
+ }
+
+ translationEntries.put(key, value);
+ });
+
+ JsonObject langEntryJson = new JsonObject();
+
+ for (Map.Entry entry : translationEntries.entrySet()) {
+ langEntryJson.addProperty(entry.getKey(), entry.getValue());
+ }
+
+ DataProvider.writeToPath(writer, langEntryJson, getLangFilePath(this.languageCode));
+ }
+
+ private Path getLangFilePath(String code) {
+ return dataGenerator.getOutput().resolve("assets/%s/lang/%s.json".formatted(dataGenerator.getModId(), code));
+ }
+
+ @Override
+ public String getName() {
+ return "Language";
+ }
+
+ /**
+ * A consumer used by {@link FabricLanguageProvider#generateTranslations(TranslationBuilder)}.
+ */
+ @ApiStatus.NonExtendable
+ @FunctionalInterface
+ public interface TranslationBuilder {
+ /**
+ * Adds a translation.
+ *
+ * @param translationKey The key of the translation.
+ * @param value The value of the entry.
+ */
+ void add(String translationKey, String value);
+
+ /**
+ * Adds a translation for an {@link Item}.
+ * @param item The {@link Item} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(Item item, String value) {
+ add(item.getTranslationKey(), value);
+ };
+
+ /**
+ * Adds a translation for a {@link Block}.
+ * @param block The {@link Block} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(Block block, String value) {
+ add(block.getTranslationKey(), value);
+ }
+
+ /**
+ * Adds a translation for an {@link ItemGroup}.
+ * @param group The {@link ItemGroup} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(ItemGroup group, String value) {
+ add("itemGroup." + group.getName(), value);
+ }
+
+ /**
+ * Adds a translation for an {@link EntityType}.
+ * @param entityType The {@link EntityType} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(EntityType> entityType, String value) {
+ add(entityType.getTranslationKey(), value);
+ }
+
+ /**
+ * Adds a translation for an {@link Enchantment}.
+ * @param enchantment The {@link Enchantment} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(Enchantment enchantment, String value) {
+ add(enchantment.getTranslationKey(), value);
+ }
+
+ /**
+ * Adds a translation for an {@link EntityAttribute}.
+ * @param entityAttribute The {@link EntityAttribute} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(EntityAttribute entityAttribute, String value) {
+ add(entityAttribute.getTranslationKey(), value);
+ }
+
+ /**
+ * Adds a translation for a {@link StatType}.
+ * @param statType The {@link StatType} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(StatType> statType, String value) {
+ add(statType.getTranslationKey(), value);
+ }
+
+ /**
+ * Adds a translation for a {@link StatusEffect}.
+ * @param statusEffect The {@link StatusEffect} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(StatusEffect statusEffect, String value) {
+ add(statusEffect.getTranslationKey(), value);
+ }
+
+ /**
+ * Adds a translation for an {@link Identifier}.
+ * @param identifier The {@link Identifier} to get the translation key from.
+ * @param value The value of the entry.
+ */
+ default void add(Identifier identifier, String value) {
+ add(identifier.toTranslationKey(), value);
+ }
+
+ /**
+ * Merges an existing language file into the generated language file.
+ * @param existingLanguageFile The path to the existing language file.
+ * @throws IOException If loading the language file failed.
+ */
+ default void add(Path existingLanguageFile) throws IOException {
+ try (Reader reader = Files.newBufferedReader(existingLanguageFile)) {
+ JsonObject translations = JsonParser.parseReader(reader).getAsJsonObject();
+
+ for (String key : translations.keySet()) {
+ add(key, translations.get(key).getAsString());
+ }
+ }
+ }
+ }
+}
diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java
index 1456b19a2..f397f2b82 100644
--- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java
+++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestContent.java
@@ -24,10 +24,13 @@ import net.minecraft.block.Material;
import net.minecraft.item.BlockItem;
import net.minecraft.item.Item;
import net.minecraft.item.ItemGroup;
+import net.minecraft.item.ItemStack;
+import net.minecraft.item.Items;
import net.minecraft.util.Identifier;
import net.minecraft.util.registry.Registry;
import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.client.itemgroup.FabricItemGroupBuilder;
public class DataGeneratorTestContent implements ModInitializer {
public static final String MOD_ID = "fabric-data-gen-api-v1-testmod";
@@ -35,9 +38,11 @@ public class DataGeneratorTestContent implements ModInitializer {
public static Block SIMPLE_BLOCK;
public static Block BLOCK_WITHOUT_ITEM;
public static Block BLOCK_WITHOUT_LOOT_TABLE;
+ public static ItemGroup SIMPLE_ITEM_GROUP;
@Override
public void onInitialize() {
+ SIMPLE_ITEM_GROUP = FabricItemGroupBuilder.build(new Identifier(MOD_ID, "default"), () -> new ItemStack(Items.BONE));
SIMPLE_BLOCK = createBlock("simple_block", true);
BLOCK_WITHOUT_ITEM = createBlock("block_without_item", false);
BLOCK_WITHOUT_LOOT_TABLE = createBlock("block_without_loot_table", false);
diff --git a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java
index 97dbd2877..f64217639 100644
--- a/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java
+++ b/fabric-data-generation-api-v1/src/testmod/java/net/fabricmc/fabric/test/datagen/DataGeneratorTestEntrypoint.java
@@ -20,10 +20,17 @@ import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.BLOCK_WI
import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.BLOCK_WITHOUT_LOOT_TABLE;
import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.MOD_ID;
import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.SIMPLE_BLOCK;
+import static net.fabricmc.fabric.test.datagen.DataGeneratorTestContent.SIMPLE_ITEM_GROUP;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Optional;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
import net.minecraft.advancement.Advancement;
import net.minecraft.advancement.AdvancementFrame;
import net.minecraft.advancement.criterion.OnKilledCriterion;
@@ -31,6 +38,8 @@ import net.minecraft.data.client.BlockStateModelGenerator;
import net.minecraft.data.client.ItemModelGenerator;
import net.minecraft.data.server.recipe.RecipeJsonProvider;
import net.minecraft.data.server.recipe.ShapelessRecipeJsonBuilder;
+import net.minecraft.entity.EntityType;
+import net.minecraft.entity.attribute.EntityAttributes;
import net.minecraft.item.Items;
import net.minecraft.loot.LootPool;
import net.minecraft.loot.LootTable;
@@ -52,6 +61,7 @@ import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint;
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricBlockLootTableProvider;
+import net.fabricmc.fabric.api.datagen.v1.provider.FabricLanguageProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricModelProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricRecipeProvider;
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
@@ -60,6 +70,7 @@ import net.fabricmc.fabric.api.resource.conditions.v1.ConditionJsonProvider;
import net.fabricmc.fabric.api.resource.conditions.v1.DefaultResourceConditions;
public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DataGeneratorTestEntrypoint.class);
private static final ConditionJsonProvider NEVER_LOADED = DefaultResourceConditions.allModsLoaded("a");
private static final ConditionJsonProvider ALWAYS_LOADED = DefaultResourceConditions.not(NEVER_LOADED);
@@ -71,6 +82,8 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
dataGenerator.addProvider(TestAdvancementProvider::new);
dataGenerator.addProvider(TestBlockLootTableProvider::new);
dataGenerator.addProvider(TestBarterLootTableProvider::new);
+ dataGenerator.addProvider(ExistingEnglishLangProvider::new);
+ dataGenerator.addProvider(JapaneseLangProvider::new);
TestBlockTagProvider blockTagProvider = dataGenerator.addProvider(TestBlockTagProvider::new);
dataGenerator.addProvider(new TestItemTagProvider(dataGenerator, blockTagProvider));
@@ -110,6 +123,51 @@ public class DataGeneratorTestEntrypoint implements DataGeneratorEntrypoint {
}
}
+ private static class ExistingEnglishLangProvider extends FabricLanguageProvider {
+ private ExistingEnglishLangProvider(FabricDataGenerator dataGenerator) {
+ super(dataGenerator);
+ }
+
+ @Override
+ public void generateTranslations(TranslationBuilder translationBuilder) {
+ translationBuilder.add(SIMPLE_BLOCK, "Simple Block");
+ translationBuilder.add(new Identifier(MOD_ID, "identifier_test"), "Identifier Test");
+ translationBuilder.add(EntityType.ALLAY, "Allay");
+ translationBuilder.add(EntityAttributes.GENERIC_ARMOR, "Generic Armor");
+
+ try {
+ Optional path = dataGenerator.getModContainer().findPath("assets/testmod/lang/en_us.base.json");
+
+ if (path.isPresent()) {
+ translationBuilder.add(path.get());
+ } else {
+ throw new RuntimeException("The existing language file could not be found in the testmod assets!");
+ }
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+
+ try {
+ translationBuilder.add(EntityType.ALLAY, "Allay Duplicate Test");
+ } catch (RuntimeException e) {
+ LOGGER.info("Duplicate test passed.");
+ }
+ }
+ }
+
+ private static class JapaneseLangProvider extends FabricLanguageProvider {
+ private JapaneseLangProvider(FabricDataGenerator dataGenerator) {
+ super(dataGenerator, "ja_jp");
+ }
+
+ @Override
+ public void generateTranslations(TranslationBuilder translationBuilder) {
+ translationBuilder.add(SIMPLE_BLOCK, "シンプルブロック");
+ translationBuilder.add(SIMPLE_ITEM_GROUP, "データ生成項目");
+ translationBuilder.add("this.is.a.test", "こんにちは");
+ }
+ }
+
private static class TestConditionalRecipeProvider extends FabricRecipeProvider {
private TestConditionalRecipeProvider(FabricDataGenerator dataGenerator) {
super(dataGenerator);
diff --git a/fabric-data-generation-api-v1/src/testmod/resources/assets/testmod/lang/en_us.base.json b/fabric-data-generation-api-v1/src/testmod/resources/assets/testmod/lang/en_us.base.json
new file mode 100644
index 000000000..d29b657bd
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/testmod/resources/assets/testmod/lang/en_us.base.json
@@ -0,0 +1,3 @@
+{
+ "itemGroup.fabric-data-gen-api-v1-testmod.default": "Datagen Itemgroup"
+}