Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
c7750fe
Add design spec for KCountries full sweep (fixes, model extension, i1…
Merkost Mar 23, 2026
5e2ad1d
Add implementation plan for KCountries full sweep (13 tasks across 4 …
Merkost Mar 23, 2026
bec4775
fix: correct CI workflows — replace :deci: references, standardize ch…
Merkost Mar 23, 2026
76ac3b0
fix: generate Countries.VERSION from version catalog instead of hardc…
Merkost Mar 23, 2026
4a6b9d9
fix: validate FlagEmoji using codepoints instead of string length
Merkost Mar 24, 2026
e383b43
fix: data inaccuracies (Turkey/Belgium/Peru), remove orphaned XK tran…
Merkost Mar 24, 2026
9a93405
feat: add Continent, Region, CallingCode, CurrencyCode, TimezoneId types
Merkost Mar 24, 2026
15530ef
feat: extend Country with continent, region, callingCode, currency, t…
Merkost Mar 24, 2026
49320f4
feat: add repository query methods and DSL predicates for continent, …
Merkost Mar 24, 2026
694e3b2
feat: add String extension properties for callingCode, currencyCode, …
Merkost Mar 24, 2026
ad1e685
feat: add JA, PT, HI, KO, IT, TR, ID translations, complete ZH gaps
Merkost Mar 24, 2026
9ce43c0
feat: normalize locale strings in getLocalizedName and getTranslation…
Merkost Mar 24, 2026
3e508e2
feat: Iterable query result, @DslMarker, not{} combinator, fix empty …
Merkost Mar 24, 2026
e953cc9
feat: update sample app with new country fields and 14 language support
Merkost Mar 24, 2026
a17eb0c
docs: update READMEs for new features, bump dependency versions
Merkost Mar 24, 2026
db716fa
chore: upgrade yarn lock file for updated dependencies
Merkost Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: macOS-latest
steps:
- name: Check out code
uses: actions/checkout@v5
uses: actions/checkout@v6

- name: Set up JDK 21
uses: actions/setup-java@v5
Expand All @@ -26,25 +26,27 @@ jobs:
uses: gradle/actions/setup-gradle@v5

- name: Run JVM tests
run: ./gradlew :deci:jvmTest
run: ./gradlew :countries-core:jvmTest :countries-i18n:jvmTest

- name: Run iOS tests
run: ./gradlew :deci:iosSimulatorArm64Test
run: ./gradlew :countries-core:iosSimulatorArm64Test :countries-i18n:iosSimulatorArm64Test

- name: Run JS tests
run: ./gradlew :deci:jsTest
run: ./gradlew :countries-core:jsTest :countries-i18n:jsTest

- name: Run WasmJs tests
run: ./gradlew :deci:wasmJsTest
run: ./gradlew :countries-core:wasmJsTest :countries-i18n:wasmJsTest

- name: Upload test reports
if: always()
uses: actions/upload-artifact@v5
with:
name: test-reports
path: |
deci/build/reports/tests/
deci/build/test-results/
countries-core/build/reports/tests/
countries-core/build/test-results/
countries-i18n/build/reports/tests/
countries-i18n/build/test-results/
retention-days: 30

build:
Expand All @@ -65,4 +67,4 @@ jobs:
uses: gradle/actions/setup-gradle@v5

- name: Build library
run: ./gradlew build --no-configuration-cache
run: ./gradlew :countries-core:build :countries-i18n:build --no-configuration-cache
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
runs-on: macOS-latest
steps:
- name: Check out code
uses: actions/checkout@v5
uses: actions/checkout@v6

- name: Set up JDK 21
uses: actions/setup-java@v5
Expand Down
103 changes: 81 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,23 @@ A lightweight, high-performance Kotlin Multiplatform library providing ISO 3166-
## Features

- **249 Countries**: Complete ISO 3166-1 dataset with codes, names, and flags
- **Rich Metadata**: Continent, region, calling code, currency, and timezone for every country
- **Native Names**: Authentic country names in local scripts (日本, Россия, مصر, etc.)
- **7 Languages**: Optional i18n module with Spanish, French, German, Arabic, Chinese, Russian
- **14 Languages**: Optional i18n module with ES, FR, DE, AR, ZH, RU, JA, PT, HI, KO, IT, TR, ID
- **User-Friendly Names**: Display names without formal ISO formatting
- **Type-Safe API**: Inline value classes for codes (zero runtime overhead)
- **Multiple Access Patterns**: Repository, DSL queries, and extension functions
- **Platform Support**: Android, iOS, JVM, JS, WASM
- **Minimal Size**: ~50KB core + ~50KB i18n (optional)
- **Minimal Size**: ~50KB core + ~100KB i18n (optional)
- **Fast Performance**: O(1) hash-indexed lookups

## Installation

```kotlin
dependencies {
implementation("org.kimplify:countries-core:0.1.0")
implementation("org.kimplify:countries-core:0.1.1")

// Optional: Multilingual support
// Optional: Multilingual support (14 languages)
implementation("org.kimplify:countries-i18n:0.1.1")
}
```
Expand Down Expand Up @@ -64,10 +65,24 @@ val northAmericans = Countries.repository.query {
}
}.toList()

// Count results
val count = Countries.repository.query {
nameStartsWith("United")
}.count()
// Filter by continent, region, calling code, currency, timezone
val europeanCountries = Countries.repository.query {
continent(Continent.EUROPE)
}.toList()

val westernEurope = Countries.repository.query {
region(Region.WESTERN_EUROPE)
}.toList()

// NOT logic
val nonEuropean = Countries.repository.query {
not { continent(Continent.EUROPE) }
}.toList()

// Iterate directly (CountriesQueryResult implements Iterable)
for (country in Countries.repository.query { currency("EUR") }) {
println("${country.getDisplayName()} uses EUR")
}
```

### Extension Functions
Expand All @@ -86,6 +101,13 @@ val nativeName = "JP".nativeCountryName // "日本"
// Convert between formats
val alpha3 = "US".toAlpha3 // "USA"
val alpha2 = "USA".toAlpha2 // "US"

// Access metadata
val code = "US".callingCode // "+1"
val curr = "JP".currencyCode // "JPY"
val cont = "BR".continent // Continent.SOUTH_AMERICA
val reg = "BR".region // Region.SOUTH_AMERICA
val tz = "DE".timezone // "Europe/Berlin"
```

## Data Model
Expand All @@ -99,8 +121,13 @@ data class Country(
val numeric: NumericCode, // ISO 3166-1 numeric (e.g., "840")
val name: CountryName, // Formal ISO name (e.g., "United States of America (the)")
val flag: FlagEmoji, // Flag emoji (e.g., "🇺🇸")
val displayName: String? = null, // User-friendly name (e.g., "United States")
val native: String? = null // Native language name (e.g., "日本")
val displayName: String?, // User-friendly name (e.g., "United States")
val native: String?, // Native language name (e.g., "日本")
val continent: Continent, // Geographic continent (e.g., NORTH_AMERICA)
val region: Region, // UN geoscheme region (e.g., NORTHERN_AMERICA)
val callingCode: CallingCode, // E.164 calling code (e.g., "+1")
val currency: CurrencyCode, // ISO 4217 currency (e.g., "USD")
val timezone: TimezoneId // IANA timezone (e.g., "America/New_York")
)
```

Expand All @@ -118,7 +145,15 @@ All codes are wrapped in inline value classes for type safety with zero runtime
- `Alpha3Code`: 3-letter country code
- `NumericCode`: 3-digit country code
- `CountryName`: Country name string
- `FlagEmoji`: Flag emoji string
- `FlagEmoji`: Flag emoji (validated via Unicode codepoints)
- `CallingCode`: E.164 calling code (e.g., "+1", "+44")
- `CurrencyCode`: ISO 4217 currency code (e.g., "USD", "EUR")
- `TimezoneId`: IANA timezone identifier (e.g., "America/New_York")

### Enums

- `Continent`: 7 continents (AFRICA, ANTARCTICA, ASIA, EUROPE, NORTH_AMERICA, OCEANIA, SOUTH_AMERICA)
- `Region`: 23 UN geoscheme regions (CARIBBEAN, EASTERN_EUROPE, SOUTHEASTERN_ASIA, etc.)

## API Reference

Expand All @@ -127,7 +162,7 @@ All codes are wrapped in inline value classes for type safety with zero runtime
```kotlin
object Countries {
val repository: CountriesRepository // Main data access point
const val VERSION: String // Library version
val VERSION: String // Library version (auto-generated from catalog)
const val TOTAL_COUNTRIES: Int // Total countries (249)
}
```
Expand All @@ -141,24 +176,35 @@ interface CountriesRepository {
fun findByAlpha3(code: Alpha3Code): Country?
fun findByNumeric(code: NumericCode): Country?
fun searchByName(query: String): List<Country>
fun getByContinent(continent: Continent): List<Country>
fun getByRegion(region: Region): List<Country>
fun getByCallingCode(callingCode: CallingCode): List<Country>
fun getByCurrency(currencyCode: CurrencyCode): List<Country>
fun query(block: CountriesQuery.() -> Unit): CountriesQueryResult
}
```

### DSL Query Builder

```kotlin
@CountriesDsl
class CountriesQuery {
fun alpha2(code: String)
fun alpha3(code: String)
fun numeric(code: String)
fun nameContains(text: String)
fun nameEquals(name: String)
fun nameStartsWith(prefix: String)
fun continent(continent: Continent)
fun region(region: Region)
fun callingCode(code: String)
fun currency(code: String)
fun timezone(id: String)
fun or(block: CountriesQuery.() -> Unit)
fun not(block: CountriesQuery.() -> Unit)
}

class CountriesQueryResult {
class CountriesQueryResult : Iterable<Country> {
fun firstOrNull(): Country?
fun first(): Country
fun toList(): List<Country>
Expand All @@ -170,8 +216,8 @@ class CountriesQueryResult {

## Performance

- **Core library**: ~50KB (249 countries with native names)
- **I18n module**: ~50KB (6 languages × 250 translations)
- **Core library**: ~80KB (249 countries with native names + metadata)
- **I18n module**: ~100KB (13 languages × 249 translations)
- **Initialization**: <10ms (lazy)
- **Lookups**: O(1) hash-indexed, <1ms
- **Translations**: O(1) map lookup
Expand All @@ -198,13 +244,13 @@ No platform-specific code needed!
## Versioning

Semantic versioning: `MAJOR.MINOR.PATCH`
- **Current**: 1.0.0
- **Data updates**: MINOR bump (e.g., 1.1.0)
- **API changes**: MAJOR bump (e.g., 2.0.0)
- **Current**: 0.1.1
- **Data updates**: MINOR bump
- **API changes**: MAJOR bump

## Internationalization (countries-i18n)

Optional module providing country name translations in 6 languages.
Optional module providing country name translations in 13 languages (+ English fallback).

```kotlin
val country = Countries.repository.findByAlpha2(Alpha2Code("JP"))!!
Expand All @@ -216,12 +262,24 @@ country.getLocalizedName(Locale.DE) // "Japan"
country.getLocalizedName(Locale.AR) // "اليابان"
country.getLocalizedName(Locale.ZH) // "日本"
country.getLocalizedName(Locale.RU) // "Япония"
country.getLocalizedName(Locale.JA) // "日本"
country.getLocalizedName(Locale.PT) // "Japão"
country.getLocalizedName(Locale.KO) // "일본"
country.getLocalizedName(Locale.IT) // "Giappone"
country.getLocalizedName(Locale.TR) // "Japonya"
country.getLocalizedName(Locale.HI) // "जापान"
country.getLocalizedName(Locale.ID) // "Jepang"

// Locale strings are normalized automatically
country.getLocalizedName("es-MX") // "Japón" (extracts "es")
country.getLocalizedName("PT_BR") // "Japão" (extracts "pt")
```

**Supported Languages:**
- English (en), Spanish (es), French (fr), German (de)
- Arabic (ar) with RTL support
- Chinese (zh), Russian (ru)
- English (en), Spanish (es), French (fr), German (de), Italian (it)
- Arabic (ar) with RTL support, Turkish (tr), Indonesian (id)
- Chinese (zh), Japanese (ja), Korean (ko), Hindi (hi)
- Russian (ru), Portuguese (pt)

See [countries-i18n/README.md](countries-i18n/README.md) for full documentation.

Expand All @@ -238,4 +296,5 @@ See [countries-i18n/README.md](countries-i18n/README.md) for full documentation.
- **Standard**: ISO 3166-1:2020
- **Total Entries**: 249 territories
- **Includes**: 193 UN member states + 56 dependencies/special areas
- **Metadata Sources**: UN M49 (regions), ITU-T E.164 (calling codes), ISO 4217 (currencies), IANA (timezones)
- **Last Updated**: 2025-01-26
27 changes: 25 additions & 2 deletions countries-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,12 +53,35 @@ kotlin {
}
}

val generateVersionFile = tasks.register("generateVersionFile") {
val version = libs.versions.kcountries.get()
val outputDir = layout.buildDirectory.dir("generated/version/kotlin")
outputs.dir(outputDir)
doLast {
val dir = outputDir.get().asFile.resolve("org/kimplify/countries")
dir.mkdirs()
dir.resolve("BuildKConfig.kt").writeText(
"""
|package org.kimplify.countries
|
|internal object BuildKConfig {
| const val VERSION = "$version"
|}
""".trimMargin()
)
}
}

kotlin.sourceSets.commonMain {
kotlin.srcDir(generateVersionFile.map { it.outputs.files.singleFile })
}
Comment thread
Merkost marked this conversation as resolved.

android {
namespace = "org.kimplify.countriesCore"
compileSdk = 35
compileSdk = libs.versions.android.compileSdk.get().toInt()

defaultConfig {
minSdk = 21
minSdk = libs.versions.android.minSdk.get().toInt()
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ object Countries {
/**
* Library version following semantic versioning (MAJOR.MINOR.PATCH).
*/
const val VERSION = "1.0.0"
val VERSION = BuildKConfig.VERSION

/**
* Total number of countries in the ISO 3166-1 dataset.
Expand Down
Loading
Loading