Repository: serpro69/kotlin-faker (kotlin)

Issue:
non-matching localized provider keys should be present in the localized dictionary
```
            it("non-matching localized keys should be present in the localized dictionary") {
                val enMsService = fakerService("en-MS", YamlCategory.NAME)
                enMsService.dictionary[YamlCategory.NAME]?.keys shouldContainExactlyInAnyOrder listOf(
                    // localized w/ unique keys
                    "prefix",
                    "malay_male_first_name", // unique key
                    "malay_female_first_name", // unique key
                    "chinese_male_first_name", // unique key
                    "chinese_male_last_name", // unique key
                    "chinese_female_first_name", // unique key
                    "male_first_name",
                    "female_first_name",
                    "first_name",
                    "name",
                    // non-localized
                    "neutral_first_name",
                    "last_name",
                    "suffix",
                    "name_with_middle",
                    "male_last_name",
                    "female_last_name"
                )
            }

```

Pull request description:

Patch:
diff --git a/core/src/integration/kotlin/io/github/serpro69/kfaker/provider/NameIT.kt b/core/src/integration/kotlin/io/github/serpro69/kfaker/provider/NameIT.kt
index 4abef11d03..3ef445e67c 100644
--- a/core/src/integration/kotlin/io/github/serpro69/kfaker/provider/NameIT.kt
+++ b/core/src/integration/kotlin/io/github/serpro69/kfaker/provider/NameIT.kt
@@ -19,12 +19,12 @@ class NameIT : DescribeSpec({
             }
 
             it("generates maleLastName") {
-                val maleLastNames = List(42) { name.maleLastName() }
+                val maleLastNames = List(42) { name.lastName() }
                 maleLastNames shouldNotContain ""
             }
 
             it("generates femaleLastName") {
-                val femaleLastNames = List(42) { name.femaleLastName() }
+                val femaleLastNames = List(42) { name.lastName() }
                 femaleLastNames shouldNotContain ""
             }
         }
diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt
index 87320e9644..dfb7ad30aa 100644
--- a/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/FakerService.kt
@@ -99,8 +99,8 @@ internal class FakerService {
         default: HashMap<String, Map<String, *>>,
         localized: Map<String, Map<out String, *>>
     ): Map<String, Map<String, *>> {
-        localized.forEach { (k, localizedMap) ->
-            default[k]?.let { enMap ->
+        localized.forEach { (key, localizedMap) ->
+            default[key]?.let { enMap ->
                 /*
                  * This is a provider level access for default providers (enMap) and localized providers (localizedMap),
                  * WHERE mapKey IS provider_name: [address, name, games, etc]
@@ -112,18 +112,19 @@ internal class FakerService {
                  * enMap["games"] == { {...}, {...}, pokemon={names=[...],locations=[...],moves=[...]} }
                  * localizedMap["games"] == { pokemon={names=[...]} }
                  */
-                default[k] = enMap.mapValuesTo(linkedMapOf()) { (k, v) ->
+                default[key] = localizedMap.mapValuesTo(linkedMapOf()) { (k, v) ->
+                    println("{$k=$v}")
                     /*
                      * This is provider_functions level access for default providers (enMap).
                      * The goal here is to find-and-replace any matching functions (v) for each provider (k).
                      * But since some functions may contain secondary_key the following is needed.
                      */
-                    if (v is Map<*, *> && localizedMap.containsKey(k)) {
+                    if (v is Map<*, *> && enMap.containsKey(k)) {
                         // check if function has a secondary_key that is used to resolve the values
                         // if true we assume that u[k] should also be a Map because the structure of dict files should match
                         // v IS en.faker.games.<secondary_key> (i.e pokemon)
-                        v.plus(localizedMap[k] as Map<*, *>)
-                    } else if (localizedMap.containsKey(k)) {
+                        (enMap[k] as Map<*, *>).plus(v)
+                    } else if (enMap.containsKey(k)) {
                         // check if the primary_key (function_name) matches with localized provider
                         // if v is not a map, but localized key matches, then use the values for that key
                         localizedMap[k]
diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Address.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Address.kt
index a3a09da234..5f45f3ac41 100644
--- a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Address.kt
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Address.kt
@@ -18,13 +18,18 @@ class Address internal constructor(fakerService: FakerService) : YamlFakeDataPro
         fakerService.load(yamlCategory)
     }
 
+    fun cityPrefix() = resolve("city_prefix")
+    fun citySuffix() = resolve("city_suffix")
     fun country() = resolve("country")
     fun countryByCode(countryCode: String) = resolve("country_by_code", countryCode)
     fun countryByName(countryName: String) = resolve("country_by_name", countryName)
     fun countryCode() = resolve("country_code")
     fun countryCodeLong() = resolve("country_code_long")
     fun buildingNumber() = with(fakerService) { resolve("building_number").numerify() }
+    fun communityPrefix() = resolve("community_prefix")
+    fun communitySuffix() = resolve("community_suffix")
     fun community() = resolve("community")
+    fun streetSuffix() = resolve("street_suffix")
     fun secondaryAddress() = with(fakerService) { resolve("secondary_address").numerify() }
     fun postcode() = with(fakerService) {
         when (faker.config.locale) {
@@ -45,15 +50,18 @@ class Address internal constructor(fakerService: FakerService) : YamlFakeDataPro
     fun mailbox() = with(fakerService) { resolve("mail_box").numerify() }
     fun defaultCountry() = resolve("default_country")
 
-    // TODO make internal
     @Deprecated(
-        message = "This function will become 'private' in 1.9.0",
+        message = "This function is deprecated and will be removed in future releases",
         replaceWith = ReplaceWith("city()"),
         level = DeprecationLevel.WARNING
     )
-    fun cityName() = resolve("city_name")
+    fun cityName() = city()
 
-    // fix #87
-    internal fun cityRoot() = resolve("city_root")
+    @Deprecated(
+        message = "This function is deprecated and will be removed in future releases",
+        replaceWith = ReplaceWith("cityPrefix()"),
+        level = DeprecationLevel.WARNING
+    )
+    internal fun cityRoot() = cityPrefix()
 }
 
diff --git a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Name.kt b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Name.kt
index be7814217a..0690e3d482 100644
--- a/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Name.kt
+++ b/core/src/main/kotlin/io/github/serpro69/kfaker/provider/Name.kt
@@ -28,6 +28,14 @@ class Name internal constructor(fakerService: FakerService) : YamlFakeDataProvid
     // NB! These are not part of original name.yml
     // but are here to support some discrepancies with some localized dicts ('bg', 'ru', 'uk')
     // See also https://github.com/serpro69/kotlin-faker/issues/89
-    fun maleLastName() = resolve("male_last_name")
-    fun femaleLastName() = resolve("female_last_name")
+    @Deprecated(
+        message = "This function is deprecated and will be removed in future releases",
+        ReplaceWith("lastName()")
+    )
+    fun maleLastName() = lastName()
+    @Deprecated(
+        message = "This function is deprecated and will be removed in future releases",
+        ReplaceWith("lastName()")
+    )
+    fun femaleLastName() = lastName()
 }
diff --git a/core/src/main/resources/locales/en/address.yml b/core/src/main/resources/locales/en/address.yml
index 2834ad9fc0..78590e516c 100644
--- a/core/src/main/resources/locales/en/address.yml
+++ b/core/src/main/resources/locales/en/address.yml
@@ -565,19 +565,11 @@ en:
       state: [Alabama, Alaska, Arizona, Arkansas, California, Colorado, Connecticut, Delaware, Florida, Georgia, Hawaii, Idaho, Illinois, Indiana, Iowa, Kansas, Kentucky, Louisiana, Maine, Maryland, Massachusetts, Michigan, Minnesota, Mississippi, Missouri, Montana, Nebraska, Nevada, New Hampshire, New Jersey, New Mexico, New York, North Carolina, North Dakota, Ohio, Oklahoma, Oregon, Pennsylvania, Rhode Island, South Carolina, South Dakota, Tennessee, Texas, Utah, Vermont, Virginia, Washington, West Virginia, Wisconsin, Wyoming]
       state_abbr: [AL, AK, AZ, AR, CA, CO, CT, DE, FL, GA, HI, ID, IL, IN, IA, KS, KY, LA, ME, MD, MA, MI, MN, MS, MO, MT, NE, NV, NH, NJ, NM, NY, NC, ND, OH, OK, OR, PA, RI, SC, SD, TN, TX, UT, VT, VA, WA, WV, WI, WY]
       time_zone: [Pacific/Midway, Pacific/Pago_Pago, Pacific/Honolulu, America/Juneau, America/Los_Angeles, America/Tijuana, America/Denver, America/Phoenix, America/Chihuahua, America/Mazatlan, America/Chicago, America/Regina, America/Mexico_City, America/Mexico_City, America/Monterrey, America/Guatemala, America/New_York, America/Indiana/Indianapolis, America/Bogota, America/Lima, America/Lima, America/Halifax, America/Caracas, America/La_Paz, America/Santiago, America/St_Johns, America/Sao_Paulo, America/Argentina/Buenos_Aires, America/Guyana, America/Godthab, Atlantic/South_Georgia, Atlantic/Azores, Atlantic/Cape_Verde, Europe/Dublin, Europe/London, Europe/Lisbon, Europe/London, Africa/Casablanca, Africa/Monrovia, Etc/UTC, Europe/Belgrade, Europe/Bratislava, Europe/Budapest, Europe/Ljubljana, Europe/Prague, Europe/Sarajevo, Europe/Skopje, Europe/Warsaw, Europe/Zagreb, Europe/Brussels, Europe/Copenhagen, Europe/Madrid, Europe/Paris, Europe/Amsterdam, Europe/Berlin, Europe/Berlin, Europe/Rome, Europe/Stockholm, Europe/Vienna, Africa/Algiers, Europe/Bucharest, Africa/Cairo, Europe/Helsinki, Europe/Kiev, Europe/Riga, Europe/Sofia, Europe/Tallinn, Europe/Vilnius, Europe/Athens, Europe/Istanbul, Europe/Minsk, Asia/Jerusalem, Africa/Harare, Africa/Johannesburg, Europe/Moscow, Europe/Moscow, Europe/Moscow, Asia/Kuwait, Asia/Riyadh, Africa/Nairobi, Asia/Baghdad, Asia/Tehran, Asia/Muscat, Asia/Muscat, Asia/Baku, Asia/Tbilisi, Asia/Yerevan, Asia/Kabul, Asia/Yekaterinburg, Asia/Karachi, Asia/Karachi, Asia/Tashkent, Asia/Kolkata, Asia/Kolkata, Asia/Kolkata, Asia/Kolkata, Asia/Kathmandu, Asia/Dhaka, Asia/Dhaka, Asia/Colombo, Asia/Almaty, Asia/Novosibirsk, Asia/Rangoon, Asia/Bangkok, Asia/Bangkok, Asia/Jakarta, Asia/Krasnoyarsk, Asia/Shanghai, Asia/Chongqing, Asia/Hong_Kong, Asia/Urumqi, Asia/Kuala_Lumpur, Asia/Singapore, Asia/Taipei, Australia/Perth, Asia/Irkutsk, Asia/Ulaanbaatar, Asia/Seoul, Asia/Tokyo, Asia/Tokyo, Asia/Tokyo, Asia/Yakutsk, Australia/Darwin, Australia/Adelaide, Australia/Melbourne, Australia/Melbourne, Australia/Sydney, Australia/Brisbane, Australia/Hobart, Asia/Vladivostok, Pacific/Guam, Pacific/Port_Moresby, Asia/Magadan, Asia/Magadan, Pacific/Noumea, Pacific/Fiji, Asia/Kamchatka, Pacific/Majuro, Pacific/Auckland, Pacific/Auckland, Pacific/Tongatapu, Pacific/Fakaofo, Pacific/Apia]
-      # NB! This is not part of original address.yml
-      # but are here to support some discrepancies with some localized dicts ('nb-NO', 'vi')
-      # See also https://github.com/serpro69/kotlin-faker/issues/87
-      city_root: "#{city_prefix}"
       city:
         - "#{city_prefix} #{Name.first_name}#{city_suffix}"
         - "#{city_prefix} #{Name.first_name}"
         - "#{Name.first_name}#{city_suffix}"
         - "#{Name.last_name}#{city_suffix}"
-      # NB! This is not part of original address.yml
-      # but are here to support some discrepancies with some localized dicts
-      # See also https://github.com/serpro69/kotlin-faker/issues/45
-      city_name: "#{city}"
       city_with_state:
         - "#{city}, #{state}"
       street_name:
diff --git a/core/src/main/resources/locales/en/name.yml b/core/src/main/resources/locales/en/name.yml
index 827c275e4c..8cc635bc1d 100644
--- a/core/src/main/resources/locales/en/name.yml
+++ b/core/src/main/resources/locales/en/name.yml
@@ -25,9 +25,4 @@ en:
         - "#{first_name} #{last_name} #{last_name}"
         - "#{first_name} #{last_name} #{last_name}"
         - "#{first_name} #{last_name} #{last_name}"
-      # NB! These are not part of original name.yml
-      # but are here to support some discrepancies with some localized dicts ('bg', 'ru', 'uk')
-      # See also https://github.com/serpro69/kotlin-faker/issues/89
-      male_last_name: "#{last_name}"
-      female_last_name: "#{last_name}"
 # END name_provider_dict


Test patch:
diff --git a/cli-bot/src/test/kotlin/io/github/serpro69/kfaker/app/cli/IntrospectorTest.kt b/cli-bot/src/test/kotlin/io/github/serpro69/kfaker/app/cli/IntrospectorTest.kt
index 28414d1817..4474dbcfb0 100644
--- a/cli-bot/src/test/kotlin/io/github/serpro69/kfaker/app/cli/IntrospectorTest.kt
+++ b/cli-bot/src/test/kotlin/io/github/serpro69/kfaker/app/cli/IntrospectorTest.kt
@@ -439,8 +439,12 @@ class IntrospectorTest : DescribeSpec() {
                     val expectedFunctions = listOf(
                         "buildingNumber",
                         "city",
+                        "cityPrefix",
+                        "citySuffix",
                         "cityWithState",
                         "community",
+                        "communityPrefix",
+                        "communitySuffix",
                         "country",
                         "countryByCode",
                         "countryByName",
@@ -456,6 +460,7 @@ class IntrospectorTest : DescribeSpec() {
                         "stateAbbr",
                         "streetAddress",
                         "streetName",
+                        "streetSuffix",
                         "timeZone"
                     )
 
diff --git a/core/src/test/kotlin/io/github/serpro69/kfaker/FakerServiceTest.kt b/core/src/test/kotlin/io/github/serpro69/kfaker/FakerServiceTest.kt
index b555c579bf..3e96cac1e4 100644
--- a/core/src/test/kotlin/io/github/serpro69/kfaker/FakerServiceTest.kt
+++ b/core/src/test/kotlin/io/github/serpro69/kfaker/FakerServiceTest.kt
@@ -11,6 +11,7 @@ import io.kotest.matchers.collections.shouldBeIn
 import io.kotest.matchers.collections.shouldContain
 import io.kotest.matchers.collections.shouldContainAll
 import io.kotest.matchers.collections.shouldContainExactly
+import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder
 import io.kotest.matchers.collections.shouldHaveAtLeastSize
 import io.kotest.matchers.collections.shouldHaveAtMostSize
 import io.kotest.matchers.collections.shouldHaveSize
@@ -59,11 +60,33 @@ internal class FakerServiceTest : DescribeSpec({
                 val cats = listOf("elder_scrolls", "fallout", "half_life", "super_mario", "witcher", "zelda").map {
                     Category.ofName(it)
                 }.toTypedArray()
-                    val esGames = fakerService("es", YamlCategory.GAMES, *cats).dictionary.getEntryByCategory("games")
+                val esGames = fakerService("es", YamlCategory.GAMES, *cats).dictionary.getEntryByCategory("games")
                 val defaultGames = fakerService(YamlCategory.GAMES, *cats).dictionary.getEntryByCategory("games")
                 esGames shouldBe defaultGames
             }
 
+            it("non-matching localized keys should be present in the localized dictionary") {
+                val enMsService = fakerService("en-MS", YamlCategory.NAME)
+                enMsService.dictionary[YamlCategory.NAME]?.keys shouldContainExactlyInAnyOrder listOf(
+                    // localized w/ unique keys
+                    "prefix",
+                    "malay_male_first_name", // unique key
+                    "malay_female_first_name", // unique key
+                    "chinese_male_first_name", // unique key
+                    "chinese_male_last_name", // unique key
+                    "chinese_female_first_name", // unique key
+                    "male_first_name",
+                    "female_first_name",
+                    "first_name",
+                    "name",
+                    // non-localized
+                    "neutral_first_name",
+                    "last_name",
+                    "suffix",
+                    "name_with_middle",
+                )
+            }
+
             it("partially localized functions with secondary_key should contain non-localized default values ") {
                 val deCommerce = fakerService("de", YamlCategory.COMMERCE).dictionary.getEntryByCategory("commerce")
                 val defaultCommerce = fakerService(YamlCategory.COMMERCE).dictionary.getEntryByCategory("commerce")
diff --git a/core/src/test/kotlin/io/github/serpro69/kfaker/TestConstants.kt b/core/src/test/kotlin/io/github/serpro69/kfaker/TestConstants.kt
index 20848bf037..aa26148114 100644
--- a/core/src/test/kotlin/io/github/serpro69/kfaker/TestConstants.kt
+++ b/core/src/test/kotlin/io/github/serpro69/kfaker/TestConstants.kt
@@ -22,9 +22,7 @@ val addressCategoryKeys = listOf(
     "state",
     "state_abbr",
     "time_zone",
-    "city_root",
     "city",
-    "city_name",
     "city_with_state",
     "street_name",
     "street_address",