diff --git a/build.gradle b/build.gradle index 07d4a1a..981d392 100644 --- a/build.gradle +++ b/build.gradle @@ -34,7 +34,6 @@ dependencies { compileOnly 'net.md-5:bungeecord-api:1.18-R0.1-SNAPSHOT' compileOnly 'xyz.theprogramsrc:simplecoreapi:0.1.9-SNAPSHOT' compileOnly 'xyz.theprogramsrc:filesmodule:0.1.0-SNAPSHOT' - compileOnly 'xyz.theprogramsrc:loggingmodule:0.2.0-SNAPSHOT' testImplementation 'org.junit.jupiter:junit-jupiter:5.8.2' } diff --git a/src/main/kotlin/xyz/theprogramsrc/translationsmodule/Main.kt b/src/main/kotlin/xyz/theprogramsrc/translationsmodule/Main.kt new file mode 100644 index 0000000..1cd47f2 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/translationsmodule/Main.kt @@ -0,0 +1,10 @@ +package xyz.theprogramsrc.translationsmodule + +import xyz.theprogramsrc.simplecoreapi.global.module.Module + +class Main: Module() { + + override fun onEnable() { + TranslationManager() + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/translationsmodule/TranslationManager.kt b/src/main/kotlin/xyz/theprogramsrc/translationsmodule/TranslationManager.kt new file mode 100644 index 0000000..bcd6379 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/translationsmodule/TranslationManager.kt @@ -0,0 +1,125 @@ +package xyz.theprogramsrc.translationsmodule + +import xyz.theprogramsrc.filesmodule.config.YmlConfig +import xyz.theprogramsrc.filesmodule.utils.folder +import xyz.theprogramsrc.translationsmodule.objects.Translation +import java.io.File + +/** + * Representation of the translation manager. + */ +class TranslationManager { + + private val translationsCache = mutableMapOf>() + private val cache = mutableMapOf>>() + + companion object { + private val translationSettings = YmlConfig(File(File("plugins/SimpleCoreAPI").folder(), "TranslationSettings.yml")).add("language", "en") + lateinit var instance: TranslationManager + + fun getCurrentLanguage(): String = translationSettings.getStringOrSet("language", "en") + } + + init { + instance = this + loadTranslations() + } + + /** + * Register the given translations to the given group id + * @param group The group (folder) of the translation. Defaults to "common" + * @param translations The translations to register + */ + fun registerTranslations(group: String = "common", translations: Collection) { + val translationsCache = this.translationsCache[group] ?: mutableListOf() + translations.forEach { t -> + if(translationsCache.find { it == t } == null) { + translationsCache.add(t) + } + } + this.translationsCache[group] = translationsCache + loadTranslations() + } + + /** + * Get the translation for the given id in the given language + * @param id The id of the translation + * @param language The language of the translation. Set to null to use the default language. Defaults to null + * @param group The group (folder) of the translation. Defaults to "common" + * @param placeholders The placeholders to replace in the translation. Defaults to empty map + * @return The translation. If the translation is not found, the id is returned + */ + fun translate(id: String, language: String? = null, group: String = "common", placeholders: Map = emptyMap()): String { + val cached = cache[group] ?: return id + var translation = ((cached[language] ?: cached[getCurrentLanguage()] ?: return id)[id] ?: return id) + placeholders.forEach { (key, value) -> + translation = translation.replace("{$key}", value).replace("%$key%", value) + } + return translation + } + + /** + * Loads the translations to the cache. + */ + fun loadTranslations() { + val translationsFolder = File("translations/").folder() + // First load the default translations + translationsCache.forEach { (group, translations) -> + val folder = File(translationsFolder, group).folder() + translations.forEach { + val languageFile = YmlConfig(File(folder, "${it.language}.lang")) + languageFile.add(it.id, it.defaultValue) + } + } + + // Then load the translations from the language files + (translationsFolder.listFiles() ?: emptyArray()).filter(File::isDirectory).forEach { groupFolder -> + val cached = cache[groupFolder.name] ?: mutableMapOf() + (groupFolder.listFiles() ?: emptyArray()).filter { it.extension == "lang" }.forEach { + val langCache = cached[it.nameWithoutExtension] ?: mutableMapOf() + val cfg = YmlConfig(it) + cfg.keys(true).forEach { id -> + val t = translationsCache[groupFolder.name]?.find { t1 -> t1.id == id } + if(t != null){ + langCache[id] = t.translate(groupFolder.name, it.nameWithoutExtension) + } + } + if(langCache.isNotEmpty()){ + cached[it.nameWithoutExtension] = langCache + } + } + + if(cached.isNotEmpty()) { + cache[groupFolder.name] = cached + } + } + } + + /** + * Count the amount of translations + * @param group The group (folder) of the translation. If null it'll count all the groups. Defaults to null + * @param lang The language of the translation. If null it'll count all the languages. Defaults to null + */ + fun countTranslations(group: String? = null, lang: String? = null): Int { + return if(group == null){ + cache.values.sumOf { + if(lang != null){ + it[lang]?.size ?: 0 + }else { + it.values.sumOf { it2 -> + it2.values.size + } + } + } + } else { + val c = (this.cache[group] ?: return -2) + if(lang != null){ + c[lang]?.size ?: 0 + }else { + c.values.sumOf { it2 -> + it2.values.size + } + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/xyz/theprogramsrc/translationsmodule/objects/Translation.kt b/src/main/kotlin/xyz/theprogramsrc/translationsmodule/objects/Translation.kt new file mode 100644 index 0000000..9fef660 --- /dev/null +++ b/src/main/kotlin/xyz/theprogramsrc/translationsmodule/objects/Translation.kt @@ -0,0 +1,58 @@ +package xyz.theprogramsrc.translationsmodule.objects + +import xyz.theprogramsrc.filesmodule.config.YmlConfig +import xyz.theprogramsrc.filesmodule.utils.folder +import xyz.theprogramsrc.translationsmodule.TranslationManager +import java.io.File + +/** + * Representation of a translation. + * @param id The id of the translation + * @param defaultValue The default value of the translation. + * @param language The language of the translation. (Defaults to "en") + * @param colors The colors to use in the translation replacing strings. Example (using color '&c'): '**test**' should return '&ctest'. Defaults to empty array. + * @param placeholders The placeholders to use in the translation replacing strings. Example (using placeholder id 'test' and value 'test_value'): '{test}' should return 'test_value'. + * You can use '{}' or '%%' as placeholder identifiers like '{test}' or '%test%'. Defaults to empty map. + */ +data class Translation(val id: String, val defaultValue: String, val language: String = "en", val colors: Array = emptyArray()) { + + /** + * Translates this [Translation] to the current language. + * @param group The group (folder) of the translation. Defaults to "common" + * @param language The language of the translation. Set to null to use the default language. Defaults to null + * @return The translated string. + */ + fun translate(group: String = "common", language: String? = null): String { + val file = YmlConfig(File(File("translations/${if(group.endsWith("/")) group else "$group/"}").folder(), (language ?: TranslationManager.getCurrentLanguage()) + ".lang")) + var translation = if(file.has(id)) file.getString(id) else defaultValue + for(i in colors.indices) { + try { + val color = colors[i] + val string = Regex("\\*\\*(.+?)\\*\\*").findAll(translation).first().groupValues[1] + translation = translation.replaceFirst("**$string**", "$color$string") + }catch (_: Exception){} + } + + return translation + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as Translation + + if (id != other.id) return false + if (defaultValue != other.defaultValue) return false + if (!colors.contentEquals(other.colors)) return false + + return true + } + + override fun hashCode(): Int { + var result = id.hashCode() + result = 31 * result + defaultValue.hashCode() + result = 31 * result + colors.contentHashCode() + return result + } +} diff --git a/src/main/resources/module.properties b/src/main/resources/module.properties new file mode 100644 index 0000000..579a883 --- /dev/null +++ b/src/main/resources/module.properties @@ -0,0 +1,7 @@ +main=xyz.theprogramsrc.translationsmodule.Main +name=TranslationsModule +description=@description@ +version=@version@ +author=TheProgramSrc +repository-id=translationsmodule +dependencies=FilesModule diff --git a/src/test/kotlin/xyz/theprogramsrc/translationsmodule/TranslationManagerTest.kt b/src/test/kotlin/xyz/theprogramsrc/translationsmodule/TranslationManagerTest.kt new file mode 100644 index 0000000..fab244a --- /dev/null +++ b/src/test/kotlin/xyz/theprogramsrc/translationsmodule/TranslationManagerTest.kt @@ -0,0 +1,52 @@ +package xyz.theprogramsrc.translationsmodule + +import org.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.Test +import xyz.theprogramsrc.translationsmodule.objects.Translation + +internal class TranslationManagerTest { + private val tm = TranslationManager() + private val translations = listOf( + Translation( + id = "id", + defaultValue = "My **colorful** translation!", + language = "en", + colors = arrayOf("&a"), + ), + Translation( + id = "id2", + defaultValue = "My **{text}** translation!", + language = "en", + colors = arrayOf("&a"), + ), + Translation( + id = "id3", + defaultValue = "My **{text}** translation!", + language = "en", + ), + ) + + @Test + fun countTest(){ + tm.registerTranslations(translations = translations) + assertEquals(3, tm.countTranslations()) + } + + @Test + fun translationId1Test(){ + tm.registerTranslations(translations = translations) + assertEquals("My &acolorful translation!", tm.translate("id", "en")) + } + + @Test + fun translationId2Test() { + tm.registerTranslations(translations = translations) + assertEquals("My &acolorful translation!", tm.translate("id2", "en", placeholders = mapOf("text" to "colorful"))) + } + + @Test + fun translationId3Test() { + tm.registerTranslations(translations = translations) + assertEquals("My **colorful** translation!", tm.translate("id3", "en", placeholders = mapOf("text" to "colorful"))) + } +} \ No newline at end of file