Sean Keane
04/27/2021, 1:54 PMBig Chungus
04/27/2021, 1:55 PMSean Keane
04/27/2021, 1:59 PMBig Chungus
04/27/2021, 2:00 PMBig Chungus
04/27/2021, 2:01 PMSean Keane
04/27/2021, 2:02 PMSean Keane
04/27/2021, 2:03 PMSean Keane
04/27/2021, 2:04 PMRobert Jaros
04/27/2021, 2:06 PMRobert Jaros
04/27/2021, 2:06 PMRobert Jaros
04/27/2021, 2:07 PMSean Keane
04/27/2021, 2:08 PMTomasz Krakowiak
04/27/2021, 3:37 PMfun interface Message<T> {
suspend operator fun get(languageQualifier: LanguageQualifier) : T
}
and a translation interface:
interface Translation {
val hello : String get() = "Hello"
// ...
}
and for regular messages I can just delegate to Translation interface {lq -> translation(lq).hello}
NOTE: If you have modular app, you may use Trasnslation
interface and translation
function per module.
If you want to load translations from server, you can may Translation
implementations @Serializable
and translation
function suspend
.
You can also have some Message
implementations serializable, but I haven't yet encountered a use case where I would recommend it.
In future I will be looking to support my solution with code generation.Sean Keane
04/28/2021, 3:41 AMfun transkribe(key: String, currentLocale: Locale = //default) =
keyMap[locale].?let { lang ->
//return translated string
} ?: run { "" }
}
value = transkribe(HELLO)
I delegate the population of the map to external sources using builder functions in the object. This way the map can be loaded with the translation file from any location without worrying about supporting it directly in code but as a config.
It might be worth adding a parser for different feeds, such as Android XML and Swift in the future but for now I think this might work across all platforms with a standard template.Tomasz Krakowiak
04/28/2021, 7:28 AM@Serializable
Translation
interface - you can send it to client when needed (also, that's why I used suspend
in Message
fun interface). You don't even need to hardcode translation strings, but having translation as interface allows you to create locale specific implementations that handles inflection nicely. Other option would be code-splitting, but that might be difficult in kotlin.
For example - units conjugation.
English:
class EnglishUnitTranslation(
val singular : String,
val plural : String
) {
inflated(count : Int) = if(count == 1) { singular } else { plural }
}
Simple, isn't it? Let's do polish:
class PolishUnitTranslation(
val singular : String, // ex. jednostka
val pluralA : String, // ex. jednostki
val pluralB : String, // ex. jednostek
) {
inflated(count : Int) = when {
count == 1 -> singular
count >= 11 && count <= 20 -> pluralB
count%10 >= 2 && count%10 <== 4 -> pluralA
else -> pluralB
}
}
Gettext distinguishes 12 "plural-forms" languages families - https://www.gnu.org/software/gettext/manual/html_node/Plural-forms.html
I don't think generic i18n frameworks would give you this much flexibility.
For example, this case you can simply implement with generic i18n framwork:
fun helloMessage(user, unreadMessagesCount) = if(undreadMessagesCount > 0) {
helloMessage.substitude(
"userReference" to referenceUser(user),
"unreadMessagesCount" to unreadMessagesCount,
"unreadMessagesUnit" to messageUnit.inflated($unreadMessagesCount)
} else {
helloNoMessages.substitude(
"userReference" to referenceUser(user)
)
}
But if you want to use "Good morning", "Good evening", "Good afternoon" instead simple "Hello", this also may become locale specific and any i18n framework probably won't save you now 🙃christophsturm
04/28/2021, 7:56 AMSean Keane
04/28/2021, 4:43 PMSean Keane
04/29/2021, 1:44 PMfun interface Message<T> {
suspend operator fun get(languageQualifier: LanguageQualifier) : T
}
All I get are errors with this.Tomasz Krakowiak
04/29/2021, 6:48 PMsuspend
. I guess `operator`s cannot be used with suspend
. The easier solution would be to get rid of operator
keyword. It's just syntactic sugar.dave08
05/03/2021, 2:07 AM