Skip to content

Commit

Permalink
Separate translation keys into their individual games
Browse files Browse the repository at this point in the history
  • Loading branch information
FluxCapacitor2 committed Dec 29, 2024
1 parent bb4f94a commit eda996f
Show file tree
Hide file tree
Showing 6 changed files with 146 additions and 798 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package com.bluedragonmc.server.bootstrap

import com.bluedragonmc.server.DEFAULT_LOCALE
import com.bluedragonmc.server.NAMESPACE
import com.bluedragonmc.server.queue.GameClassLoader
import net.kyori.adventure.key.Key
import net.kyori.adventure.text.Component
import net.kyori.adventure.translation.GlobalTranslator
import net.kyori.adventure.translation.GlobalTranslator.renderer
import net.kyori.adventure.translation.TranslationRegistry
import net.minestom.server.adventure.MinestomAdventure
import net.minestom.server.entity.Player
import net.minestom.server.event.Event
import net.minestom.server.event.EventNode
import java.net.URL
import java.util.*

/**
Expand All @@ -25,22 +24,66 @@ import java.util.*
object GlobalTranslation : Bootstrap() {
override fun hook(eventNode: EventNode<Event>) {
MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION = true
addSource(NAMESPACE, GlobalTranslation::class.java.classLoader)
}

val classLoader = GlobalTranslation::class.java.classLoader
val registry = TranslationRegistry.create(Key.key(NAMESPACE, "i18n"))
val translations = Properties().apply {
load(classLoader.getResourceAsStream("i18n.properties"))
/**
* Adds a source to the [GlobalTranslator] by looking for
* a `i18n.properties` file using the supplied [classLoader].
* `i18n.properties` should contain key formatted like
* `lang_${lang}`, where `${lang}` is the language code, like `en-US`.
* Language codes must work with [Locale.forLanguageTag].
*
* Then, values should be file names of `.properties` files that
* contain translations for the key's language.
*
* For example:
* ```properties
* lang_en-US=lang_en.properties
* lang_en-PT=lang_en_pt.properties
* lang_zh-CN=lang_zh_cn.properties
* ```
* Each `.properties` file specified as a value should
* contain translation keys. It will be loaded
* using the same [classLoader].
*/
fun addSource(namespace: String, classLoader: ClassLoader) {
val registry = TranslationRegistry.create(Key.key(namespace, "i18n"))
val translations = Properties()
val file = getEntryAsStream(classLoader, "i18n.properties")
if (file == null) {
logger.warn("i18n.properties file not found in namespace '$namespace'.")
return
}
translations.load(file)
val languages = translations.keys.map { it.toString() }.filter { it.startsWith("lang_") }
if (languages.isEmpty()) {
logger.warn("Could not find any translations in namespace '$namespace'.")
}
for (language in languages) {
val path = translations.getProperty(language)
val stream = getEntryAsStream(classLoader, path)
if (stream == null) {
logger.warn("Failed to find translation file '$path' in namespace '$namespace'.")
continue
}
val locale = Locale.forLanguageTag(language.substringAfter("lang_"))
val bundle = PropertyResourceBundle(classLoader.getResourceAsStream(path))
val bundle = PropertyResourceBundle(stream)
registry.registerAll(locale, bundle, true)
logger.debug("Registered language $language (locale: $locale) from file ${translations.getProperty(language)}")
}
registry.defaultLocale(DEFAULT_LOCALE)

GlobalTranslator.translator().addSource(registry)
}

private fun getEntryAsStream(classLoader: ClassLoader, name: String) = getEntry(classLoader, name)?.openStream()

private fun getEntry(classLoader: ClassLoader, name: String): URL? =
if (classLoader is GameClassLoader) {
classLoader.findResource(name)
} else {
classLoader.getResource(name)
}

}
26 changes: 13 additions & 13 deletions src/main/kotlin/com/bluedragonmc/server/queue/GameClassLoader.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,6 @@ package com.bluedragonmc.server.queue

import java.net.URL
import java.net.URLClassLoader
import java.nio.file.Path
import kotlin.io.path.extension
import kotlin.io.path.listDirectoryEntries

/**
* A class loader that loads all JAR files in a given directory.
Expand Down Expand Up @@ -36,22 +33,25 @@ class GameClassLoader(urls: Array<out URL>?) : URLClassLoader(urls) {
throw ex
}

init {
loaders.add(this)
}

override fun close() {
loaders.remove(this)
super.close()
}

companion object {
/**
* Overrides [URLClassLoader] to prioritize resources in the
* URL list over resources found by the parent [ClassLoader].
*/
override fun getResource(name: String?): URL? {
findResource(name)?.let { return it }
return super.getResource(name)
}

companion object {
internal val loaders = mutableSetOf<GameClassLoader>()

/**
* Returns the URLs of all JAR files in the given folder.
*/
private fun getUrls(folder: Path): Array<URL> {
return folder.listDirectoryEntries()
.filter { it.extension == "jar" }
.map { it.toUri().toURL() }.toTypedArray()
}
}
}
7 changes: 4 additions & 3 deletions src/main/kotlin/com/bluedragonmc/server/queue/GameLoader.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bluedragonmc.server.queue

import com.bluedragonmc.server.Game
import com.bluedragonmc.server.bootstrap.GlobalTranslation
import org.slf4j.LoggerFactory
import java.io.BufferedInputStream
import java.nio.file.Path
Expand Down Expand Up @@ -30,14 +31,14 @@ object GameLoader {
gamesList.forEach { (path, props) ->
val name = props.getProperty("name")
val mainClass = props.getProperty("main-class")
// Preload the game's main class
// Load the game's main class and translation keys
val ms = measureTimeMillis {
val classLoader = GameClassLoader(arrayOf(path.toUri().toURL()))
GameClassLoader.loaders.add(classLoader)
GlobalTranslation.addSource(name.lowercase().replace(Regex("[^a-zA-Z0-9]"), "_"), classLoader)
val clazz = classLoader.loadClass(mainClass)
classes[name] = clazz as Class<out Game>
}
logger.info("Initialized plugin '${props.getProperty("name")}' (${path.name}) in ${ms}ms")
logger.info("Initialized plugin '$name' (${path.name}) in ${ms}ms")
}
}

Expand Down
Loading

0 comments on commit eda996f

Please sign in to comment.