Custom i18n module with HOCON translation files, CLDR-aware pluralization for 40+ locales, namespace::group.item key resolution, :name/:Name/:NAME parameter interpolation, and locale fallback chains. No effect system deps.
214 lines
9.5 KiB
Scala
214 lines
9.5 KiB
Scala
package summer.phrasebook
|
|
|
|
class PluralRulesSpec extends munit.FunSuite:
|
|
|
|
// --- getPluralIndex tests ---
|
|
|
|
test("English: 2 forms (singular/plural)") {
|
|
assertEquals(PluralRules.getPluralIndex("en", 0), 1)
|
|
assertEquals(PluralRules.getPluralIndex("en", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("en", 2), 1)
|
|
assertEquals(PluralRules.getPluralIndex("en", 100), 1)
|
|
}
|
|
|
|
test("French: 0 and 1 are singular") {
|
|
assertEquals(PluralRules.getPluralIndex("fr", 0), 0)
|
|
assertEquals(PluralRules.getPluralIndex("fr", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("fr", 2), 1)
|
|
assertEquals(PluralRules.getPluralIndex("fr", 5), 1)
|
|
}
|
|
|
|
test("Polish: 3 forms (one/few/many)") {
|
|
assertEquals(PluralRules.getPluralIndex("pl", 1), 0) // one
|
|
assertEquals(PluralRules.getPluralIndex("pl", 2), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("pl", 3), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("pl", 4), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("pl", 5), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("pl", 10), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("pl", 11), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("pl", 12), 2) // many (12-14 are exceptions)
|
|
assertEquals(PluralRules.getPluralIndex("pl", 13), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("pl", 14), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("pl", 22), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("pl", 23), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("pl", 24), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("pl", 25), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("pl", 100), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("pl", 112), 2) // many (n%100=12, exception)
|
|
}
|
|
|
|
test("Russian: 3 forms (Slavic rule)") {
|
|
assertEquals(PluralRules.getPluralIndex("ru", 1), 0) // one
|
|
assertEquals(PluralRules.getPluralIndex("ru", 2), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("ru", 5), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("ru", 11), 2) // many (exception: 11-19)
|
|
assertEquals(PluralRules.getPluralIndex("ru", 21), 0) // one
|
|
assertEquals(PluralRules.getPluralIndex("ru", 22), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("ru", 25), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("ru", 111), 2) // many (n%100=11)
|
|
assertEquals(PluralRules.getPluralIndex("ru", 121), 0) // one (n%10=1, n%100=21)
|
|
}
|
|
|
|
test("Czech: 3 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("cs", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("cs", 2), 1)
|
|
assertEquals(PluralRules.getPluralIndex("cs", 4), 1)
|
|
assertEquals(PluralRules.getPluralIndex("cs", 5), 2)
|
|
assertEquals(PluralRules.getPluralIndex("cs", 100), 2)
|
|
}
|
|
|
|
test("Japanese: no plural (always 0)") {
|
|
assertEquals(PluralRules.getPluralIndex("ja", 0), 0)
|
|
assertEquals(PluralRules.getPluralIndex("ja", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("ja", 100), 0)
|
|
}
|
|
|
|
test("Chinese: no plural (always 0)") {
|
|
assertEquals(PluralRules.getPluralIndex("zh", 0), 0)
|
|
assertEquals(PluralRules.getPluralIndex("zh", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("zh", 42), 0)
|
|
}
|
|
|
|
test("Arabic: 6 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("ar", 0), 0) // zero
|
|
assertEquals(PluralRules.getPluralIndex("ar", 1), 1) // one
|
|
assertEquals(PluralRules.getPluralIndex("ar", 2), 2) // two
|
|
assertEquals(PluralRules.getPluralIndex("ar", 3), 3) // few (3-10)
|
|
assertEquals(PluralRules.getPluralIndex("ar", 10), 3) // few
|
|
assertEquals(PluralRules.getPluralIndex("ar", 11), 4) // many (11-99)
|
|
assertEquals(PluralRules.getPluralIndex("ar", 99), 4) // many
|
|
assertEquals(PluralRules.getPluralIndex("ar", 100), 5) // other (100+)
|
|
assertEquals(PluralRules.getPluralIndex("ar", 1000), 5) // other
|
|
}
|
|
|
|
test("Slovenian: 4 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("sl", 1), 0) // n%100==1
|
|
assertEquals(PluralRules.getPluralIndex("sl", 2), 1) // n%100==2
|
|
assertEquals(PluralRules.getPluralIndex("sl", 3), 2) // n%100==3 or 4
|
|
assertEquals(PluralRules.getPluralIndex("sl", 4), 2)
|
|
assertEquals(PluralRules.getPluralIndex("sl", 5), 3) // other
|
|
assertEquals(PluralRules.getPluralIndex("sl", 101), 0) // n%100==1
|
|
assertEquals(PluralRules.getPluralIndex("sl", 102), 1) // n%100==2
|
|
}
|
|
|
|
test("Welsh: 4 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("cy", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("cy", 2), 1)
|
|
assertEquals(PluralRules.getPluralIndex("cy", 8), 2)
|
|
assertEquals(PluralRules.getPluralIndex("cy", 11), 2)
|
|
assertEquals(PluralRules.getPluralIndex("cy", 5), 3)
|
|
}
|
|
|
|
test("Maltese: 4 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("mt", 1), 0) // one
|
|
assertEquals(PluralRules.getPluralIndex("mt", 0), 1) // few (0, or n%100 2-10)
|
|
assertEquals(PluralRules.getPluralIndex("mt", 2), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("mt", 10), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("mt", 11), 2) // many (n%100 11-19)
|
|
assertEquals(PluralRules.getPluralIndex("mt", 19), 2) // many
|
|
assertEquals(PluralRules.getPluralIndex("mt", 20), 3) // other
|
|
}
|
|
|
|
test("Lithuanian: 3 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("lt", 1), 0) // one (n%10==1, not 11)
|
|
assertEquals(PluralRules.getPluralIndex("lt", 2), 1) // few (n%10>=2, n%100<10 or >=20)
|
|
assertEquals(PluralRules.getPluralIndex("lt", 10), 2) // other
|
|
assertEquals(PluralRules.getPluralIndex("lt", 11), 2) // exception
|
|
assertEquals(PluralRules.getPluralIndex("lt", 21), 0) // one
|
|
assertEquals(PluralRules.getPluralIndex("lt", 12), 2) // exception (n%100 in 10-19)
|
|
}
|
|
|
|
test("Latvian: 3 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("lv", 0), 0) // zero
|
|
assertEquals(PluralRules.getPluralIndex("lv", 1), 1) // one (n%10==1, not 11)
|
|
assertEquals(PluralRules.getPluralIndex("lv", 2), 2) // other
|
|
assertEquals(PluralRules.getPluralIndex("lv", 11), 2) // other (exception)
|
|
assertEquals(PluralRules.getPluralIndex("lv", 21), 1) // one
|
|
}
|
|
|
|
test("Romanian: 3 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("ro", 1), 0) // one
|
|
assertEquals(PluralRules.getPluralIndex("ro", 0), 1) // few (0 or n%100 1-19)
|
|
assertEquals(PluralRules.getPluralIndex("ro", 19), 1) // few
|
|
assertEquals(PluralRules.getPluralIndex("ro", 20), 2) // other
|
|
assertEquals(PluralRules.getPluralIndex("ro", 100), 2) // other
|
|
}
|
|
|
|
test("Macedonian: last digit rule") {
|
|
assertEquals(PluralRules.getPluralIndex("mk", 1), 0) // n%10==1
|
|
assertEquals(PluralRules.getPluralIndex("mk", 2), 1)
|
|
assertEquals(PluralRules.getPluralIndex("mk", 11), 0) // n%10==1
|
|
assertEquals(PluralRules.getPluralIndex("mk", 21), 0) // n%10==1
|
|
}
|
|
|
|
test("Irish: 3 forms") {
|
|
assertEquals(PluralRules.getPluralIndex("ga", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("ga", 2), 1)
|
|
assertEquals(PluralRules.getPluralIndex("ga", 3), 2)
|
|
}
|
|
|
|
test("locale with region uses language portion") {
|
|
// pt_BR should use "pt" rules (2 forms)
|
|
assertEquals(PluralRules.getPluralIndex("pt_BR", 1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("pt_BR", 2), 1)
|
|
}
|
|
|
|
test("unknown locale returns 0") {
|
|
assertEquals(PluralRules.getPluralIndex("xx", 5), 0)
|
|
}
|
|
|
|
test("negative numbers use absolute value") {
|
|
assertEquals(PluralRules.getPluralIndex("en", -1), 0)
|
|
assertEquals(PluralRules.getPluralIndex("en", -5), 1)
|
|
}
|
|
|
|
// --- choose() tests ---
|
|
|
|
test("choose with simple English plural") {
|
|
val en = LocaleTag("en")
|
|
assertEquals(PluralRules.choose("item|items", 1, en), "item")
|
|
assertEquals(PluralRules.choose("item|items", 0, en), "items")
|
|
assertEquals(PluralRules.choose("item|items", 5, en), "items")
|
|
}
|
|
|
|
test("choose with explicit conditions") {
|
|
val en = LocaleTag("en")
|
|
assertEquals(PluralRules.choose("{0} No items|{1} One item|[2,*] Many items", 0, en), "No items")
|
|
assertEquals(PluralRules.choose("{0} No items|{1} One item|[2,*] Many items", 1, en), "One item")
|
|
assertEquals(PluralRules.choose("{0} No items|{1} One item|[2,*] Many items", 5, en), "Many items")
|
|
assertEquals(PluralRules.choose("{0} No items|{1} One item|[2,*] Many items", 100, en), "Many items")
|
|
}
|
|
|
|
test("choose with explicit range") {
|
|
val en = LocaleTag("en")
|
|
assertEquals(PluralRules.choose("[1,3] Few|[4,*] Many", 2, en), "Few")
|
|
assertEquals(PluralRules.choose("[1,3] Few|[4,*] Many", 4, en), "Many")
|
|
}
|
|
|
|
test("choose with Polish 3 forms") {
|
|
val pl = LocaleTag("pl")
|
|
val line = "wpis|wpisy|wpisów"
|
|
assertEquals(PluralRules.choose(line, 1, pl), "wpis")
|
|
assertEquals(PluralRules.choose(line, 2, pl), "wpisy")
|
|
assertEquals(PluralRules.choose(line, 5, pl), "wpisów")
|
|
assertEquals(PluralRules.choose(line, 22, pl), "wpisy")
|
|
assertEquals(PluralRules.choose(line, 12, pl), "wpisów")
|
|
}
|
|
|
|
test("choose with Arabic 6 forms") {
|
|
val ar = LocaleTag("ar")
|
|
val line = "zero|one|two|few|many|other"
|
|
assertEquals(PluralRules.choose(line, 0, ar), "zero")
|
|
assertEquals(PluralRules.choose(line, 1, ar), "one")
|
|
assertEquals(PluralRules.choose(line, 2, ar), "two")
|
|
assertEquals(PluralRules.choose(line, 5, ar), "few")
|
|
assertEquals(PluralRules.choose(line, 50, ar), "many")
|
|
assertEquals(PluralRules.choose(line, 100, ar), "other")
|
|
}
|
|
|
|
test("choose falls back to last segment if index out of bounds") {
|
|
val en = LocaleTag("en")
|
|
// Only 1 segment but English wants index 1 for n!=1
|
|
assertEquals(PluralRules.choose("only one form", 5, en), "only one form")
|
|
}
|