diff --git a/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/DefaultLocaleImpl.kt b/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/DefaultLocaleImpl.kt new file mode 100644 index 0000000..e683bba --- /dev/null +++ b/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/DefaultLocaleImpl.kt @@ -0,0 +1,96 @@ +package de.comahe.i18n4k + +import kotlinx.collections.immutable.ImmutableMap +import kotlinx.collections.immutable.persistentMapOf + +/** Default implementation for [Locale] for non JVM targets */ +@Suppress("MemberVisibilityCanBePrivate", "unused") +class DefaultLocaleImpl( + private val language: String, + private val script: String, + private val country: String, + private val variant: String, + private val extensions: ImmutableMap, +) { + constructor( + language: String + ) : this(language, "", "") + + constructor( + language: String, + country: String, + ) : this(language, country, "") + + + constructor( + language: String, + country: String, + variant: String, + ) : this(language, "", country, variant, persistentMapOf()) + + fun getLanguage(): String = language + fun getScript(): String = script + fun getCountry(): String = country + fun getVariant(): String = variant + + + fun hasExtensions(): Boolean = extensions.isNotEmpty() + + fun stripExtensions(): DefaultLocaleImpl { + if (hasExtensions()) + return DefaultLocaleImpl(language, script, country, variant, persistentMapOf()) + return this + } + + fun getExtension(key: Char): String? = extensions[key] + + fun getExtensionKeys(): Set = extensions.keys + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is DefaultLocaleImpl) return false + + if (language != other.language) return false + if (script != other.script) return false + if (country != other.country) return false + if (variant != other.variant) return false + if (extensions != other.extensions) return false + + return true + } + + override fun hashCode(): Int { + var result = language.hashCode() + result = 31 * result + script.hashCode() + result = 31 * result + country.hashCode() + result = 31 * result + variant.hashCode() + result = 31 * result + extensions.hashCode() + return result + } + + override fun toString(): String { + return toLocaleTag(language, script, country, variant, extensions) + } +} + +/** Default implementation for [createLocale] for non JVM targets */ +fun createDefaultLocaleImpl( + language: String, + script: String?, + country: String?, + variant: String?, + extensions: Map? +): DefaultLocaleImpl { + val extensionsBuilder = persistentMapOf().builder() + extensions?.forEach { (key, value) -> + extensionsBuilder[key.lowercaseChar()] = value.lowercase() + } + + return DefaultLocaleImpl( + language.trim().lowercase(), + script?.trim()?.capitalize() ?: "", + country?.trim()?.uppercase() ?: "", + variant?.trim()?.lowercase() ?: "", + extensionsBuilder.build() + ) +} \ No newline at end of file diff --git a/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/Locale.kt b/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/Locale.kt index 4ebfa4c..d1225ba 100644 --- a/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/Locale.kt +++ b/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/Locale.kt @@ -1,19 +1,23 @@ package de.comahe.i18n4k -/** Class representing a locale */ -expect class Locale( - /** See [Locale.getLanguage] */ - language: String, - /** See [Locale.getCountry] */ - country: String, - /** See [Locale.getVariant] */ - variant: String -) { + +/** Class representing a locale */ +expect class Locale { + @Deprecated( + message = "Use `createLocale`", + replaceWith = ReplaceWith("createLocale(language)"), + level = DeprecationLevel.WARNING + ) constructor( /** See [Locale.getLanguage] */ language: String ) + @Deprecated( + message = "Use `createLocale`", + replaceWith = ReplaceWith("createLocale(language, null, country)"), + level = DeprecationLevel.WARNING + ) constructor( /** See [Locale.getLanguage] */ language: String, @@ -21,18 +25,100 @@ expect class Locale( country: String ) + @Deprecated( + message = "Use `createLocale`", + replaceWith = ReplaceWith("createLocale(language, null, country, variant)"), + level = DeprecationLevel.WARNING + ) + constructor ( + /** See [Locale.getLanguage] */ + language: String, + /** See [Locale.getCountry] */ + country: String, + /** See [Locale.getVariant] */ + variant: String + ) + - /** Language code. Should be a [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes) */ + /** + * Language code. + * + * Should be a + * [ISO 639-1 code](https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes). + * + * Will be lowercase. + */ fun getLanguage(): String - /**country/region code for this locale, which should either be the empty string, an uppercase ISO 3166 2-letter code, or a UN M.49 3-digit code. */ + /** + * Returns the script for this locale, which should either be the empty + * string or an "ISO 15924 4"-letter script code. + * + * The first letter is uppercase and the rest are lowercase, for example, + * 'Latn', 'Cyrl'. + */ + fun getScript(): String + + /** + * country/region code for this locale, which should either be the empty + * string, an uppercase ISO 3166 2-letter code, or a UN M.49 3-digit code. + */ fun getCountry(): String /** the variant code for this locale. Can be an empty string */ fun getVariant(): String + /** Returns `true` if this `Locale` has any extensions. */ + fun hasExtensions(): Boolean + + /** + * Returns a copy of this (or this itself) `Locale` with no extensions. + * + * If this `Locale` has no extensions, this `Locale` is returned. + */ + fun stripExtensions(): Locale + + /** + * Returns the extension (or private use) value associated with the + * specified key, or null if there is no extension associated with the key. + * + * To be well-formed, the key must be one of `[0-9A-Za-z]`. + * + * Keys are case-insensitive, so for example, 'z' and 'Z' represent the + * same extension. + * + * @param key the extension key + * @return The extension, or null if this locale defines no extension for + * the specified key. + * @throws IllegalArgumentException if key is not well-formed + */ + fun getExtension(key: Char): String? + + /** + * Returns the set of extension keys associated with this locale, or the + * empty set if it has no extensions. + * + * The returned set is unmodifiable. + * + * The keys will all be lower-case. + */ + fun getExtensionKeys(): Set } +/** + * Create a local with extensions. + * + * Regarding extensions see: + * * [language-tags](https://www.w3.org/International/articles/language-tags/#extension) + * * [rfc5646](https://www.rfc-editor.org/rfc/rfc5646.html#section-2.2.6) + */ +expect fun createLocale( + language: String, + script: String? = null, + country: String? = null, + variant: String? = null, + extensions: Map? = null, +): Locale /** The current locale of the system or user */ expect val systemLocale: Locale \ No newline at end of file diff --git a/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/LocaleUtils.kt b/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/LocaleUtils.kt index 481f819..0ba317a 100644 --- a/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/LocaleUtils.kt +++ b/i18n4k-core/src/commonMain/kotlin/de/comahe/i18n4k/LocaleUtils.kt @@ -10,24 +10,22 @@ import kotlinx.collections.immutable.persistentMapOf /** * Property to access [Locale.getLanguage] * - * Direct expected property not possible because `java.util.Locale` should - * be a actual typealias and getter cannot replace property currently: - * https://youtrack.jetbrains.com/issue/KT-15620 + * Direct expected property is not possible because `java.util.Locale` + * should be an actual typealias and getter cannot replace property + * currently: [KT-15620](https://youtrack.jetbrains.com/issue/KT-15620) */ val Locale.language: String get() = this.getLanguage() -/** - * Property to access [Locale.getCountry] - * - */ +/** Property to access [Locale.getScript] */ +val Locale.script: String + get() = this.getScript() + +/** Property to access [Locale.getCountry] */ val Locale.country: String get() = this.getCountry() -/** - * Property to access [Locale.getVariant] - * - */ +/** Property to access [Locale.getVariant] */ val Locale.variant: String get() = this.getVariant() @@ -60,225 +58,341 @@ val Locale.lessSpecificLocale: Locale? } -/** Transforms a languageTag like "en_US_WIN" to a Locale("en","US","WIN") */ -fun forLocaleTag(languageTag: String, separator: String = "_"): Locale { - val underscore1 = languageTag.indexOf(separator) - if (underscore1 < 0) - return Locale(languageTag) +/** + * Transforms a languageTag like "en_US_texas" to a + * Locale("en","US","texas") + * + * See [rfc5646](https://www.rfc-editor.org/rfc/rfc5646.html#section-2.1) + */ +fun forLocaleTag(languageTag: String, separator: Char = '_', separator2: Char = '-'): Locale { + require(languageTag.isNotEmpty()) { "Language tag was empty!" } + + var language = "" + var script = "" + var country = "" + var variant = "" + val extensions = mutableMapOf() + + val parts = languageTag.split(separator, separator2) - val underscore2 = languageTag.indexOf(separator, underscore1 + 1) - if (underscore2 < 0) - return Locale( - languageTag.substring(0, underscore1), - languageTag.substring(underscore1 + 1) - ) + // tool functions.... + fun String.isAlpha() = all { it.isLetter() } + fun String.isDigit() = all { it.isDigit() } + fun String.isAlphaNum() = all { it.isLetterOrDigit() } - return Locale( - languageTag.substring(0, underscore1), - languageTag.substring(underscore1 + 1, underscore2), - languageTag.substring(underscore2 + 1) + + var index = 0; + + language = parts[index++] + check(language.length in 2..8) { "Language must have between 2 and 8 chars. Actual: $language" } + check(language.isAlpha()) { "Language must only contain letters. Actual: $language" } + + // up to 3 extlang -> not supported by Java implementation + //while (parts.size > index && parts[index].length == 3) { + // language += "-" + parts[index++]; + //} + + + if (parts.size > index + && parts[index].isAlpha() + && parts[index].length == 4 + ) + script = parts[index++] + + if (parts.size > index + && ( + (parts[index].length == 2 && parts[index].isAlpha()) + || (parts[index].length == 3 && parts[index].isDigit()) + ) + ) + country = parts[index++] + + if (parts.size > index + && ((parts[index].length in 5..8 && parts[index].isAlphaNum()) + || (parts[index].length == 4 && parts[index][0].isDigit() && parts[index].isAlphaNum()) + ) ) + variant = parts[index++] + + + while (parts.size > index && parts[index].length == 1) { + val key = parts[index++] + val value = StringBuilder() + while (parts.size > index && parts[index].length in 2..8) { + if (value.isNotEmpty()) + value.append("-") + value.append(parts[index++]) + } + check(value.isNotEmpty()) { "Value for extension key '$key' is empty! language-tag: $languageTag" } + extensions[key[0]] = value.toString() + } + check(parts.size <= index) { "Unexpected part: ${parts[index]} - language-tag: $languageTag" } + + return createLocale(language, script, country, variant, extensions) +} + +fun toLocaleTag( + language: String, + script: String, + country: String, + variant: String, + extensions: Map?, + separator: String = "_" +): String { + val tag = StringBuilder() + tag.append(language) + if (script.isNotEmpty()) + tag.append(separator).append(script) + if (country.isNotEmpty()) + tag.append(separator).append(country) + if (variant.isNotEmpty()) + tag.append(separator).append(variant) + extensions?.forEach { (key, value) -> + tag.append(separator).append(key).append(separator).append(value) + } + return tag.toString() } +fun Locale.toTag(separator: String = "_"): String { + + val extensions: Map? + if (hasExtensions()) { + extensions = mutableMapOf() + for (key in getExtensionKeys()) { + val value = getExtension(key) + if (value != null) + extensions[key] = value + } + } else + extensions = null -fun Locale.toTag( separator: String = "_"): String { - if (country.isEmpty()) - return language - if (variant.isEmpty()) - return language + separator + country - return language + separator + country + separator + variant + return toLocaleTag(language, script, country, variant, extensions, separator) } -/** Returns a name for the locale that is appropriate for display to the user in the language of the locale */ + +/** + * Returns a name for the locale that is appropriate for display to the + * user in the language of the locale + */ fun Locale.getDisplayNameInLocale(): String { - val displayLanguage = localeTags.binarySearch(language).let { + val language = this.language.lowercase() + val script = this.script.lowercase() + val country = this.country.lowercase() + val variant = this.variant.lowercase() + + var display = localeTags.binarySearch(language).let { if (it < 0) - language + this.language else localeDisplayName[it] } - if (country.isEmpty()) - return displayLanguage + fun String.appendInfo(info: String): String { + val index = indexOf(")") + if (index < 0) + return "$this ($info)" + return substring(0, index) + "," + info + ")" + } - val displayCountry = localeTags.binarySearch(language + "_" + country).let { - if (it < 0) - "$displayLanguage ($country)" + + display = + if (country.isEmpty()) + display else - localeDisplayName[it] - } + localeTags.binarySearch(language + "_" + country).let { + if (it < 0) + display.appendInfo(this.country) + else + localeDisplayName[it] + } - if (variant.isEmpty()) - return displayCountry + display = + if (variant.isEmpty()) + display + else + localeTags.binarySearch(language + "_" + country + "_" + variant).let { + if (it < 0) + display.appendInfo(this.variant) + else + localeDisplayName[it] - return localeTags.binarySearch(language + "_" + country + "_" + variant).let { - if (it < 0) - "${displayCountry.substringBefore(")")},$variant)" + } + + display = + if (script.isEmpty()) + display else - localeDisplayName[it] - } + localeTags.binarySearch(language + "_" + country + "_" + variant + "#" + script).let { + if (it < 0) + display.appendInfo(this.script) + else + localeDisplayName[it] + + } + + return display } -/** Locale */ +/** Locale */ private val localeTags = arrayListOf( "ar", - "ar_AE", - "ar_BH", - "ar_DZ", - "ar_EG", - "ar_IQ", - "ar_JO", - "ar_KW", - "ar_LB", - "ar_LY", - "ar_MA", - "ar_OM", - "ar_QA", - "ar_SA", - "ar_SD", - "ar_SY", - "ar_TN", - "ar_YE", + "ar_ae", + "ar_bh", + "ar_dz", + "ar_eg", + "ar_iq", + "ar_jo", + "ar_kw", + "ar_lb", + "ar_ly", + "ar_ma", + "ar_om", + "ar_qa", + "ar_sa", + "ar_sd", + "ar_sy", + "ar_tn", + "ar_ye", "be", - "be_BY", + "be_by", "bg", - "bg_BG", + "bg_bg", "ca", - "ca_ES", + "ca_es", "cs", - "cs_CZ", + "cs_cz", "da", - "da_DK", + "da_dk", "de", - "de_AT", - "de_CH", - "de_DE", - "de_GR", - "de_LU", + "de_at", + "de_ch", + "de_de", + "de_gr", + "de_lu", "el", - "el_CY", - "el_GR", + "el_cy", + "el_gr", "en", - "en_AU", - "en_CA", - "en_GB", - "en_IE", - "en_IN", - "en_MT", - "en_NZ", - "en_PH", - "en_SG", - "en_US", - "en_ZA", + "en_au", + "en_ca", + "en_gb", + "en_ie", + "en_in", + "en_mt", + "en_nz", + "en_ph", + "en_sg", + "en_us", + "en_za", "es", - "es_AR", - "es_BO", - "es_CL", - "es_CO", - "es_CR", - "es_CU", - "es_DO", - "es_EC", - "es_ES", - "es_GT", - "es_HN", - "es_MX", - "es_NI", - "es_PA", - "es_PE", - "es_PR", - "es_PY", - "es_SV", - "es_US", - "es_UY", - "es_VE", + "es_ar", + "es_bo", + "es_cl", + "es_co", + "es_cr", + "es_cu", + "es_do", + "es_ec", + "es_es", + "es_gt", + "es_hn", + "es_mx", + "es_ni", + "es_pa", + "es_pe", + "es_pr", + "es_py", + "es_sv", + "es_us", + "es_uy", + "es_ve", "et", - "et_EE", + "et_ee", "fi", - "fi_FI", + "fi_fi", "fr", - "fr_BE", - "fr_CA", - "fr_CH", - "fr_FR", - "fr_LU", + "fr_be", + "fr_ca", + "fr_ch", + "fr_fr", + "fr_lu", "ga", - "ga_IE", + "ga_ie", "hi", - "hi_IN", + "hi_in", "hr", - "hr_HR", + "hr_hr", "hu", - "hu_HU", + "hu_hu", "in", - "in_ID", + "in_id", "is", - "is_IS", + "is_is", "it", - "it_CH", - "it_IT", + "it_ch", + "it_it", "iw", - "iw_IL", + "iw_il", "ja", - "ja_JP", + "ja_jp", "ko", - "ko_KR", + "ko_kr", "lt", - "lt_LT", + "lt_lt", "lv", - "lv_LV", + "lv_lv", "mk", - "mk_MK", + "mk_mk", "ms", - "ms_MY", + "ms_my", "mt", - "mt_MT", + "mt_mt", "nl", - "nl_BE", - "nl_NL", + "nl_be", + "nl_nl", "no", - "no_NO", - "no_NO_NY", + "no_no", + "no_no_ny", "pl", - "pl_PL", + "pl_pl", "pt", - "pt_BR", - "pt_PT", + "pt_br", + "pt_pt", "ro", - "ro_RO", + "ro_ro", "ru", - "ru_RU", + "ru_ru", "sk", - "sk_SK", + "sk_sk", "sl", - "sl_SI", + "sl_si", "sq", - "sq_AL", + "sq_al", "sr", - "sr_BA", - "sr_BA_#Latn", - "sr_CS", - "sr_ME", - "sr_ME_#Latn", - "sr_RS", - "sr_RS_#Latn", - "sr__#Latn", + "sr_ba", + "sr_ba_#latn", + "sr_cs", + "sr_me", + "sr_me_#latn", + "sr_rs", + "sr_rs_#latn", + "sr__#latn", "sv", - "sv_SE", + "sv_se", "th", - "th_TH", + "th_th", "tr", - "tr_TR", + "tr_tr", "uk", - "uk_UA", + "uk_ua", "vi", - "vi_VN", + "vi_vn", "zh", - "zh_CN", - "zh_HK", - "zh_SG", - "zh_TW", + "zh_cn", + "zh_hk", + "zh_sg", + "zh_tw", ) /** Display names of locales defined in [localeTags] */ diff --git a/i18n4k-core/src/commonTest/kotlin/da/comahe/i18n4k/LocaleUtilsTest.kt b/i18n4k-core/src/commonTest/kotlin/da/comahe/i18n4k/LocaleUtilsTest.kt index 67a3d80..f6c41f0 100644 --- a/i18n4k-core/src/commonTest/kotlin/da/comahe/i18n4k/LocaleUtilsTest.kt +++ b/i18n4k-core/src/commonTest/kotlin/da/comahe/i18n4k/LocaleUtilsTest.kt @@ -1,63 +1,276 @@ package da.comahe.i18n4k import de.comahe.i18n4k.Locale +import de.comahe.i18n4k.country +import de.comahe.i18n4k.createLocale import de.comahe.i18n4k.forLocaleTag import de.comahe.i18n4k.getDisplayNameInLocale +import de.comahe.i18n4k.language +import de.comahe.i18n4k.script +import de.comahe.i18n4k.variant import kotlin.test.Test import kotlin.test.assertEquals +import kotlin.test.assertFails +import kotlin.test.assertFalse class LocaleUtilsTest { + @Test - fun forLanguageTagTest() { + fun getDisplayNameInLocaleTest() { + assertEquals( + "xy", + createLocale("xy").getDisplayNameInLocale() + ) + assertEquals( + "xy (Frac)", + createLocale("xy", "frac").getDisplayNameInLocale() + ) + assertEquals( + "xy (AB,Frac)", + createLocale("xy", "frac", "AB").getDisplayNameInLocale() + ) + assertEquals( + "xy (AB,cdefg,Frac)", + createLocale("xy", "frac", "ab", "CDEFG").getDisplayNameInLocale() + ) + assertEquals( + "xy (AB,cdefg)", + createLocale("xy", null, "AB", "CDEFG").getDisplayNameInLocale() + ) + assertEquals( + "xy (cdefg)", + createLocale("xy", null, null, "CDEFG").getDisplayNameInLocale() + ) + assertEquals( + "xy (AB)", + createLocale("xy", null, "AB").getDisplayNameInLocale() + ) + + assertEquals( + "Deutsch (Österreich,cdefg)", + createLocale("de", null, "AT", "CDEFG").getDisplayNameInLocale() + ) + assertEquals( + "Deutsch (Österreich,cdefg,Frac)", + createLocale("de", "frac", "AT", "CDEFG").getDisplayNameInLocale() + ) + assertEquals( + "Deutsch (Österreich)", + createLocale("de", null, "AT").getDisplayNameInLocale() + ) assertEquals( - Locale("en", "US", "WIN"), - forLocaleTag("en_US_WIN") + "Deutsch", + createLocale("de").getDisplayNameInLocale() ) assertEquals( - Locale("en", "US", "WIN_123"), - forLocaleTag("en_US_WIN_123") + "norsk (Norge,nynorsk)", + Locale("no", "NO", "NY").getDisplayNameInLocale() + ) + assertEquals( + "norsk (Norge)", + createLocale("no", null, "NO").getDisplayNameInLocale() + ) + assertEquals( + "norsk", + createLocale("no").getDisplayNameInLocale() ) assertEquals( - Locale("en", "US"), - forLocaleTag("en_US") + "中文 (台灣,cdefg)", + createLocale("zh", null, "TW", "CDEFG").getDisplayNameInLocale() + ) + assertEquals( + "中文 (台灣)", + createLocale("zh", null, "TW").getDisplayNameInLocale() + ) + assertEquals( + "中文", + createLocale("zh").getDisplayNameInLocale() ) assertEquals( - Locale("en", "US"), - forLocaleTag("en_US_") + "Српски (Serbia)", + createLocale("sr", null, "rs").getDisplayNameInLocale() ) assertEquals( - Locale("en"), - forLocaleTag("en") + "Српски (Montenegro)", + createLocale("sr", null, "me").getDisplayNameInLocale() + ) + assertEquals( + "Српски (Latn)", + createLocale("sr", "latn").getDisplayNameInLocale() ) assertEquals( - Locale("en"), - forLocaleTag("en_") + "Srpski (Latin,Crna Gora)", + createLocale("sr", "latn", "me").getDisplayNameInLocale() + ) + assertEquals( + "Srpski (Latin,Srbija)", + createLocale("sr", "latn", "rs").getDisplayNameInLocale() ) } + @Test + fun forLocaleTagTest() { + + // /############# test lang + assertLocale( + forLocaleTag("de"), + "de", "", "", "", null + ) + + assertLocale( + forLocaleTag("de-de"), + "de", "", "DE", "", null + ) + + assertLocale( + forLocaleTag("de-123"), + "de", "", "123", "", null + ) + + assertLocale( + forLocaleTag("de-1234"), + "de", "", "", "1234", null + ) + + assertLocale( + forLocaleTag("de-saxony"), + "de", "", "", "saxony", null + ) + assertLocale( + forLocaleTag("de-1abc"), + "de", "", "", "1abc", null + ) + + + assertLocale( + forLocaleTag("de-de-saxony"), + "de", "", "DE", "saxony", null + ) + + assertLocale( + forLocaleTag("de-de-saxony-a-cd"), + "de", "", "DE", "saxony", mapOf('a' to "cd") + ) + + assertLocale( + forLocaleTag("de-de-saxony-a-cd-efg"), + "de", "", "DE", "saxony", mapOf('a' to "cd-efg") + ) + + assertLocale( + forLocaleTag("de-de-saxony-a-cd-efg-h-ij"), + "de", "", "DE", "saxony", mapOf('a' to "cd-efg", 'h' to "ij") + ) + + assertLocale( + forLocaleTag("de-saxony-a-cd-efg-h-ij"), + "de", "", "", "saxony", mapOf('a' to "cd-efg", 'h' to "ij") + ) + + assertLocale( + forLocaleTag("de-de-a-cd-efg-h-ij"), + "de", "", "DE", "", mapOf('a' to "cd-efg", 'h' to "ij") + ) + + // ############# test extlang + // not supported by Java implementation +// assertLocale( +// forLocaleTag("de-abc-def"), +// "de-abc-def", "", "", "", null +// ) +// +// assertLocale( +// forLocaleTag("de-abc-def-de"), +// "de-abc-def", "", "DE", "", null +// ) +// +// assertLocale( +// forLocaleTag("de-abc-def-hijk"), +// "de-abc-def", "Hijk", "", "", null +// ) + + // ############# test script + assertLocale( + forLocaleTag("de-frac"), + "de", "Frac", "", "", null + ) + assertLocale( + forLocaleTag("de-frac-de"), + "de", "Frac", "DE", "", null + ) + assertLocale( + forLocaleTag("de-frac-de-saxony"), + "de", "Frac", "DE", "saxony", null + ) + assertLocale( + forLocaleTag("de-frac-de-saxony-a-cd"), + "de", "Frac", "DE", "saxony", mapOf('a' to "cd") + ) + assertLocale( + forLocaleTag("de-frac-de-saxony-a-cd-efg"), + "de", "Frac", "DE", "saxony", mapOf('a' to "cd-efg") + ) + assertLocale( + forLocaleTag("de-frac-de-saxony-a-cd-efg-h-ij"), + "de", "Frac", "DE", "saxony", mapOf('a' to "cd-efg", 'h' to "ij") + ) + + } @Test - fun getDisplayNameInLocaleTest() { - assertEquals("xy (AB,CD)", Locale("xy", "AB", "CD").getDisplayNameInLocale()) - assertEquals("xy (AB)", Locale("xy", "AB").getDisplayNameInLocale()) - assertEquals("xy", Locale("xy").getDisplayNameInLocale()) + fun forLocaleTag_invalid_Test() { + assertFails { forLocaleTag("d") } + assertFails { forLocaleTag("de-") } + assertFails { forLocaleTag("deutscher") } + //extlang not supported (by Java implementation) + assertFails { forLocaleTag("de-fix") } + assertFails { forLocaleTag("de-12") } + assertFails { forLocaleTag("de-varianten") } + assertFails { forLocaleTag("de-frac-deut") } + assertFails { forLocaleTag("de-frac-de-s") } + assertFails { forLocaleTag("de-frac-de-saxonyyyy") } + assertFails { forLocaleTag("de-frac-de-saxony-a") } + assertFails { forLocaleTag("de-frac-de-saxony-a-b") } + assertFails { forLocaleTag("de-frac-de-saxony-a-bc-d") } + assertFails { forLocaleTag("de-frac-de-saxony-a-bc-d") } + assertFails { forLocaleTag("de-de-saxonyyyy") } + assertFails { forLocaleTag("de-de-1ab") } + assertFails { forLocaleTag("de-de-abcd") } + assertFails { forLocaleTag("de-de-saxony-a") } + assertFails { forLocaleTag("de-de-saxony-a-b") } + assertFails { forLocaleTag("de-de-saxony-a-bc-d") } + assertFails { forLocaleTag("de-de-saxony-a-bc-d") } - assertEquals("Deutsch (Österreich,XYZ)", Locale("de", "AT", "XYZ").getDisplayNameInLocale()) - assertEquals("Deutsch (Österreich)", Locale("de", "AT").getDisplayNameInLocale()) - assertEquals("Deutsch", Locale("de").getDisplayNameInLocale()) + assertFails { forLocaleTag("12") } + } - assertEquals("norsk (Norge,nynorsk)", Locale("no", "NO", "NY").getDisplayNameInLocale()) - assertEquals("norsk (Norge)", Locale("no", "NO").getDisplayNameInLocale()) - assertEquals("norsk", Locale("no", ).getDisplayNameInLocale()) - assertEquals("中文 (台灣,foo)", Locale("zh", "TW", "foo").getDisplayNameInLocale()) - assertEquals("中文 (台灣)", Locale("zh", "TW").getDisplayNameInLocale()) - assertEquals("中文", Locale("zh").getDisplayNameInLocale()) + private fun assertLocale( + locale: Locale, + language: String, + script: String, + country: String, + variant: String, + extensions: Map? + ) { + println(locale) + assertEquals(language, locale.language) + assertEquals(script, locale.script) + assertEquals(country, locale.country) + assertEquals(variant, locale.variant) + if (extensions == null) + assertFalse(locale.hasExtensions()) + else { + assertEquals(locale.getExtensionKeys(), extensions.keys) + for ((key, value) in extensions) { + assertEquals(locale.getExtension(key), value) + } + } } } \ No newline at end of file diff --git a/i18n4k-core/src/jsMain/kotlin/de/comahe/i18n4k/Locale.kt b/i18n4k-core/src/jsMain/kotlin/de/comahe/i18n4k/Locale.kt index e718bf3..ea94790 100644 --- a/i18n4k-core/src/jsMain/kotlin/de/comahe/i18n4k/Locale.kt +++ b/i18n4k-core/src/jsMain/kotlin/de/comahe/i18n4k/Locale.kt @@ -1,31 +1,20 @@ package de.comahe.i18n4k -actual data class Locale actual constructor( - val language: String, - val country: String, - val variant: String -) { - actual constructor( - language: String - ) : this(language, "", "") +actual typealias Locale = DefaultLocaleImpl - actual constructor( - language: String, - country: String - ) : this(language, country, "") - - actual fun getLanguage(): String = language - actual fun getCountry(): String = country - actual fun getVariant(): String = variant - - override fun toString(): String = toTag() -} +actual fun createLocale( + language: String, + script: String?, + country: String?, + variant: String?, + extensions: Map? +) = createDefaultLocaleImpl(language, script, country, variant, extensions) actual val systemLocale: Locale = run { var locale: Locale? = null try { if (jsTypeOf(kotlinx.browser.window) != "undefined") - locale = forLocaleTag(kotlinx.browser.window.navigator.language, "-") + locale = forLocaleTag(kotlinx.browser.window.navigator.language) } catch (ignore: Throwable) { } if (locale == null) { diff --git a/i18n4k-core/src/jvmMain/kotlin/de/comahe/i18n4k/Locale.kt b/i18n4k-core/src/jvmMain/kotlin/de/comahe/i18n4k/Locale.kt index 43ac6f5..81542de 100644 --- a/i18n4k-core/src/jvmMain/kotlin/de/comahe/i18n4k/Locale.kt +++ b/i18n4k-core/src/jvmMain/kotlin/de/comahe/i18n4k/Locale.kt @@ -3,6 +3,28 @@ package de.comahe.i18n4k actual typealias Locale = java.util.Locale +actual fun createLocale( + language: String, + script: String?, + country: String?, + variant: String?, + extensions: Map? +): Locale { + val builder = java.util.Locale.Builder() + + builder.setLanguage(language.lowercase()) + if (script != null) + builder.setScript(script.capitalize()) + if (country != null) + builder.setRegion(country.uppercase()) + if (variant != null) + builder.setVariant(variant.lowercase()) + extensions?.forEach { (key, value) -> + builder.setExtension(key.lowercaseChar(), value.lowercase()) + } + return builder.build() +} + actual val systemLocale: Locale get() { val userLanguage = System.getProperty("user.language") diff --git a/i18n4k-core/src/nativeMain/kotlin/de/comahe/i18n4k/Locale.kt b/i18n4k-core/src/nativeMain/kotlin/de/comahe/i18n4k/Locale.kt index 32b3e1f..2f2eaf6 100644 --- a/i18n4k-core/src/nativeMain/kotlin/de/comahe/i18n4k/Locale.kt +++ b/i18n4k-core/src/nativeMain/kotlin/de/comahe/i18n4k/Locale.kt @@ -1,23 +1,13 @@ package de.comahe.i18n4k +import kotlinx.collections.immutable.ImmutableMap -actual data class Locale actual constructor( - val language: String, - val country: String, - val variant: String -) { - actual constructor( - language: String - ) : this(language, "", "") +actual typealias Locale = DefaultLocaleImpl - actual constructor( - language: String, - country: String - ) : this(language, country, "") - - actual fun getLanguage(): String = language - actual fun getCountry(): String = country - actual fun getVariant(): String = variant - - override fun toString(): String = toTag() -} +actual fun createLocale( + language: String, + script: String?, + country: String?, + variant: String?, + extensions: Map? +) = createDefaultLocaleImpl(language, script, country, variant, extensions) \ No newline at end of file