Loco: Localization Package for Nim

There’re few i18n packages for Nim, all look unmaintained and undocumented. So I decided to make my own. Please welcome Loco.

Problem #

The goal is to have the usual i18n localization routine, where you declare language variables separately from the business logic, optionally with multiple variants for different quantities, and use them instead of plain text in your code.

How it’s done in other languages #

In other languages, language variables are usually invoked with a function call that accepts the name of the variable and the quantity to pluralize it with. In Python, for example, you’d write: i18n.t('mail_number', count=12).

Although this is classical approach most developers are used to, there’s one problem I see with it. Language variable names are just strings and strings are not validated. I you mistype a variable name, you either get a runtime error about a missing translation or, even worse, an empty string popping up in production.

How I do it in Loco #

Luckily, the issue described above is easily sovlable in Nim. Nim’s great metaprogramming capabilities let you generate code from declarative style definitions.

In Loco, you define your translations in .nim files using the special loco macro:

# Module with translations, i.e. `localizations/en.nim`
import loco

loco en:
  hello: "hello"

Loco turns this declaration into a function definition:

proc hello*(): string = "hello"

This function can be imported and used in other modules like any other regular one:

# Other module containing the business logic
import localizations/en

echo hello()

To compile the app with a different localization, a compilation flag can be used:

const lang {.strdefine.}: string = "en"

when lang == "ru":
  import localizations/ru
else:
  import localizations/en

To switch between languages during runtime, import both localization modules and fully qualify the called functions:

echo "“Hello” in Russian is " & ru.hello()
echo "“Hello” in Englishis " & en.hello()

Let’s add a complex language variable to the localization module:

# Module with translations, i.e. `localizations/en.nim`
import loco

loco en:
  hello: "hello"
  users:
    zero: "no users"
    one: "one user"
    many: "{n} users"

The generated users proc will have an argument n: int:

echo 0.users  # → "zero users"
echo 1.users  # → "one user"
echo 12.users # → "12 users"

The output of users proc depends on the number passed to it. All cases are grouped into four tiers: zero, one, few, and many. The rules for matching numbers to tiers are defined in pluralizers. Pluralizer is a module within Loco. As of now, there are two: en for English and ru for Russian.

In the simplest case in English, only zero and one are special cases, all other numbers fall into many tier, so the pluralizer is rather trivial (https://github.com/moigagoo/loco/blob/master/src/loco/en.nim):

template pluralize*(n: int): untyped =
  ## Pluralizer for English language.

  case n
  of 0: zero n
  of 1: one n
  else: many n

I think it’s a good idea to allow specific variants for specific values to have translation like “two users.” I may add this feature in future versions.

In Russian, the rules are trickier. You must take into account the number’s value and last digits. The Russian pluralizer is therefore more complicated and uses all four tiers.

How to add more languages to Loco #

Supporting a language means providing a pluralizer for it.

Adding a pluralizer is not hard. It’s a matter of adding a module in https://github.com/moigagoo/loco/tree/master/src/loco that implements exports pluralize(n: int). For any given n, it must provide a branch of execution that ends with a call to zero, one, few, or many. It may use helper procs (like Russian pluralizer does) but only pluralize must be exported.

Limitations #

This is my first approach to the task, so not everything I wanted to implement is currently implemented.

There are two most painful points:

case user.lang
of en: renderPage(en.hello, en.account, en.signin)
of ru: renderPage(ru.hello, ru.account, ru.signin)

Code review and contrinutions are very welcome #

I’m the only one writing in Nim among my colleagues and I’m not a professional software developer, so my code has to be poor. If you are a nimmer and have a spare minute, please review my code: https://github.com/moigagoo/loco.

The project is tiny and therefore easy to contrinute to. If you’re looking for a project to wet your feet with Nim, this is a good one. If you don’t know where to start, just email me (the address is in my GitHub profile) or ask right here in the comments.

 
10
Kudos
 
10
Kudos

Now read this

Multi-Language Documentation in Reverse

Note: This is a longer version of my lighting talk and Write the Docs EU 2014. Delivering content in many languages is great. It’s also quite resource consuming. So, you have to decide what really needs multi-language support and what... Continue →