The intl
Nim package make it easy to do internationalization (i18n) and localization (l10n) in Nim programs.
This is alpha-quality. Please file issues for any bugs you encounter.
- Compact message format
- Automatic message extraction during compilation
- Message format indicates what's been translated
- Really fast message lookup
- Flexibility beyond pluralizing
- Multiple message repositories (e.g. your program has one list of messages, and a library you import has its own)
- Extracted messages can automatically update message files during compilation (if you want)
- Select a single locale at compile time and avoid all runtime message lookups.
- Warn about duplicate message keys for non-matching values
- Don't require labels for
proc
messages. See nim-lang/Nim#15004
Intall intl
:
nimble install https://github.com/iffy/nim-intl.git
Run the following to generate some boilerplate in the ./trans
directory:
intl init
Follow the instructions emitted by intl init
to incorporate the
newly generated catalog into your code. For instance, add locales for English and Chinese:
intl add en
intl add zh
Create a Nim file (or add some of the following to an existing one):
# main.nim
import ./trans/all
if paramCount() > 0:
setLocale(paramStr(1))
echo tr"Hello, World!"
intlPostlude(currentSourcePath(), "trans")
Compile, which will automatically extract messages for translation
in locale-specific files within ./trans/
:
nim c main.nim
Edit trans/zh.nim
to perform translation.
# trans/zh.nim - Before translating
import ./base
messages "zh":
todo 71994, "s_rhellowor71994", r"Hello, World!"
# trans/zh.nim - After translating
import ./base
messages "zh":
done 71994, "s_rhellowor71994", r"你好,世界!"
Recompile:
nim c main.nim
Run the localized program!
$ ./main en
Hello, World!
$ ./main zh
你好,世界!
This program prints out one translateable message:
import intl
intlCatalog "myprogram"
# `tr` marks strings for translation
echo tr"Hello, World!"
# Emit extracted messages during compilation
intlPostlude()
Because of the intlPostlude()
call, compilation generates the extracted messages, which you can paste back into your code:
## ==== intl postlude ====
baseMessages:
msg "s_rhellowor71994", r"Hello, World!"
## ==== end intl postlude ====
Let's put the baseMessages:
block in as well as the start of a locale for Spanish using messages "localename":
:
import intl
intlCatalog "myprogram"
baseMessages:
msg "s_rhellowor71994", r"Hello, World!"
messages "es":
discard
echo tr"Hello, World!"
intlPostlude()
Compile this file, and it will provide a fleshed-out set of Spanish messages, ready to be translated:
## ==== intl postlude ====
baseMessages:
msg "s_rhellowor71994", r"Hello, World!"
messages "es":
todo 71994, "s_rhellowor71994", r"Hello, World!"
## ==== end intl postlude ====
Use setLocale()
to change the locale. The following will print ¡Hola!
:
import intl
intlCatalog "myprogram"
baseMessages:
msg "s_rhellowor71994", r"Hello, World!"
messages "es":
done 71994, "s_rhellowor71994", r"¡Hola!"
setLocale("es")
echo tr"Hello, World!"
intlPostlude()
You may specify the key used to identify each message, rather than the autogenerated one by calling tr
with 2 arguments like this:
echo tr("greeting", "Hello, World!")
This produces:
baseMessages:
msg "greeting", "Hello, World!"
messages "es":
todo 32096, "greeting", "Hello, World!"
intl
let's you use a Nim proc
to do whatever is needed for pluralizing. Translators will translate Nim functions. Consider the following:
import intl
intlCatalog "myprogram"
echo "You have " & tr("num-animals", proc(cats:int, dogs:int):string =
result = $cats & " "
if cats == 1:
result.add("cat")
else:
result.add("cats")
result.add " and " & $dogs & " "
if dogs == 1:
result.add("dog")
else:
result.add("dogs")
result.add(".")
)(1, 6)
intlPostlude()
Compiling and running produces:
## ==== intl postlude ====
baseMessages:
msg "num-animals", proc (cats: int; dogs: int): string =
result = "You have " & $cats & " "
if cats == 1:
result.add("cat")
else:
result.add("cats")
result.add " and " & $dogs & " "
if dogs == 1:
result.add("dog")
else:
result.add("dogs")
result.add(".")
## ==== end intl postlude ====
You have 1 cat and 6 dogs.
Warning: Because procs can be used, remember to audit translations from other sources.
Messages within each messages "locale":
block have the following format:
<status> <hash>, "<key>", <value>
<status>
is one of the following:
todo
- This message needs to be translated. When done translating, the translator should change this todone
done
- This message is translated. Translators change the status todone
to indicate that the translation has been complete.redo
- This message possibly needs to be re-translated. The compiler marks messages asredo
when the underlying message has changed.gone
- This message is no longer present in the source code.
<hash>
is an opaque hash computed from the message value. It's used to determine if a messages should be marked as redo
. Users should leave this alone.
<key>
is the auto-generated or user-provided unique key for this message. Users should leave this alone.
<value>
is the localized value for the message. Users should change this.
If you want intl
to automatically update message catalogs, call the postlude like this:
import intl
intlCatalog "mycatalog"
intlPostlude(currentSourcePath(), "trans")
This will cause files to be written in ./trans
. After the first time running this, change your main Nim file to this:
import ./trans/all
intlPostlude(currentSourcePath(), "trans")
Add locales by creating empty Nim files named ./trans/XX.nim
where XX
is the locale name. Recompile and they will be populated with your messages.
All message lookups are simply attribute access on the currently-selected locale's messages. This is probably fast (though I haven't measured anything). Consider this echo statement:
echo tr"Hello, World!"
In the case where the message is in baseMessages:
, it expands to:
echo [selectedMessages_myprogram.s_rhellowor71994]
And if the message hasn't yet been added to baseMessages:
, it reverts to the literal value:
echo ["Hello, World!"]