Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Gettext support in OCaml

I'd like to get i18n working in my OCaml code (translating text messages into the user's preferred language). Most translatable strings are the first non-labelled argument to one of a small group of functions. e.g.

  • Text output: print "Feed '%s':" new_feed
  • Errors: raise_safe "Local feed file '%s' does not exist" path
  • Logging: log_warning ~ex "Error checking signature for %s" feed

In the GUI code, strings in ~label arguments should be extracted too.

Is there some way I can extract these strings automatically? To make things more interesting, I use some syntax extensions (e.g. Lwt) too.

I see there is an ocaml-gettext package, but:

  • It only seems to support a fixed set of functions (f_, s_, etc).
  • OPAM says nothing uses it.
  • It failed to install (had to unset $NAME first), then failed to uninstall too, which also suggests it's not heavily used.

What do people actually use/recommend?

like image 346
Thomas Leonard Avatar asked Oct 04 '14 10:10

Thomas Leonard


1 Answers

This is an issue I want to address in my Gasoline project, an application toolkit.

An answer from the future

In the examples, there is an over-engineered wordcount program, which serves as use-case for the library. The function Component.Message.cannot_open uses its format string as a key in an internationalised messages database:

let cannot_open name reason =
  send sink Error "${FILENAME:s}: cannot open (${REASON:s})"
  [ "FILENAME", make String name;
    "REASON", make String reason;
  ]

The sink is a global state of the software component it belongs to, it is bound to a database of internationalised messages where a translation is looked up and used to prepare the actual message.

Was suggested by the example, this facility supports formatting indications, much like printf does, we may write ${FILENAME:+20s} for instance.

Corrections for the present

For now, there is no actual database lookup implemented. The send function is defined in the functor Generic_message.Sink.Make parametrised by a Database module. The current implementation of wordcount uses the following implementation of the database:

module Database =
struct
  type t = unit
  type connection_token = unit
  let opendb () = ()
  let find () id = id
  let close () = ()
end

It is however possible to replace this definition with an actual database lookup — which is in the backlog.

Organisation of messages

In my opinion, message internationalisation should be accomplished by software components — the largest organisational unity below the whole application — and libraries or similar facilities should use standard constant sum types to report errors.

like image 56
Michaël Le Barbier Avatar answered Nov 01 '22 12:11

Michaël Le Barbier