Browse Source

Add unit tests for pluralization rules

pull/4519/head
Chanjung Kim 8 months ago
parent
commit
d0eea88e2b
  1. 103
      components/buildSrc/src/main/kotlin/GeneratePluralRuleListsTask.kt
  2. 8
      components/resources/library/build.gradle.kts
  3. 26
      components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/PluralRulesTest.kt
  4. 18
      components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt

103
components/buildSrc/src/main/kotlin/GeneratePluralRuleListsTask.kt

@ -21,32 +21,75 @@ abstract class GeneratePluralRuleListsTask : DefaultTask() {
abstract val pluralsFile: RegularFileProperty
@get:OutputDirectory
abstract val outputDir: DirectoryProperty
abstract val mainDir: DirectoryProperty
@get:OutputDirectory
abstract val testDir: DirectoryProperty
@TaskAction
fun generatePluralRuleLists() {
val outputDir = outputDir.get().asFile
if (outputDir.exists()) {
outputDir.deleteRecursively()
}
outputDir.mkdirs()
generateDirectories()
val pluralRuleLists = parsePluralRuleLists()
val mainContent = generateMainContent(pluralRuleLists)
mainDir.get().asFile.resolve("cldr.kt").writeText(mainContent)
val testContent = generateTestContent(pluralRuleLists)
testDir.get().asFile.resolve("cldr.test.kt").writeText(testContent)
}
private fun generateDirectories() {
for (directoryProperty in arrayOf(mainDir, testDir)) {
val directory = directoryProperty.get().asFile
if (directory.exists()) {
directory.deleteRecursively()
}
directory.mkdirs()
}
}
private fun parsePluralRuleLists(): List<PluralRuleList> {
val parser = XmlParser(false, false).apply {
setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
}
val supplementalData = parser.parse(pluralsFile.get().asFile)
val pluralRuleLists = supplementalData.children().filterIsInstance<Node>().first { it.name() == "plurals" }
return pluralRuleLists.children().filterIsInstance<Node>().map { pluralRules ->
val locales = pluralRules.attribute("locales").toString().split(' ')
PluralRuleList(
locales,
pluralRules.children().filterIsInstance<Node>().map { pluralRule ->
val rule = pluralRule.text().split('@')
PluralRule(
pluralRule.attribute("count").toString(),
// trim samples as not needed
rule[0].trim(),
rule.firstOrNull { it.startsWith("integer") }?.substringAfter("integer")?.trim() ?: "",
rule.firstOrNull { it.startsWith("decimal") }?.substringAfter("decimal")?.trim() ?: "",
)
}
)
}
}
private fun generateMainContent(pluralRuleLists: List<PluralRuleList>): String {
val pluralRuleListIndexByLocale = pluralRuleLists.flatMapIndexed { idx, pluralRuleList ->
pluralRuleList.locales.map { locale ->
locale to idx
}
}
val fileContent = """
return """
package org.jetbrains.compose.resources.intl
internal val cldrPluralRuleListIndexByLocale = mapOf(
${pluralRuleListIndexByLocale.joinToString { (locale, idx) -> "\"$locale\" to $idx" }}
)
internal val cldrPluralRuleLists = arrayOf(
${
internal val cldrPluralRuleLists = arrayOf(${
pluralRuleLists.joinToString { pluralRuleList ->
"""
arrayOf(${
@ -56,34 +99,32 @@ abstract class GeneratePluralRuleListsTask : DefaultTask() {
})
""".trimIndent()
}
}
)
})
""".trimIndent()
outputDir.resolve("generated.kt").writeText(fileContent)
}
private fun parsePluralRuleLists(): List<PluralRuleList> {
val parser = XmlParser(false, false).apply {
setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false)
setFeature("http://apache.org/xml/features/disallow-doctype-decl", false)
private fun generateTestContent(pluralRuleLists: List<PluralRuleList>): String {
val pluralRuleIntegerSamplesByLocale = pluralRuleLists.flatMap { pluralRuleList ->
pluralRuleList.locales.map { locale ->
locale to pluralRuleList.rules.map { it.count to it.integerSample }
}
}
val supplementalData = parser.parse(pluralsFile.get().asFile)
val pluralRuleLists = supplementalData.children().filterIsInstance<Node>().first { it.name() == "plurals" }
return pluralRuleLists.children().filterIsInstance<Node>().map { pluralRules ->
val locales = pluralRules.attribute("locales").toString().split(' ')
PluralRuleList(
locales,
pluralRules.children().filterIsInstance<Node>().map { pluralRule ->
PluralRule(
pluralRule.attribute("count").toString(),
// trim samples as not needed
pluralRule.text().split('@')[0].trim(),
)
return """
package org.jetbrains.compose.resources
import org.jetbrains.compose.resources.intl.PluralCategory
internal val cldrPluralRuleIntegerSamples = arrayOf(${
pluralRuleIntegerSamplesByLocale.joinToString { (locale, samples) ->
""""$locale" to arrayOf(${
samples.joinToString { (count, sample) ->
"PluralCategory.${count.uppercase()} to \"$sample\""
}
)
} )""".trimIndent()
}
})
""".trimIndent()
}
}
@ -95,4 +136,6 @@ private data class PluralRuleList(
private data class PluralRule(
val count: String,
val rule: String,
val integerSample: String,
val decimalSample: String,
)

8
components/resources/library/build.gradle.kts

@ -70,9 +70,8 @@ kotlin {
pluralsFile = project.layout.projectDirectory.file(
"src/commonMain/kotlin/org/jetbrains/compose/resources/intl/plurals.xml"
)
outputDir = project.layout.buildDirectory.dir(
"generated/intl/kotlin"
)
mainDir = project.layout.buildDirectory.dir("generated/intl/commonMain/kotlin")
testDir = project.layout.buildDirectory.dir("generated/intl/commonTest/kotlin")
}
tasks.withType<KotlinCompile<*>> {
@ -85,7 +84,7 @@ kotlin {
implementation(compose.foundation)
implementation(libs.kotlinx.coroutines.core)
}
kotlin.srcDir(generatePluralRuleListsTask.flatMap { it.outputDir })
kotlin.srcDir(generatePluralRuleListsTask.flatMap { it.mainDir })
}
val commonTest by getting {
dependencies {
@ -95,6 +94,7 @@ kotlin {
@OptIn(ExperimentalComposeLibrary::class)
implementation(compose.uiTest)
}
kotlin.srcDir(generatePluralRuleListsTask.flatMap { it.testDir })
}
val blockingMain by creating {
dependsOn(commonMain)

26
components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/PluralRulesTest.kt

@ -0,0 +1,26 @@
/*
* Copyright 2020-2024 JetBrains s.r.o. and respective authors and developers.
* Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE.txt file.
*/
package org.jetbrains.compose.resources
import org.jetbrains.compose.resources.intl.PluralRuleList
import kotlin.test.*
class PluralRulesTest {
/**
* Test pluralization for integer quantities using the samples in Unicode CLDR.
*/
@Test
fun testIntegerSamples() {
for ((locale, samplesByCategory) in cldrPluralRuleIntegerSamples) {
val pluralRuleList = PluralRuleList.createInstance(locale)
for ((category, samples) in samplesByCategory) {
for (sample in parsePluralSamples(samples)) {
assertEquals(category, pluralRuleList.getCategory(sample))
}
}
}
}
}

18
components/resources/library/src/commonTest/kotlin/org/jetbrains/compose/resources/TestUtils.kt

@ -13,3 +13,21 @@ internal fun TestQuantityStringResource(key: String) = QuantityStringResource(
key,
setOf(ResourceItem(emptySet(), "strings.xml"))
)
fun parsePluralSamples(samples: String): List<Int> {
return samples.split(',').flatMap {
val range = it.trim()
when {
range.isEmpty() -> emptyList()
range in arrayOf("", "...") -> emptyList()
// ignore numbers in compact exponent format
range.contains('c') || range.contains('e') -> emptyList()
range.contains('~') -> {
val (start, endInclusive) = range.split('~')
return@flatMap (start.toInt()..endInclusive.toInt()).toList()
}
else -> listOf(range.toInt())
}
}
}
Loading…
Cancel
Save