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" +}