Skip to content

Commit

Permalink
Merge pull request #12 from Skeptick/improve-localization
Browse files Browse the repository at this point in the history
Improve localization
  • Loading branch information
Skeptick authored Mar 25, 2023
2 parents ee27927 + b04c81f commit 85aac9e
Show file tree
Hide file tree
Showing 15 changed files with 176 additions and 138 deletions.
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Resources generation in Kotlin Multiplatform.

buildscript {
dependencies {
classpath("io.github.skeptick.libres:gradle-plugin:1.1.6")
classpath("io.github.skeptick.libres:gradle-plugin:1.1.7")
}
}
```
Expand Down Expand Up @@ -81,7 +81,7 @@ Android or JVM:

### Strings
Strings are stored in the usual for Android form in `xml` files.
The file postfix must contain the code of the language for which the strings are intended. Ex: `my_app_strings_en.xml`
The file postfix must contain the code of the language for which the strings are intended. E.g.: `my_app_strings_en.xml`
For each of the languages you can create several files and they will be merged during compilation.
```xml
<?xml version="1.0" encoding="utf-8"?>
Expand All @@ -106,10 +106,13 @@ Swift:
MainRes.shared.string.simple_string
```
***
> ⚠️ Note: In the example above `MainRes.string.simple_string` will return a string,
> **Note**
> In the example above `MainRes.string.simple_string` will return a string,
> so for better localization support it's not recommended to store the value.
> Get it directly at the place where it's displayed in the UI.
> This seems familiar but the ability to work directly with strings instead of resource IDs can be misused.
***
#### [More about localization](docs/LOCALIZATION.md)

### Images

Expand Down Expand Up @@ -171,7 +174,7 @@ Image size in Figma is **240x89**. Final image name is **pic_(orig)_(240).png**
kotlin {
commonMain {
dependencies {
implementation("io.github.skeptick.libres:libres-compose:1.1.6")
implementation("io.github.skeptick.libres:libres-compose:1.1.7")
}
}
}
Expand Down
62 changes: 62 additions & 0 deletions docs/LOCALIZATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
## Changing Localization

As mentioned in the [README](../README.md#strings), the language to which the strings belong
is indicated in the file name before the extension (e.g. `strings_en.xml`).

During the application runtime, the localization selection is based on the system settings
and changing the language should also be done at the application instance level.

Examples of setting English language for all supported platforms:

**Android & JVM**
```kotlin
Locale.setDefault(Locale("en"))
```
> **Note**
> For Android, don't forget to update the configuration as well.
**iOS & MacOS**
```swift
UserDefaults.standard.set("en", forKey: "AppleLanguages")
```
> **Note**
> All used localizations must be declared in XCode project settings.
**JS**

In JS, there is no such possibility to change the language (without changing the language in the operating system or browser),
so the best option is to use `LibresSettings`:

```kotlin
LibresSettings.languageCode = "en"
```

This solution will also work for all other platforms but remember that this setting applies
only to the selection of localization for strings in Libres, and some
system components may remain untranslated.
> **Warning**
> The value in `LibresSettings.languageCode` has higher priority than system settings.
## Plural rules

In Android, the SDK solution is used to determine the plural form,
in JVM 3rd-party library is used. But in Apple this API is closed and in JS it's completely absent,
so to avoid burdening the library with heavy solutions, "out of the box" only rules are provided
for [some languages](../libres-core/src/appleAndJsMain/kotlin/io/github/skeptick/libres/strings/PluralRules.kt).

You can create a Pull Request with the required languages or define these values at runtime:

```kotlin
import io.github.skeptick.libres.strings.PluralRules
import io.github.skeptick.libres.strings.PluralForm

PluralRules["en"] = PluralRule { number ->
when (number) {
1 -> PluralForm.One
else -> PluralForm.Other
}
}
```

When defining rules you can refer to
[this document](https://unicode-org.github.io/cldr-staging/charts/37/supplemental/language_plural_rules.html).
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ android.disableAutomaticComponentCreation=true
kotlin.mpp.androidSourceSetLayoutVersion=2

GROUP=io.github.skeptick.libres
VERSION_NAME=1.1.6
VERSION_NAME=1.1.7

SONATYPE_HOST=S01

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ import androidx.compose.ui.graphics.painter.Painter
import io.github.skeptick.libres.images.Image

@Composable
expect fun painterResource(image: Image): Painter
expect fun painterResource(image: Image): Painter

@Composable
fun Image.painterResource() = painterResource(this)
10 changes: 10 additions & 0 deletions libres-core/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ android {

kotlin {
sourceSets {
val commonMain by getting
val appleMain by getting
val jsMain by getting

androidMain {
dependencies {
implementation(libs.androidx.core)
Expand All @@ -22,5 +26,11 @@ kotlin {
implementation(libs.icu4j)
}
}

val appleAndJsMain by creating {
dependsOn(commonMain)
appleMain.dependsOn(this)
jsMain.dependsOn(this)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package io.github.skeptick.libres.strings

import io.github.skeptick.libres.strings.PluralForm.*

fun interface PluralRule {
fun getForm(number: Int): PluralForm
}

enum class PluralForm(internal val formName: String) {
Zero("zero"),
One("one"),
Two("two"),
Few("few"),
Many("many"),
Other("other")
}

object PluralRules {

private val custom = mutableMapOf<String, PluralRule>()

private val English = PluralRule { number ->
when (number) {
1 -> One
else -> Other
}
}

private val Russian = PluralRule { number ->
when (number % 100) {
11, 12, 13, 14 -> Many
else -> when (number % 10) {
1 -> One
2, 3, 4 -> Few
else -> Many
}
}
}

private val Ukrainian = PluralRule { number ->
when (number % 100) {
11, 12, 13, 14 -> Many
else -> when (number % 10) {
1 -> One
2, 3, 4 -> Few
else -> Many
}
}
}

private val Kazakh = PluralRule { number ->
when (number) {
1 -> One
else -> Other
}
}

private val French = PluralRule { number ->
when (number) {
0, 1 -> One
else -> Other
}
}

operator fun set(languageCode: String, value: PluralRule) {
custom[languageCode] = value
}

operator fun get(languageCode: String): PluralRule {
return when (languageCode) {
"en" -> English
"ru" -> Russian
"uk" -> Ukrainian
"kk" -> Kazakh
"fr" -> French
else -> custom[languageCode] ?: error("Plural rule for '$languageCode' not provided")
}
}

operator fun plus(value: Pair<String, PluralRule>) {
custom[value.first] = value.second
}

operator fun plusAssign(map: Map<String, PluralRule>) {
custom += map
}

}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,6 @@ actual fun getPlatformLanguageCode(): String {
}

actual fun getPluralizedString(forms: PluralForms, languageCode: String, number: Int): String {
val form = getPluralForm(languageCode, number)
return forms[form] ?: error("Plural form '$form' not provided")
val formName = PluralRules[languageCode].getForm(number).formName
return forms[formName] ?: error("Plural form '$formName' not provided")
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ actual fun getPlatformLanguageCode(): String {
}

actual fun getPluralizedString(forms: PluralForms, languageCode: String, number: Int): String {
val form = getPluralForm(languageCode, number)
return forms[form] ?: error("Plural form '$form' not provided")
val formName = PluralRules[languageCode].getForm(number).formName
return forms[formName] ?: error("Plural form '$formName' not provided")
}

private inline val String.languageCode get() = if (length == 2) this else take(2)

0 comments on commit 85aac9e

Please sign in to comment.