Skip to content

Commit c87c1c7

Browse files
committed
Fix locale fallback
1 parent 21e4183 commit c87c1c7

File tree

4 files changed

+66
-50
lines changed

4 files changed

+66
-50
lines changed

CHANGELOG.adoc

+1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
=== Fixed
2222

2323
* https://github.com/serpro69/kotlin-faker/pull/161[#161] Fix empty lists as parameter values
24+
* https://github.com/serpro69/kotlin-faker/pull/171[#171] Fix locale fallback
2425

2526
[discrete]
2627
=== Other

core/src/integration/kotlin/io/github/serpro69/kfaker/provider/NameIT.kt

+12-8
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,30 @@ import io.kotest.matchers.collections.shouldNotContain
77
@Suppress("unused")
88
class NameIT : DescribeSpec({
99
describe("Name provider") {
10-
context("ru locale") {
11-
val faker = faker {
12-
fakerConfig { locale = "ru" }
13-
}
14-
val name = faker.name
10+
val name: (locale: String) -> Name = { faker { fakerConfig { locale = it } }.name }
1511

12+
context("ru locale") {
1613
it("generates lastName") {
17-
val lastNames = List(42) { name.lastName() }
14+
val lastNames = List(42) { name("ru").lastName() }
1815
lastNames shouldNotContain ""
1916
}
2017

2118
it("generates maleLastName") {
22-
val maleLastNames = List(42) { name.lastName() }
19+
val maleLastNames = List(42) { name("ru").lastName() }
2320
maleLastNames shouldNotContain ""
2421
}
2522

2623
it("generates femaleLastName") {
27-
val femaleLastNames = List(42) { name.lastName() }
24+
val femaleLastNames = List(42) { name("ru").lastName() }
2825
femaleLastNames shouldNotContain ""
2926
}
3027
}
28+
29+
context("de-DE locale") {
30+
it("generates firstsName") {
31+
val firstNames = List(42) { name("de-DE").firstName() }
32+
firstNames shouldNotContain ""
33+
}
34+
}
3135
}
3236
})

core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt

+14-20
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,9 @@ import io.github.serpro69.kfaker.provider.FakeDataProvider
1919
import io.github.serpro69.kfaker.provider.Name
2020
import io.github.serpro69.kfaker.provider.Tertiary
2121
import io.github.serpro69.kfaker.provider.YamlFakeDataProvider
22-
import org.w3c.dom.ranges.RangeException
2322
import java.io.InputStream
2423
import java.util.*
2524
import java.util.regex.Matcher
26-
import kotlin.collections.LinkedHashMap
2725
import kotlin.collections.component1
2826
import kotlin.collections.component2
2927
import kotlin.collections.set
@@ -81,6 +79,7 @@ internal class FakerService {
8179

8280
private fun getLocaleFileStream(locale: String): InputStream? {
8381
return javaClass.classLoader.getResourceAsStream("locales/$locale.json")
82+
?: javaClass.classLoader.getResourceAsStream("locales/${locale.substringBefore("-")}.json")
8483
}
8584

8685
/**
@@ -226,15 +225,9 @@ internal class FakerService {
226225
// these have multiple files per directory, as opposed to other localizations
227226
"fr", "ja" -> getCategoryFileStreamOrNull(locale, category, secondaryCategory)
228227
else -> if (locale != "en") /*'en' is already processed at this point*/ {
229-
getLocaleFileStream(locale)
230-
?: getLocaleFileStream(locale.substringBefore("-"))
231-
?: throw IllegalArgumentException(
232-
"Dictionary file not found for locale values: '$locale' or '${
233-
locale.substringBefore(
234-
"-"
235-
)
236-
}'"
237-
)
228+
getLocaleFileStream(locale) ?: throw IllegalArgumentException(
229+
"Dictionary file not found for locale values: '$locale' or '${locale.substringBefore("-")}'"
230+
)
238231
} else null
239232
}
240233
input?.use { instr ->
@@ -291,7 +284,9 @@ internal class FakerService {
291284
locale: String,
292285
category: YamlCategory
293286
): Map<String, Any>? {
294-
val localeData = Mapper.readValue(inputStream, Map::class.java)[locale] as Map<*, *>
287+
val localeData: Map<*, *> = with(Mapper.readValue(inputStream, Map::class.java)) {
288+
(get(locale) ?: get(locale.substringBefore("-"))) as Map<*, *>
289+
}
295290
val fakerData = localeData["faker"] as LinkedHashMap<String, Map<String, *>>
296291
return fakerData[category.lowercase()] as Map<String, Any>?
297292
}
@@ -300,22 +295,22 @@ internal class FakerService {
300295
* Returns raw value as [RawExpression] from a given [category] fetched by its [key]
301296
*/
302297
fun getRawValue(category: YamlCategory, key: String): RawExpression {
303-
val parameterValue = dictionary[category]?.get(key)
298+
val paramValue = dictionary[category]?.get(key)
304299
?: throw NoSuchElementException("Parameter '$key' not found in '$category' category")
305300

306-
return when (parameterValue) {
301+
return when (paramValue) {
307302
is List<*> -> {
308-
if (parameterValue.isEmpty()) RawExpression("") else when (val value = randomService.randomValue(parameterValue)) {
303+
if (paramValue.isEmpty()) RawExpression("") else when (val value = randomService.randomValue(paramValue)) {
309304
is List<*> -> {
310305
if (value.isEmpty()) RawExpression("") else RawExpression(randomService.randomValue(value) as String)
311306
}
312307
is String -> RawExpression(value)
313308
is Int -> RawExpression(value.toString())
314-
else -> throw UnsupportedOperationException("Unsupported type of raw value: ${parameterValue::class.simpleName}")
309+
else -> throw UnsupportedOperationException("Unsupported type of raw value: ${paramValue::class.simpleName}")
315310
}
316311
}
317-
is String -> RawExpression(parameterValue)
318-
else -> throw UnsupportedOperationException("Unsupported type of raw value: ${parameterValue::class.simpleName}")
312+
is String -> RawExpression(paramValue)
313+
else -> throw UnsupportedOperationException("Unsupported type of raw value: ${paramValue::class.simpleName}")
319314
}
320315
}
321316

@@ -470,7 +465,7 @@ internal class FakerService {
470465
* fun street_number() = with(fakerService) { resolveExpression().numerify() }
471466
* fun street_name() = with(fakerService) { resolveExpression().letterify() }
472467
* // Explicitly numerify and letterify returned value, even though we are doing that above as well
473-
* // because the functions are in the same categry
468+
* // because the functions are in the same category
474469
* fun street() = with(fakerService) { resolveExpression().numerify().letterify()
475470
* ```
476471
*/
@@ -542,7 +537,6 @@ internal class FakerService {
542537
* @param T instance of [FakeDataProvider]
543538
* @param kFunction the [KFunction] of [T]
544539
*/
545-
@Suppress("UNCHECKED_CAST")
546540
private fun <T : FakeDataProvider> T.callFunction(kFunction: KFunction<*>): String {
547541
return kFunction.call(this) as String
548542
}

core/src/test/kotlin/io/github/serpro69/kfaker/FakerServiceTest.kt

+39-22
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
1515
import io.kotest.matchers.collections.shouldHaveAtLeastSize
1616
import io.kotest.matchers.collections.shouldHaveAtMostSize
1717
import io.kotest.matchers.collections.shouldHaveSize
18+
import io.kotest.matchers.maps.shouldContainExactly
1819
import io.kotest.matchers.shouldBe
1920
import io.kotest.matchers.shouldNotBe
2021
import io.kotest.matchers.string.shouldContain
@@ -26,7 +27,7 @@ val random = Random()
2627

2728
internal class FakerServiceTest : DescribeSpec({
2829
describe("locale for the dictionary") {
29-
context("it is set to default value") {
30+
context("is set to default value") {
3031
it("it should load the category for 'en' locale") {
3132
fakerService(YamlCategory.ADDRESS)
3233
.dictionary
@@ -49,7 +50,7 @@ internal class FakerServiceTest : DescribeSpec({
4950
}
5051
}
5152

52-
context("it is set to custom value") {
53+
context("is set to custom value") {
5354
it("matching keys should be overwritten in the localized dictionary") {
5455
val esAddress = fakerService("es", YamlCategory.ADDRESS).dictionary.getEntryByCategory("address")
5556
val defaultAddress = fakerService(YamlCategory.ADDRESS).dictionary.getEntryByCategory("address")
@@ -98,13 +99,13 @@ internal class FakerServiceTest : DescribeSpec({
9899
}
99100
}
100101

101-
context("it is set with a valid String value") {
102+
context("is set with a valid String value") {
102103
it("localized category should be loaded") {
103104
fakerService("es", YamlCategory.ADDRESS).dictionary shouldNotBe emptyMap<YamlCategory, Map<*, *>>()
104105
}
105106
}
106107

107-
context("it is set with invalid String value") {
108+
context("is set with invalid String value") {
108109
it("an exception is thrown when loading the category") {
109110
val exception = shouldThrow<IllegalArgumentException> {
110111
fakerService("pe", YamlCategory.ADDRESS)
@@ -114,24 +115,17 @@ internal class FakerServiceTest : DescribeSpec({
114115
}
115116
}
116117

117-
// TODO not supported since 1.11.0
118-
// see: https://github.com/serpro69/kotlin-faker/issues/131
119-
// Note: when implemented also add tests to check that existing `lang-COUNTRY` locale is actually loaded, e.g. `fr-CA`
120-
// context("it is set as `lang-COUNTRY` but dictionary file exists only for `lang`") {
121-
// val frFRDict = FakerService(Faker(), "fr-FR").dictionary
122-
//
123-
// it("localized dictionary for `lang` should be loaded") {
124-
// frFRDict shouldNotBe null
125-
// }
126-
// }
127-
//
128-
// context("it is set as `lang_COUNTRY` String") {
129-
// val frFRDict = FakerService(Faker(), "fr_FR").dictionary
130-
//
131-
// it("it should be set as `lang-COUNTRY` String") {
132-
// frFRDict shouldNotBe null
133-
// }
134-
// }
118+
context("is set as `lang-COUNTRY` but dictionary file exists only for `lang`") {
119+
val service = FakerService(Faker(), Locale.forLanguageTag("de-DE"))
120+
121+
@Suppress("LocalVariableName")
122+
val `de-DE Dictionary` = service.load(YamlCategory.ADDRESS)
123+
124+
it("localized dictionary for `lang` should be loaded") {
125+
`de-DE Dictionary` shouldContainExactly FakerService(Faker(), Locale.forLanguageTag("de"))
126+
.load(YamlCategory.ADDRESS)
127+
}
128+
}
135129
}
136130

137131
describe("dictionary is loaded") {
@@ -549,6 +543,29 @@ internal class FakerServiceTest : DescribeSpec({
549543
jaCat["breed"] shouldNotBe defaultCat["breed"]
550544
}
551545
}
546+
547+
// context("fr locale") { // also applicable for ja and any other multi-file localized data
548+
// // TODO not supported since 1.11.0
549+
// // see: https://github.com/serpro69/kotlin-faker/issues/131
550+
// // Note: when implemented also add tests to check that existing `lang-COUNTRY` locale is actually loaded, e.g. `fr-CA`
551+
// context("it is set as `lang-COUNTRY` but dictionary file exists only for `lang`") {
552+
// val frFRDict = FakerService(Faker(), Locale.forLanguageTag("fr-FR")).dictionary
553+
//
554+
// it("localized dictionary for `lang` should be loaded") {
555+
// frFRDict shouldNotBe null
556+
// frFRDict shouldNotBe emptyMap<YamlCategory, YamlCategoryData>()
557+
// }
558+
// }
559+
//
560+
// context("it is set as `lang_COUNTRY` String") {
561+
// val frFRDict = FakerService(Faker(), Locale.forLanguageTag("fr_FR")).dictionary
562+
//
563+
// it("it should be set as `lang-COUNTRY` String") {
564+
// frFRDict shouldNotBe null
565+
// frFRDict shouldNotBe emptyMap<YamlCategory, YamlCategoryData>()
566+
// }
567+
// }
568+
// }
552569
}
553570
})
554571

0 commit comments

Comments
 (0)