[Maintenance] Use type-safe generated LangLeys object instead of hardcoding en_us.json keys
This is not a user-facing feature, nor a bug fix; it's a code quality suggestion to make it cheaper to maintain the mod in the long term.
Currently, language keys in en_us.json are hardcoded directly in the source code:
import static com.github.exopandora.shouldersurfing.ShoulderSurfingCommon.MOD_ID;
.translation(MOD_ID + ".configuration.camera.offset.offset_z")
But it could be type-safe:
import com.github.exopandora.shouldersurfing.generated.LangKeys;
.translation(LangKeys.CONFIGURATION_CAMERA_OFFSET_OFFSET_Z)
I have created a simple, reusable Gradle plugin for this, which can be used in any JVM project, whether it's Kotlin or Java, even if it has no Minecraft dependencies.
This is type-safe and forces you to fix all references at compile time, while guaranteeing it will work (as long as it en_us.json file is loaded), with no possibility of typo mistakes, that cause misleading buggy behavior, or a crash.
See also an example difference in this Epic Fight PR.
This also removes the need for hardcoding the mod ID or MOD_ID + ".configuration.camera.offset.offset_z" or any other utility methods.
The generated object is not checked into the version control, and the plugin supports Kotlin as well, so the risk of using this Gradle plugin dependency is fairly low. In worst cases, you can replace it or write a custom Gradle task (example), and the source code won't need any updates in this case.
I'm trying to encourage mods to adapt it, in the hope that it becomes a standard in the modding community, so NeoForge and Fabric ship a similar functionality in their templates. I'm addicted to type-safety and modern patterns.
I would be happy to send a PR, though I'm uncertain whether this should be backported to previous MC branches, as it may cause future conflicts that are hard to maintain.
Thank you for the pointer! I think this is a neat plugin to make things easier and safer. But I'm not so sure about if it is a huge benefit for this project specifically. Translation keys are only used for the config system and a few keybinds. However, I would only opt for the plugin if all currently maintained branches (1.18.2, 1.19.2, 1.20.1, 1.21.1, 1.21.10/master) will use this plugin, as otherwise, it would make backporting more complicated than it needs to be.
But I'm not so sure about if it is a huge benefit for this project specifically. Translation keys are only used for the config system and a few keybinds.
It can be used for any JSON resource keys, and is not limited to en_us.json.
However, I would only opt for the plugin if all currently maintained branches (1.18.2, 1.19.2, 1.20.1, 1.21.1, 1.21.10/master) will use this plugin, as otherwise, it would make backporting more complicated than it needs to be.
I totally agree, any unnecessary differences between the branches will make the maintenance harder, which might outweigh the benefits of this change.
That said, we can still set it up and use it for new keys, file an issue, and link it in a TODO to track this change for all versions. Personally, I prefer to avoid TODOs or technical debts, and it's better to do it all at once.
Are there any huge differences in Minecraft imports in the file where Component.translatable() is used across these branches?
I would only opt for the plugin
Right now, I'm trying to publish it to the Gradle Portal platform, just to remove the need for adding a new Maven repository for a single plugin.
Are there any huge differences in Minecraft imports in the file where Component.translatable() is used across these branches?
Not that I can think of. The only classes where translations are used are InputHandler and Config.
Right now, I'm trying to publish it to the Gradle Portal platform, just to remove the need for adding a new Maven repository for a single plugin.
I would also like to wait until the plugin is available on the Gradle Portal platform.
Update: The plugin has been published now to Gradle Portal.
I have also made some additional fixes and implemented a convenient Gradle extension, simplifying the usage:
plugins {
// ...
id("dev.echoellet.mc-safe-resources") version("0.0.1")
}
mcSafeResources {
namespace.set(modId)
}
java.sourceSets.main.get().java.srcDirs(
tasks.generateLangKeys.map { it.outputs.files.singleFile },
tasks.generateSoundKeys.map { it.outputs.files.singleFile }
)
tasks.compileJava { dependsOn(tasks.generateLangKeys, tasks.generateSoundKeys) }
I'm not sure if we can simplify it further, in an opinionated way that works with more complex Gradle build scripts and configuration.
I would be happy to send a PR:
- Let me know if you request any additional changes.
- When backporting, should I cherry-pick the commits from the branch that was sent to the
master, or merge/rebase?
Sorry for the late reply! I was busy with other stuff. While I still like the idea, I think the UX could still be improved. For example, the DSL could be similar to the one provided by gradle-build-config.
Example sketch:
plugins {
id("dev.echoellet.mc-safe-resources") version("0.0.1")
}
mcSafeResources {
namespace.set(modId)
langKeys(sourceSets.main.get().resources.files.single { it.name == "en_us.json" })
soundKeys(sourceSets.main.get().resources.files.single { it.name == "sounds.json" }) // or whatever makes sense to reference
}