diff --git a/CHANGELOG.md b/CHANGELOG.md index e654726e..39096bcc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ and Freecam's versioning is based on [Semantic Versioning](https://semver.org/sp ### Added +- \[Fabric]: The name & description shown in the Mod Menu can now be translated ([#172](https://github.com/MinecraftFreecam/Freecam/issues/172)). + ### Changed ### Removed diff --git a/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/LangTask.kt b/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/LangTask.kt index 985427c4..62a0d5e7 100644 --- a/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/LangTask.kt +++ b/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/LangTask.kt @@ -71,7 +71,9 @@ abstract class LangTask : DefaultTask() { @TaskAction fun build() { val processors = listOf( - VariantTooltipProcessor(variant.get()) + VariantTooltipProcessor(variant.get()), + ModDescriptionProcessor(modId.get(), variant.get()), + ModNameProcessor(modId.get(), variant.get()) ) val languages = inputDirectory.get().asFile @@ -91,6 +93,32 @@ abstract class LangTask : DefaultTask() { } } + /** + * Get the given translation, for the given language. + * + * Will fall back to using the [source language][source] if the key isn't + * found in the specified language or if language isn't specified. + * + * Should only be used **after** this task has finished executing. + * I.e. **not** during Gradle's configuration step. + * + * @param key the translation key + * @param lang the locale, e.g. en-US, en_us, or zh-CN + * @return the translation, or null if not found + */ + @JvmOverloads + fun getTranslation(key: String, lang: String = source.get()): String? { + val file = fileFor(lang) + val translation = readJsonFile(file)[key] + + // Check "source" translation if key wasn't found + return if (translation == null && file != fileFor(source.get())) { + getTranslation(key) + } else { + translation + } + } + private fun fileFor(lang: String) = outputDirectory.get().asFile .resolve("assets") .resolve(modId.get()) diff --git a/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/ModDescriptionProcessor.kt b/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/ModDescriptionProcessor.kt new file mode 100644 index 00000000..6236f8ab --- /dev/null +++ b/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/ModDescriptionProcessor.kt @@ -0,0 +1,38 @@ +package net.xolt.freecam.gradle + +internal class ModDescriptionProcessor(private val modID: String, private val variant: String) : LangProcessor { + override fun process( + translations: Map, + fallback: Map? + ): Map { + val firstID = "${modID}.description" + val secondID = "${modID}.description.${variant}" + val ids = listOf(firstID, secondID) + + // Nothing to do if this language has no "description" translations + if (ids.none(translations.keys::contains)) { + return translations + } + + val map = translations.toMutableMap() + + // Remove any description.variant keys + map.keys + .filter { it.startsWith("${firstID}.") } + .forEach(map::remove) + + // Set modmenu summary if this language has a translation for firstID + translations[firstID]?.let { map["modmenu.summaryTranslation.${modID}"] = it } + + // Set "full" description + // Use fallback if either part is missing from this language + ids.mapNotNull { translations[it] ?: fallback?.get(it) } + .joinToString(" ") + .let { description -> + map[firstID] = description + map["modmenu.descriptionTranslation.${modID}"] = description + } + + return map + } +} diff --git a/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/ModNameProcessor.kt b/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/ModNameProcessor.kt new file mode 100644 index 00000000..c56d0ea9 --- /dev/null +++ b/buildSrc/src/main/kotlin/net/xolt/freecam/gradle/ModNameProcessor.kt @@ -0,0 +1,35 @@ +package net.xolt.freecam.gradle + +internal class ModNameProcessor(private val modID: String, private val variant: String) : LangProcessor { + override fun process( + translations: Map, + fallback: Map? + ): Map { + val firstID = "${modID}.name" + val secondID = "${modID}.name.${variant}" + val ids = listOf(firstID, secondID) + + // Nothing to do if this language has no "name" translations + if (ids.none(translations.keys::contains)) { + return translations + } + + val map = translations.toMutableMap() + + // Remove any name.variant keys + map.keys + .filter { it.startsWith("${firstID}.") } + .forEach(map::remove) + + // Set "full" name + // Use fallback if either part is missing from this language + ids.mapNotNull { translations[it] ?: fallback?.get(it) } + .joinToString(" ") + .let { name -> + map[firstID] = name + map["modmenu.nameTranslation.${modID}"] = name + } + + return map + } +} diff --git a/buildSrc/src/test/kotlin/net/xolt/freecam/gradle/ModDescriptionProcessorTest.kt b/buildSrc/src/test/kotlin/net/xolt/freecam/gradle/ModDescriptionProcessorTest.kt new file mode 100644 index 00000000..30f49fc0 --- /dev/null +++ b/buildSrc/src/test/kotlin/net/xolt/freecam/gradle/ModDescriptionProcessorTest.kt @@ -0,0 +1,38 @@ +package net.xolt.freecam.gradle + +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.TestFactory + +class ModDescriptionProcessorTest { + + @TestFactory + fun `Basic tests`(): List { + val sample1 = mapOf( + "modid.description" to "Default description", + "modid.description.special" to "(extra special)" + ) + + return listOf( + processorTest( + name = "Discard variant descriptions", + processor = ModDescriptionProcessor("modid", "normal"), + translations = sample1, + result = mapOf( + "modid.description" to "Default description", + "modmenu.descriptionTranslation.modid" to "Default description", + "modmenu.summaryTranslation.modid" to "Default description" + ) + ), + processorTest( + name = "Append \"extra special\" to description", + processor = ModDescriptionProcessor("modid", "special"), + translations = sample1, + result = mapOf( + "modid.description" to "Default description (extra special)", + "modmenu.descriptionTranslation.modid" to "Default description (extra special)", + "modmenu.summaryTranslation.modid" to "Default description" + ) + ) + ) + } +} \ No newline at end of file diff --git a/buildSrc/src/test/kotlin/net/xolt/freecam/gradle/ModNameProcessorTest.kt b/buildSrc/src/test/kotlin/net/xolt/freecam/gradle/ModNameProcessorTest.kt new file mode 100644 index 00000000..86b48a95 --- /dev/null +++ b/buildSrc/src/test/kotlin/net/xolt/freecam/gradle/ModNameProcessorTest.kt @@ -0,0 +1,36 @@ +package net.xolt.freecam.gradle + +import org.junit.jupiter.api.DynamicTest +import org.junit.jupiter.api.TestFactory + +class ModNameProcessorTest { + + @TestFactory + fun `Basic tests`(): List { + val sample1 = mapOf( + "modid.name" to "ModName", + "modid.name.special" to "(extra special)" + ) + + return listOf( + processorTest( + name = "Discard variant names", + processor = ModNameProcessor("modid", "normal"), + translations = sample1, + result = mapOf( + "modid.name" to "ModName", + "modmenu.nameTranslation.modid" to "ModName" + ) + ), + processorTest( + name = "Append \"extra special\" to name", + processor = ModNameProcessor("modid", "special"), + translations = sample1, + result = mapOf( + "modid.name" to "ModName (extra special)", + "modmenu.nameTranslation.modid" to "ModName (extra special)" + ) + ) + ) + } +} \ No newline at end of file diff --git a/data/build.gradle b/data/build.gradle index 80d34a24..1a9de661 100644 --- a/data/build.gradle +++ b/data/build.gradle @@ -25,24 +25,18 @@ variants.each { variant -> kebabName += "-$variant" } - def metadataTask = tasks.register("${name}GenMetadata", Copy) { + def metadataTask = tasks.register("${name}GenMetadata") { group = "metadata" description = { def file = platform.endsWith "forge" ? "mods.toml" : "${platform}.mods.json" return "Build $variant $file file" }() - // Include files under `src/platform` - from layout.projectDirectory.dir("src").dir(platform) - into layout.buildDirectory.dir("metadata/${kebabName}") - + // Depend on langTask and the following properties + dependsOn langTask inputs.properties( - mod_id: rootProject.name, - modrinth_name: project.modrinth_name, version: project.mod_version, authors: project.authors, - description: project.description, - modrinth_description: project.modrinth_description, licence: project.licence, homepage_url: project.homepage_url, source_code_url: project.source_code_url, @@ -54,22 +48,33 @@ variants.each { variant -> neoforge_req: project.neoforge_req, ) - def overrides = new TreeMap() - overrides.mod_id = inputs.properties.mod_id.toLowerCase() - overrides.name = overrides.mod_id.capitalize() - overrides.json_authors = inputs.properties.authors.split(',').collect { "\"$it\"" }.join(", ") - - if (variant == "modrinth") { - overrides.name += ' ' + inputs.properties.modrinth_name - overrides.description = inputs.properties.description + ' ' + inputs.properties.modrinth_description - } - - filesMatching("fabric.mod.json") { - expand inputs.properties + overrides - } - - filesMatching("META-INF/mods.toml") { - expand inputs.properties + overrides + def inputDir = layout.projectDirectory.dir("src").dir(platform) + def outputDir = layout.buildDirectory.dir("metadata/${kebabName}") + + // Declare task outputs so they can be used by other tasks + outputs.dir outputDir + + // Need to do this at execution time; + // LangTask.getTranslation() can't be used at configuration time... + doLast { + def values = new TreeMap(inputs.properties) + values.mod_id = rootProject.name.toLowerCase() + values.name = langTask.get().getTranslation("freecam.name") + values.description = langTask.get().getTranslation("freecam.description") + values.json_authors = values.authors.split(',').collect { "\"$it\"" }.join(", ") + + copy { + from inputDir + into outputDir + + filesMatching("fabric.mod.json") { + expand values + } + + filesMatching("META-INF/mods.toml") { + expand values + } + } } } diff --git a/data/src/lang/en-US/mod.json b/data/src/lang/en-US/mod.json new file mode 100644 index 00000000..8b682b10 --- /dev/null +++ b/data/src/lang/en-US/mod.json @@ -0,0 +1,6 @@ +{ + "freecam.name": "Freecam", + "freecam.name.modrinth": "(Modrinth Edition)", + "freecam.description": "A highly customizable freecam mod.", + "freecam.description.modrinth": "Some features have been restricted to comply with Modrinth's Content Rules." +} diff --git a/gradle.properties b/gradle.properties index d3b2b248..386ed021 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,9 +3,6 @@ org.gradle.jvmargs=-Xmx4G mod_version=1.2.3 maven_group=net.xolt.freecam authors=hashalite,Matt Sturgeon -description=A highly customizable freecam mod. -modrinth_name=(Modrinth Edition) -modrinth_description=Some features have been restricted to comply with Modrinth's Content Rules. licence=MIT homepage_url=https://www.curseforge.com/minecraft/mc-mods/free-cam source_code_url=https://github.com/MinecraftFreecam/Freecam