a lot of improvements in language codes

This commit is contained in:
InsanusMokrassar 2023-12-21 15:41:11 +06:00
parent 7ab21871cd
commit 48d3fe41f2
8 changed files with 2992 additions and 2477 deletions

View File

@ -2,6 +2,12 @@
## 0.20.23 ## 0.20.23
* `LanguageCodes`:
* Fixes in intermediate language codes (like `Chinese.Hans`)
* Rename `IetfLanguageCode` to `IetfLang`
* Rename all subsequent functions (including serializer)
* New lazy properties `knownLanguageCodesMap`, `knownLanguageCodesMapByLowerCasedKeys` and several others
## 0.20.22 ## 0.20.22
* `Common`: * `Common`:

View File

@ -12,9 +12,12 @@ private val json = Json {
ignoreUnknownKeys = true ignoreUnknownKeys = true
} }
private const val baseClassName = "IetfLanguageCode" private const val baseClassName = "IetfLang"
private const val oldBaseClassName = "IetfLanguageCode"
private const val unknownBaseClassName = "Unknown$baseClassName" private const val unknownBaseClassName = "Unknown$baseClassName"
private const val baseClassSerializerName = "IetfLanguageCodeSerializer" private const val oldUnknownBaseClassName = "Unknown$oldBaseClassName"
private const val baseClassSerializerName = "${baseClassName}Serializer"
private const val oldBaseClassSerializerName = "IetfLanguageCodeSerializer"
private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)" private const val baseClassSerializerAnnotationName = "@Serializable(${baseClassSerializerName}::class)"
@Serializable @Serializable
@ -78,14 +81,12 @@ private fun printLanguageCodeAndTags(
indents: String = " " indents: String = " "
): String = if (tag.subtags.isEmpty()) { ): String = if (tag.subtags.isEmpty()) {
"""${indents}${baseClassSerializerAnnotationName} """${indents}${baseClassSerializerAnnotationName}
${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"; override val withoutDialect: String get() = ${parent ?.title ?.let { "$it.code" } ?: "code"} }""" ${indents}object ${tag.title} : ${parent ?.title ?: baseClassName}() { override val code: String = "${tag.tag}"${parent ?.let { parent -> "; override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""} }"""
} else { } else {
""" """
${indents}${baseClassSerializerAnnotationName} ${indents}${baseClassSerializerAnnotationName}
${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() { ${indents}sealed class ${tag.title} : ${parent ?.title ?: baseClassName}() {
${indents} override val code: String = "${tag.tag}" ${indents} override val code: String = "${tag.tag}"${parent ?.let { parent -> "\n${indents} override val parentLang: ${parent.title} get() = ${parent.title};" } ?: ""}
${indents} override val withoutDialect: String
${indents} get() = code
${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }} ${tag.subtags.joinToString("\n") { printLanguageCodeAndTags(it, tag, "${indents} ") }}
@ -95,7 +96,7 @@ ${indents}}
""" """
} }
fun buildKtFileContent(tags: List<Tag>): String = """ fun buildKtFileContent(tags: List<Tag>, prePackage: String): String = """
import kotlinx.serialization.Serializable import kotlinx.serialization.Serializable
/** /**
@ -106,20 +107,27 @@ import kotlinx.serialization.Serializable
${baseClassSerializerAnnotationName} ${baseClassSerializerAnnotationName}
sealed class $baseClassName { sealed class $baseClassName {
abstract val code: String abstract val code: String
abstract val withoutDialect: String open val parentLang: $baseClassName?
get() = null
open val withoutDialect: String
get() = parentLang ?.code ?: code
${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } } ${tags.joinToString("\n") { printLanguageCodeAndTags(it, indents = " ") } }
$baseClassSerializerAnnotationName $baseClassSerializerAnnotationName
data class $unknownBaseClassName (override val code: String) : $baseClassName() { data class $unknownBaseClassName (override val code: String) : $baseClassName() {
override val withoutDialect: String = code.takeWhile { it != '-' } override val parentLang = code.dropLastWhile { it != '-' }.removeSuffix("-").takeIf { it.length > 0 } ?.let(::$unknownBaseClassName)
} }
@Deprecated("Renamed", ReplaceWith("$baseClassName.$unknownBaseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName.$unknownBaseClassName"))
val $oldUnknownBaseClassName = $unknownBaseClassName
override fun toString() = code override fun toString() = code
} }
@Deprecated("Renamed", ReplaceWith("$baseClassName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
typealias $oldBaseClassName = $baseClassName
""".trimIndent() """.trimIndent()
fun createStringConverterCode(tags: List<Tag>): String { fun createStringConverterCode(tags: List<Tag>, prePackage: String): String {
fun createDeserializeVariantForTag( fun createDeserializeVariantForTag(
tag: Tag, tag: Tag,
pretitle: String = baseClassName, pretitle: String = baseClassName,
@ -128,19 +136,70 @@ fun createStringConverterCode(tags: List<Tag>): String {
val currentTitle = "$pretitle.${tag.title}" val currentTitle = "$pretitle.${tag.title}"
return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}""" return """${indents}$currentTitle.code -> $currentTitle${if (tag.subtags.isNotEmpty()) tag.subtags.joinToString("\n", "\n") { createDeserializeVariantForTag(it, currentTitle, indents) } else ""}"""
} }
fun createInheritorVariantForTag(
return """fun String.as$baseClassName(): $baseClassName { tag: Tag,
return when (this) { pretitle: String = baseClassName,
${tags.joinToString("\n") { createDeserializeVariantForTag(it) }} indents: String = " "
else -> $baseClassName.${unknownBaseClassName}(this) ): String {
val currentTitle = "$pretitle.${tag.title}"
val subtags = if (tag.subtags.isNotEmpty()) {
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForTag(it, currentTitle, "$indents ") }
} else {
""
}
return "${indents}$currentTitle$subtags"
} }
fun createInheritorVariantForMapForTag(
tag: Tag,
pretitle: String = baseClassName,
indents: String = " ",
codeSuffix: String = ""
): String {
val currentTitle = "$pretitle.${tag.title}"
val subtags = if (tag.subtags.isNotEmpty()) {
tag.subtags.joinToString(",\n", ",\n") { createInheritorVariantForMapForTag(it, currentTitle, "$indents ", codeSuffix) }
} else {
""
}
return "${indents}$currentTitle.code${codeSuffix} to $currentTitle$subtags"
}
return """val knownLanguageCodesMap: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ") }}
)
} }
val knownLanguageCodesMapByLowerCasedKeys: Map<String, $baseClassName> by lazy {
mapOf(
${tags.joinToString(",\n") { createInheritorVariantForMapForTag(it, indents = " ", codeSuffix = ".lowercase()") }}
)
}
val knownLanguageCodes: List<$baseClassName> by lazy {
knownLanguageCodesMap.values.toList()
}
fun String.as$baseClassName(): $baseClassName {
return knownLanguageCodesMap[this] ?: $baseClassName.${unknownBaseClassName}(this)
}
fun String.as${baseClassName}CaseInsensitive(): $baseClassName {
return knownLanguageCodesMapByLowerCasedKeys[this.lowercase()] ?: $baseClassName.${unknownBaseClassName}(this)
}
@Deprecated("Renamed", ReplaceWith("this.as$baseClassName()", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}as$baseClassName"))
fun String.as$oldBaseClassName(): $baseClassName = as$baseClassName()
fun convertTo$baseClassName(code: String) = code.as$baseClassName() fun convertTo$baseClassName(code: String) = code.as$baseClassName()
fun convertTo${baseClassName}CaseInsensitive(code: String) = code.as${baseClassName}CaseInsensitive()
@Deprecated("Renamed", ReplaceWith("convertTo$baseClassName(code)", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}convertTo$baseClassName"))
fun convertTo$oldBaseClassName(code: String) = convertTo$baseClassName(code)
fun $baseClassName(code: String) = code.as$baseClassName() fun $baseClassName(code: String) = code.as$baseClassName()
fun ${baseClassName}CaseInsensitive(code: String) = code.as${baseClassName}CaseInsensitive()
@Deprecated("Renamed", ReplaceWith("$baseClassName(code)", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassName"))
fun $oldBaseClassName(code: String) = $baseClassName(code)
""" """
} }
fun createSerializerCode(tags: List<Tag>): String { fun createSerializerCode(tags: List<Tag>, prePackage: String): String {
return """import kotlinx.serialization.KSerializer return """import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder import kotlinx.serialization.encoding.Decoder
@ -153,16 +212,20 @@ object $baseClassSerializerName : KSerializer<$baseClassName> {
return $baseClassName(decoder.decodeString()) return $baseClassName(decoder.decodeString())
} }
override fun serialize(encoder: Encoder, value: IetfLanguageCode) { override fun serialize(encoder: Encoder, value: $baseClassName) {
encoder.encodeString(value.code) encoder.encodeString(value.code)
} }
} }
@Deprecated("Renamed", ReplaceWith("$baseClassSerializerName", "${if (prePackage.isNotEmpty()) "$prePackage." else ""}$baseClassSerializerName"))
typealias $oldBaseClassSerializerName = $baseClassSerializerName
""" """
} }
suspend fun main(vararg args: String) { suspend fun main(vararg args: String) {
val outputFolder = args.firstOrNull() ?.let { File(it) } val outputFolder = args.firstOrNull() ?.let { File(it) }
outputFolder ?.mkdirs() outputFolder ?.mkdirs()
val targetPackage = args.getOrNull(1)
val targetPackagePrefix = targetPackage ?.let { "package $it\n\n" } ?: ""
val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json" val ietfLanguageCodesLink = "https://datahub.io/core/language-codes/r/language-codes.json"
val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json" val ietfLanguageCodesAdditionalTagsLink = "https://datahub.io/core/language-codes/r/ietf-language-tags.json"
@ -203,18 +266,18 @@ suspend fun main(vararg args: String) {
File(outputFolder, "LanguageCodes.kt").apply { File(outputFolder, "LanguageCodes.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(buildKtFileContent(tags)) writeText(targetPackagePrefix + buildKtFileContent(tags, targetPackage ?: ""))
} }
File(outputFolder, "StringToLanguageCodes.kt").apply { File(outputFolder, "StringToLanguageCodes.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(createStringConverterCode(tags)) writeText(targetPackagePrefix + createStringConverterCode(tags, targetPackage ?: ""))
} }
File(outputFolder, "$baseClassSerializerName.kt").apply { File(outputFolder, "$baseClassSerializerName.kt").apply {
delete() delete()
createNewFile() createNewFile()
writeText(createSerializerCode(tags)) writeText(targetPackagePrefix + createSerializerCode(tags, targetPackage ?: ""))
} }
} }

View File

@ -0,0 +1,20 @@
package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object IetfLangSerializer : KSerializer<IetfLang> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): IetfLang {
return IetfLang(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLang) {
encoder.encodeString(value.code)
}
}
@Deprecated("Renamed", ReplaceWith("IetfLangSerializer", "dev.inmo.micro_utils.language_codes.IetfLangSerializer"))
typealias IetfLanguageCodeSerializer = IetfLangSerializer

View File

@ -1,18 +0,0 @@
package dev.inmo.micro_utils.language_codes
import kotlinx.serialization.KSerializer
import kotlinx.serialization.builtins.serializer
import kotlinx.serialization.encoding.Decoder
import kotlinx.serialization.encoding.Encoder
object IetfLanguageCodeSerializer : KSerializer<IetfLanguageCode> {
override val descriptor = String.serializer().descriptor
override fun deserialize(decoder: Decoder): IetfLanguageCode {
return IetfLanguageCode(decoder.decodeString())
}
override fun serialize(encoder: Encoder, value: IetfLanguageCode) {
encoder.encodeString(value.code)
}
}

View File

@ -0,0 +1,16 @@
package dev.inmo.micro_utils.language_codes
import kotlin.test.Test
import kotlin.test.assertEquals
class UnknownIetfLanguageTests {
@Test
fun commonTestOfParentCode() {
knownLanguageCodes.forEach {
val language = IetfLang.UnknownIetfLang(it.code)
assertEquals(it.code, language.code)
assertEquals(it.withoutDialect, language.withoutDialect)
assertEquals(it.parentLang ?.code, language.parentLang ?.code)
}
}
}

View File

@ -2,6 +2,19 @@ package dev.inmo.micro_utils.strings
import dev.inmo.micro_utils.language_codes.IetfLanguageCode import dev.inmo.micro_utils.language_codes.IetfLanguageCode
/**
* Use this class as a type of your strings object fields. For example:
*
* ```kotlin
* object Strings {
* val someResource: StringResource
* }
* ```
*
* Use [buildStringResource] for useful creation of string resource
*
* @see buildStringResource
*/
class StringResource( class StringResource(
val default: String, val default: String,
val map: Map<IetfLanguageCode, Lazy<String>> val map: Map<IetfLanguageCode, Lazy<String>>
@ -14,14 +27,24 @@ class StringResource(
infix fun IetfLanguageCode.variant(value: Lazy<String>) { infix fun IetfLanguageCode.variant(value: Lazy<String>) {
map[this] = value map[this] = value
} }
infix fun IetfLanguageCode.variant(value: () -> String) = this variant lazy(value)
infix fun IetfLanguageCode.variant(value: String) = this variant lazyOf(value) infix fun IetfLanguageCode.variant(value: String) = this variant lazyOf(value)
infix fun String.variant(value: Lazy<String>) = IetfLanguageCode(this) variant value infix fun String.variant(value: Lazy<String>) = IetfLanguageCode(this) variant value
infix fun String.variant(value: () -> String) = IetfLanguageCode(this) variant lazy(value)
infix fun String.variant(value: String) = this variant lazyOf(value) infix fun String.variant(value: String) = this variant lazyOf(value)
fun build() = StringResource(default, map.toMap()) fun build() = StringResource(default, map.toMap())
} }
fun translation(languageCode: IetfLanguageCode): String = (map[languageCode] ?: map[IetfLanguageCode(languageCode.withoutDialect)]) ?.value ?: default fun translation(languageCode: IetfLanguageCode): String {
map[languageCode] ?.let { return it.value }
return languageCode.parentLang ?.let {
map[it] ?.value
} ?: default
}
} }
inline fun buildStringResource( inline fun buildStringResource(