[ANN] I18n 5.0 — Internationalisation made easy for any OCaml app

The Ocsigen team is happy to announce the release of ocsigen-i18n 5.0, a small but practical internationalisation library for OCaml.

The big change in this release: ocsigen-i18n is no longer tied to Eliom. It can now be used in any OCaml project, with optional extensions for Tyxml and Eliom when you need them.

Ocsigen-i18n was initially written by @sagotch from Be Sport. This release has been made possible thanks to the work of Habib, funded by IRILL.

opam install ocsigen-i18n

How it works

Translations are written in a plain TSV file (one key per line, one column per language):

foo                 This is a simple key.        Ceci est une clé toute simple.
a_human             a human                      un humain
bar                 I am {{x}}.                  Je suis {{x}}.
baz                 There {{{c?are||is an}}} apple{{{c?s||}}} here!  Il y a {{{c?des||une}}} pomme{{{c?s||}}} ici !
bu                  I am {{x %s}} ({{n %d}}).    Je suis {{x %s}} ({{n %d}}).

The mini-templating language supports:

  • {{x}} a string variable ~x
  • {{x %d}} a typed variable using the given format specifier
  • {{{c?yes||no}}} an optional boolean ?c switching between two strings

Then in your code, a PPX extension turns [%i18n key] into the right call:

print_endline [%i18n foo];
print_endline [%i18n bar ~x:[%i18n a_human]];
print_endline [%i18n baz ~c:(nb > 1)];
print_endline [%i18n bu ~x:"Jean-Michel" ~n:42];

(* Switching language explicitly *)
print_endline [%i18n foo ~lang:My_i18n.Fr];

The current language is held in a mutable reference you can swap (or replace with an Eliom scoped reference if you need per-session/tab languages).

Dune integration

The tool plugs into Dune very naturally. Generate the OCaml module from your TSV file with a rule:

(rule
 (target example_i18n.ml)
 (deps example_i18n.tsv)
 (action
  (run %{bin:ocsigen-i18n} --languages en,fr --input-file %{deps}
                           --output-file %{target})))

And wire the PPX in your library/executable:

(preprocess (pps ocsigen-i18n -- --default-module Example_i18n))

That’s all you need for a plain OCaml project.

Tyxml support

Pass --tyxml to the generator and the same [%i18n key] expression now produces a list of Tyxml HTML elements instead of a string:

(* Builds an HTML fragment, ready to drop into a Tyxml tree *)
let greeting = [%i18n bar ~x:[%i18n a_human]]

Variables can themselves be lists of HTML nodes, so you can mix translated text with markup naturally:

[%i18n bar ~x:[ txt "Jean-Michel ("
              ; txt (string_of_int id)
              ; txt ")" ]]

If you need a plain string in Tyxml mode (for an attribute, for instance), just prefix with S.:

[%i18n S.bar ~x:[%i18n S.a_human]]   (* string output *)

Eliom support

For client–server Eliom apps, pass --eliom. The generator emits an .eliom file (so the same translations are available on both sides), implies --tyxml, and adds [@@deriving json] on the language type so you can serialise it across the wire:

(rule
 (target example_i18n.eliom)
 (deps example_i18n.tsv)
 (action
  (run %{bin:ocsigen-i18n} --eliom --languages en,fr --input-file %{deps}
                           --output-file %{target})))

Multiple TSV files

You can split translations across several files. The PPX uses your module path to find the right one:

[%i18n foo]                          (* default module *)
[%i18n MyI18n.foo]                   (* MyI18n.foo *)
[%i18n MyI18n.S.bar ~x:[%i18n S.foo]]

Optional --prefix / --suffix flags let you keep call-sites concise ([%i18n Feature.foo]Pr_Feature_i18n.foo ()).


Happy translating!

Note that this is the wrong rule for English which uses nb<>1.

This is for this reason that the PO file format uses the number of items to support plural forms since languages tend to have many rules for plural.

For instance English uses a special rule for ordinal depending on n mod 10. Polish has a similar rule except that it has two plural forms (one for 2-4 and another for 5-9). In general, dual is also not unheard of as a second plural form, for instance in Slovene.