Giter VIP home page Giter VIP logo

ktor-i18n's Introduction

Ktor I18n

License: MIT Latest Version CI

Internationalization support for Ktor framework.

Usage

Add The Dependency

add the dependency to your gradle build file:

repositories {
    jcenter()
    maven { url "https://jitpack.io" }
}

dependencies {
    implementation 'com.github.aymanizz:ktor-i18n:VERSION'
}

where "VERSION" is dependent on the ktor version you are using. If you are using ktor 2.0.0 or newer, use version 2.0.0. If you are using older ktor versions, use version 1.0.0.

Usage Example

For this example we will create a messages bundle with English and Arabic localizations.

Start by installing the feature in your application:

install(I18n) {
    supportedLocales = listOf("en", "ar").map(Locale::forLanguageTag)
}

Because the english locale is the first in the supported locales list, it will be considered the default/fallback locale.

Next, let's create i18n/ folder inside the resources directory, and create two files: Messages_en.properties and Messages_ar.properties, in this folder with the following content:

  • inside Messages_en.properties
greeting=Hello!
  • inside Messages_ar.properties
greeting=هلا!

We now have the most basic setup for the i18n feature, we can use it as follows:

routing {
    get("/greeting") {
        call.respondText(call.t(R("greeting")))
    }
}

Now that the route /greeting is set up, we can send a request and expect a response depending on the request's Accept-Language header value.

For example, if the header value is en-US the response will be Hello, if it's en;q=0.7,ar;q=0.9 then the response will be هلا, and if it's de then the response will fallback to Hello because english is the default locale.

Now let's add more keys to our messages. This time we will use a key with a count, and also a placeholder:

  • in Messages_en.properties
pieces=You have {0,number} pieces.
pieces.1=You have a single piece.
pieces.2=You have two pieces.
  • in Messages_ar.properties
pieces=لديك {0,number} قطع
pieces.1=لديك قطعة واحدة
pieces.2=لديك قطعتان

And use it like so:

routing {
    get("/greeting") { /* ... */ }
    get("/pieces") {
        val count = call.parameters["count"]?.toIntOrNull() ?: 1
        call.respondText(call.t(R("pieces", count = count), count))
    }
}

Note that we have passed count twice, once to the R class constructor which generates the keys to lookup the localization, this class will generate the keys "pieces.$count" and "pieces", the localization provider will try each key until one of them is present in the messages bundle, then the obtained message is formatted using count (the second parameter passed to t call.) In the case of the pieces.1 and pieces.2 messages, the extra argument is ignored. But in the case of peices message, the {} placeholder is replaced with the value of count.

Go ahead and try your application now with different Accept-Language header values and different count query parameter values.

Different Key Generation Strategy

So far we have used the default strategy provided by the R key generator instances. However, one can implement any other strategy to suit their needs. For example, here is how to add a gender key to our keys:

enum class Gender { Male, Female }

// implement a key generator
class MyKey(
        @PropertyKey(resourceBundle = "i18n.Messages")
        baseKey: String,
        count: Int? = null,
        gender: Gender? = null
) : DelimitedKeyGenerator(baseKey, count?.toString(), gender?.name?.toLowerCase())

Then we can use it in our code as before, in messages:

greeting=Hello!
greeting.female=Hi!

And the application code:

call.t(MyKey("greeting", gender=Gender.Female)) // result is Hi!

The key generator could be any iterable that produces strings, so you can customize the generation even further.

Usage with FreeMaker

You can use this plugin along with the ktorx:ktor-i18n-freemarker plugin developed by Nathan Fallet.

Contribution

Development

To run the tests:

./gradlew test

To run the formatter:

./gradlew spotlessApply

To run all checks at once:

./gradlew check

Issues

If you encounter any issues with this feature, let us know by opening a new issue with the issue description. Make sure that there are no similar issues already open.

Features

Before starting on development for a new feature please open a feature request issue for discussion.

License

The project is licensed under the MIT License. For further details see LICENSE.

ktor-i18n's People

Contributors

atistrcsn avatar aymanizz avatar kamilkurde avatar nathanfallet avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar

ktor-i18n's Issues

Discussion: Route interceptor based localization resolution

Currently, the route-dependent localizations are supported using a prefix. An improvement to this might be to use route interceptors to resolve localization for specific parts of the routes tree.

This doesn't necessarily replace the prefix-based method unless using a route interceptor can do all the features provided by the prefix-based method with minimal effort.

Use i18n with FreeMarker

Is your feature request related to a problem? Please describe.
I've been looking for an optimized way to use i18n with FreeMarker, see bellow how and what I've used currently.

Describe the solution you'd like
I don't know if it's possible; I would love to be able to call call.t from FreeMarker, as a function, or with a custom tag.

Describe alternatives you've considered
Currently I'm passing translations as a map, but it's not optimized at all, as I need to have explicit declarations of the ones I need:

call.respond(FreeMarkerContent("test.ftl", mapOf(
    "t" to mapOf(
        "key" to call.t(R("key")),
        // ...
    ),
    // Other custom data ...
)))

Additional context
I saw there is a function feature in FreeMarker, but I'm unable to pass call.t as a function, it gave me an error (it's passed as a string with the hash of the object and I cannot call it).

Route dependent localizations

Add ability to specify a localization depending on the current route, for example the localization for the routes under /en route would be English. This could be supported through route parameters /{localization} or a specialized route builder.

Support CLDR Plurals and per-call MessageResolver

Is your feature request related to a problem? Please describe.

Describe the solution you'd like
I would do the code and open a PR trying the best I can to not break any existing API but supporting the extra features.

  • extra parameter on the class R to define the type of plural stye to use (the current number based option/default) and CLDR based (the new option)
  • introduce a private val CallResolverKey = AttributeKey<ResourceBundleMessageResolver?>("CallResourceBundleMessageResolver") so that on the call to fun ApplicationCall.t( it would delegate the to a different resolver (with a different baseName)

Describe alternatives you've considered
¯\_(ツ)_/¯ I don't know forking the project maybe.

The conversion from AcceptLanguage header to a Locale is not adequate

Describe the bug
When supporting multiple locales based on the same language, the logic to find the best locale given a header is not adequate.

To Reproduce
Given: a server supportedLocales = en-US, en-AU, en-IE, en-GB, and others
When: a request with en-GB header is received
Then: en-US will be used.

Expected behavior
Expected: en-GB to be used

Additional context

I would need to do more tests, but it seems it's a simple case of switching in I18n.kt
from:

        val locale = Locale.filter(ranges, i18n.supportedLocales).firstOrNull()
            ?: Locale.lookup(ranges, i18n.supportedLocales)
            ?: i18n.defaultLocale

to:

        val locale = Locale.lookup(ranges, i18n.supportedLocales)
            ?: Locale.filter(ranges, i18n.supportedLocales).firstOrNull()
            ?: i18n.defaultLocale

it happens because Locale.filter(...).firstOrNull() will match the first locale and ignore that a more specific locale exists in the range, while the Java implementation for lookup() was created to find the best match.

Multi-module support

Is your feature request related to a problem? Please describe.
The i18n extension doesn't support multi-modules. All the string resources must live inside the main app module.

Describe the solution you'd like
I don't have any ideas at the moment.

Describe alternatives you've considered
I use a simple Gradle script to merge all the string resources from different modules.

UTF8Control not encoding the german characters properly

Describe the bug
I am using Ktor-i18n version 2.0.0 and I realized that there is an encoding issue when encoding German characters like ä or ü.
Those characters become >>

To Reproduce
Please create a resource with German characters for example Messages_de.properties:
unavailable = Nicht verfügbar

Expected behavior
Characters should be encoded correctly.

Split MessageResolver and Ktor support to different module

Is your feature request related to a problem? Please describe.
It would be better to split MessageResolver and key generators to a separate module which does not require Ktor dependencies so that someone can use it without Ktor or add it as a dependency to a module that does not require Ktor.

Ktor plugin can be in another module with the dependencies needed from Ktor project

Describe the solution you'd like
Split library to two modules, one for message resolver and one for Ktor support

Describe alternatives you've considered
I cannot think any other alternative

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.