diff --git a/src/main/kotlin/buj/tl/IL.kt b/src/main/kotlin/buj/tl/IL.kt new file mode 100644 index 0000000..5b8e59e --- /dev/null +++ b/src/main/kotlin/buj/tl/IL.kt @@ -0,0 +1,6 @@ +package buj.tl + +interface IL> { + fun put(key: String, value: Script): Self + fun put(key: String, text: String): Self +} diff --git a/src/main/kotlin/buj/tl/ILVoidDone.kt b/src/main/kotlin/buj/tl/ILVoidDone.kt new file mode 100644 index 0000000..9d561b3 --- /dev/null +++ b/src/main/kotlin/buj/tl/ILVoidDone.kt @@ -0,0 +1,5 @@ +package buj.tl + +interface ILVoidDone> : IL { + fun done(key: String) +} diff --git a/src/main/kotlin/buj/tl/IScript.kt b/src/main/kotlin/buj/tl/IScript.kt new file mode 100644 index 0000000..79231f6 --- /dev/null +++ b/src/main/kotlin/buj/tl/IScript.kt @@ -0,0 +1,6 @@ +package buj.tl + +interface IScript { + fun append(ctx: LCtx, source: String, lang: String): String + fun debug(): String +} diff --git a/src/main/kotlin/buj/tl/ITranslationLocaleRegistry.kt b/src/main/kotlin/buj/tl/ITranslationLocaleRegistry.kt new file mode 100644 index 0000000..1b21a97 --- /dev/null +++ b/src/main/kotlin/buj/tl/ITranslationLocaleRegistry.kt @@ -0,0 +1,6 @@ +package buj.tl + +interface ITranslationLocaleRegistry { + fun register(loader: ClassLoader) + fun all(): List +} diff --git a/src/main/kotlin/buj/tl/ITranslationLocaleResolver.kt b/src/main/kotlin/buj/tl/ITranslationLocaleResolver.kt new file mode 100644 index 0000000..db35262 --- /dev/null +++ b/src/main/kotlin/buj/tl/ITranslationLocaleResolver.kt @@ -0,0 +1,5 @@ +package buj.tl + +interface ITranslationLocaleResolver { + fun resolve(locale: String, key: String): Script +} diff --git a/src/main/kotlin/buj/tl/ITranslationParserFactory.kt b/src/main/kotlin/buj/tl/ITranslationParserFactory.kt new file mode 100644 index 0000000..9f361b1 --- /dev/null +++ b/src/main/kotlin/buj/tl/ITranslationParserFactory.kt @@ -0,0 +1,3 @@ +package buj.tl + +interface ITranslationParserFactory { fun create(): ITranslationScriptParser } diff --git a/src/main/kotlin/buj/tl/ITranslationParserProvider.kt b/src/main/kotlin/buj/tl/ITranslationParserProvider.kt new file mode 100644 index 0000000..a980596 --- /dev/null +++ b/src/main/kotlin/buj/tl/ITranslationParserProvider.kt @@ -0,0 +1,3 @@ +package buj.tl + +interface ITranslationParserProvider { fun provide(): ITranslationParserFactory } diff --git a/src/main/kotlin/buj/tl/ITranslationParserSelector.kt b/src/main/kotlin/buj/tl/ITranslationParserSelector.kt new file mode 100644 index 0000000..ea5b62f --- /dev/null +++ b/src/main/kotlin/buj/tl/ITranslationParserSelector.kt @@ -0,0 +1,3 @@ +package buj.tl + +interface ITranslationParserSelector { fun select(): ITranslationParserProvider } diff --git a/src/main/kotlin/buj/tl/ITranslationRuntime.kt b/src/main/kotlin/buj/tl/ITranslationRuntime.kt new file mode 100644 index 0000000..58742ec --- /dev/null +++ b/src/main/kotlin/buj/tl/ITranslationRuntime.kt @@ -0,0 +1,13 @@ +package buj.tl + +import mindustry.gen.Player + +interface ITranslationRuntime { + fun broadcast(): La + fun send(player: Player): Lc + fun fmtFor(player: Player): Ls + fun fmt(locale: String): Ls + fun fmt(player: Player): Ls + fun init(loader: ClassLoader) + fun parse(script: String): Script +} diff --git a/src/main/kotlin/buj/tl/ITranslationScriptParser.kt b/src/main/kotlin/buj/tl/ITranslationScriptParser.kt new file mode 100644 index 0000000..44775f7 --- /dev/null +++ b/src/main/kotlin/buj/tl/ITranslationScriptParser.kt @@ -0,0 +1,5 @@ +package buj.tl + +interface ITranslationScriptParser { + fun parse(source: String): Script +} diff --git a/src/main/kotlin/buj/tl/Tl.kt b/src/main/kotlin/buj/tl/Tl.kt index f3ba86b..e538bea 100644 --- a/src/main/kotlin/buj/tl/Tl.kt +++ b/src/main/kotlin/buj/tl/Tl.kt @@ -9,262 +9,6 @@ import mindustry.gen.Groups import mindustry.gen.Player import java.io.IOException -private fun minIdx(vararg vals: Int): Int { - var min = -1 - for (v in vals) if (v != -1 && v < min || min == -1) min = v - return min -} - -private fun destructLocale(locale: String): Array { - val san = locale.replace(Regex("[^a-zA-Z0-9_-]"), "_") - .replace(Regex("_+"), "_") - .replace(Regex("^_|_$"), "") - .lowercase() - if (san.indexOf('_') > 0) return arrayOf(san, san.take(san.indexOf('_')), "c") - if (san != "c") return arrayOf(san, "c") - return arrayOf("c") -} -// @formatter:off -private val COLORS = arrayOf( - "clear", "black", "white", "lightgray", - "gray", "darkgray", "blue", "navy", - "royal", "slate", "sky", "cyan", - "teal", "green", "acid", "lime", - "forest", "olive", "yellow", "gold", - "goldenrod", "orange", "brown", "tan", - "brick", "red", "scarlet", "coral", - "salmon", "pink", "magenta", "purple", - "violet", "maroon", -) -// @formatter:on -private fun isValidColor(color: String): Boolean { - if (color.startsWith("#")) return color.matches(Regex("^#([0-9a-fA-F]{1,6}|[0-9a-fA-F]{8})$")) - return color in COLORS -} - -private val loaders = Seq() -private val localeCache = ObjectMap>() - -internal class LocaleFile { - companion object { - operator fun get(localeBase: String, key: String): Script { - var i = loaders.size - while (--i >= 0) { - val loader = loaders[i] - for (locale in destructLocale(localeBase)) { - val file = this[loader, locale] - if (file == null || !file.tls.containsKey(key)) continue - return file.tls.get(key) - } - } - return ScrText("[red]$key[]") - } - - operator fun get(loader: ClassLoader, locale: String): LocaleFile? { - val cache = localeCache.get(loader) { ObjectMap() } - if (cache.containsKey(locale)) { - return cache.get(locale) - } - - val str: String - try { - val stream = loader.getResourceAsStream("lang/${locale}.l") - if (stream == null) { - cache.put(locale, null) - return null - } - val bytes = stream.readBytes() - str = bytes.toString(Charsets.UTF_8) - } catch (_: IOException) { - return null - } - - val file = parse(str) - - cache.put(locale, file) - return file - } - - internal fun parse(str: String): LocaleFile { - val span = StringSpan(str) - - val file = LocaleFile() - var prefix = "" - var name = "" - var collection = TemplateSpanCollection() - - while (!span.isEmpty) { - span.trimStart() - - if (span.stripPrefix("#")) { - if (name.isNotEmpty()) { - if (file.tls.put(name, Tl.parse(collection.intoSpanCollection())) != null) throw RuntimeException("duplicate key $name") - collection = TemplateSpanCollection() - } - - name = "" - while (!span.isEmpty && span[0] != '\n') span.inc() - continue - } - - if (span.stripPrefix("[")) { - if (name.isNotEmpty()) { - if (file.tls.put(name, Tl.parse(collection.intoSpanCollection())) != null) throw RuntimeException("duplicate key $name") - collection = TemplateSpanCollection() - } - - name = "" - span.trimStart() - prefix = buildString { - while (!span.isEmpty && (span[0].isLetterOrDigit() || span[0] in "-_.")) append(span.next()) - } - span.trimStart() - if (!span.stripPrefix("]")) throw RuntimeException("unenclosed prefix statement at ${span.at}") - continue - } - - if (span.stripPrefix("=")) { - if (name.isEmpty()) throw RuntimeException("attempt to append a value despite not having an entry open") - - // It's fine since it's just one character - collection.growFrom(StringSpan("\n")) - span.stripPrefix(" ") - while (!span.isEmpty && span[0] != '\n') { - if (span[0] == '\r') { - span.inc() - continue - } - collection.growFrom(span) - span.inc() - } - continue - } - - // Both do not do the same thing anymore. - if (span.stripPrefix("-")) { - if (name.isEmpty()) throw RuntimeException("attempt to append a value despite not having an entry open") - - collection.growFrom(StringSpan(" ")) - span.stripPrefix(" ") - while (!span.isEmpty && span[0] != '\n') { - if (span[0] == '\r') { - span.inc() - continue - } - collection.growFrom(span) - span.inc() - } - continue - } - - if (span.stripPrefix(":")) { - if (name.isEmpty()) throw RuntimeException("attempt to append a value despite not having an entry open") - - span.stripPrefix(" ") - while (!span.isEmpty && span[0] != '\n') { - if (span[0] == '\r') { - span.inc() - continue - } - collection.growFrom(span) - span.inc() - } - continue - } - - if (name.isNotEmpty()) { - if (file.tls.put(name, Tl.parse(collection.intoSpanCollection())) != null) throw RuntimeException("duplicate key $name") - collection = TemplateSpanCollection() - } - - val at = span.at - - name = buildString { - while (!span.isEmpty && (span[0].isLetterOrDigit() || span[0] in "-_.")) append(span.next()) - } - - if (name.isEmpty()) continue - - name = (if (prefix.isEmpty()) { "" } else { "$prefix." }) + name - if (name.startsWith(".")) throw RuntimeException("entry name cannot start with a dot ($name) at $at") - if (name.endsWith(".")) throw RuntimeException("entry name cannot end with a dot ($name) at $at") - if (name.contains("..")) throw RuntimeException("entry name cannot contain 2 consecutive dots ($name) at $at") - - span.trimStart() - if (!span.stripPrefix("=")) throw RuntimeException("'=' must be used to assign a value to the entry at ${span.at}") - - val t = StringBuilder() - - span.stripPrefix(" ") - while (!span.isEmpty && span[0] != '\n') { - if (span[0] == '\r') { - span.inc() - continue - } - collection.growFrom(span) - span.inc() - } - } - - if (name.isNotEmpty()) { - if (file.tls.put(name, Tl.parse(collection.intoSpanCollection())) != null) throw RuntimeException("duplicate key $name") - } - - return file - } - } - - private val tls = ObjectMap() - - operator fun get(key: String): Script? = if (tls.containsKey(key)) tls[key] else null -} - -private fun encloseColors(s: String): String { - var s = s - - if (s.isEmpty()) return s - - var colorNestness = 0 - var ptr = 0 - - while (run { ptr = s.indexOf('[', ptr); ptr } != -1) { - val start = ptr - ptr = s.indexOf(']', ptr) - if (ptr == -1) break - - val maybeColor = s.substring(start + 1, ptr) - if (maybeColor.contains('[')) { - ptr = start + 1 + maybeColor.indexOf('[') - continue - } - if (isValidColor(maybeColor)) colorNestness += 1 - else if (start + 1 == ptr) { - if (colorNestness == 0) { - s = if (ptr + 1 == s.length) s.substring(0, start) - else s.substring(0, start) + s.substring(ptr + 1) - ptr -= 2 - } else { - colorNestness -= 1 - } - } - } - - return closeColors(s) + run { val b = StringBuilder(colorNestness * 2); repeat(colorNestness) { b.append("[]") }; b } -} - -private fun closeColors(s: String): String { - if (s.isEmpty()) return s - - var ptr = s.length - 1 - while ((s[ptr] in 'a'..'f' || s[ptr] in 'A'..'F' || s[ptr] == '#') && ptr > 0) ptr-- - if (s[ptr] != '[') return s - val start = ptr - while (s[ptr] == '[' && ptr > 0) ptr-- - if ((start - ptr) % 2 == 1) return s - - return s.substring(0, start) + "[" -} - class LCtx(private val parent: LCtx? = null) { private val tls = ObjectMap() @@ -280,20 +24,12 @@ class LCtx(private val parent: LCtx? = null) { fun tl(key: String, lang: String): Script { if (tls.containsKey(key)) return tls[key] - return LocaleFile[lang, key] + return Tl.resolve(lang, key) } } -interface L> { - /** Add a mapping for a translation key. */ - fun put(key: String, value: Script): Self - /** Set translation key to an unformatted text. */ - fun put(key: String, text: String): Self -} - -interface LVoidDone> : L { - fun done(key: String) -} +typealias L = IL +typealias LVoidDone = ILVoidDone class La(val ctx: LCtx = LCtx()) : LVoidDone { /** Add a mapping for a translation key. */ @@ -352,730 +88,7 @@ class Ls(val locale: String, val ctx: LCtx = LCtx()) : L { } } -interface Script { - fun append(ctx: LCtx, source: String, lang: String): String - fun debug(): String -} - -private class ScrCombo(val list: Seq