33.3. Internationalization of User Programs

33.3.1. The GNU gettext
33.3.1.1. Domain
33.3.1.2. Category
33.3.1.3. Internationalization Example
33.3.2. Locale

33.3.1. The GNU gettext

GNU gettext is a set of functions, included in CLISP or the C library, which permit looking up translations of strings through message catalogs. It is also a set of tools which makes the translation maintenance easy for the translator and the program maintainer.

The GNU gettext functions are available in CLISP in the I18N package, which is EXT:RE-EXPORTed from the EXT package.

This module is present in the base linking set by default.

When this module is present, *FEATURES* contains the symbol :I18N.

(I18N:GETTEXT MSGID &OPTIONAL DOMAIN CATEGORY)
returns the translation of the message MSGID, in the given DOMAIN, depending on the given CATEGORY. MSGID should be an ASCII string, and is normally the English message.
(I18N:NGETTEXT MSGID msgid_plural n &OPTIONAL DOMAIN CATEGORY)
returns the plural form of the translation for of MSGID and n in the given DOMAIN, depending on the given CATEGORY. MSGID and msgid_plural should be ASCII strings, and are normally the English singular and English plural variant of the message, respectively.

33.3.1.1. Domain

The DOMAIN is a string identifier denoting the program that is requesting the translation. The pathname of the message catalog depends on the DOMAIN: usually it is located at TEXTDOMAINDIR/l/LC_MESSAGES/domain.mo, where l is the ISO 639-2 code of the language. The notion of DOMAIN allows several Lisp programs running in the same image to request translations independently of each other.

Function I18N:TEXTDOMAIN(I18N:TEXTDOMAIN) is a place that returns the default DOMAIN, used when no DOMAIN argument is passed to the I18N:GETTEXT and I18N:NGETTEXT functions. It is SETFable. (SETF I18N:TEXTDOMAIN) is usually used during the startup phase of a program. Note that the default DOMAIN is not saved in a memory image. The use of (SETF I18N:TEXTDOMAIN) is recommended only for programs that are so simple that they will never need more than one DOMAIN.

Function I18N:TEXTDOMAINDIR(I18N:TEXTDOMAINDIR DOMAIN) is a place that returns the base directory, called TEXTDOMAINDIR above, where the message catalogs for the given DOMAIN are assumed to be installed. It is SETFable. (SETF I18N:TEXTDOMAINDIR) is usually used during the startup phase of a program, and should be used because only the program knows where its message catalogs are installed. Note that the TEXTDOMAINDIRs are not saved in a memory image.

33.3.1.2. Category

The CATEGORY argument of the I18N:GETTEXT and I18N:NGETTEXT functions denotes which LOCALE facet the result should depend on. The possible values are a platform-dependent subset of :LC_ADDRESS, :LC_ALL, :LC_COLLATE, :LC_CTYPE, :LC_IDENTIFICATION, :LC_MEASUREMENT, :LC_MESSAGES, :LC_MONETARY, :LC_NAME, :LC_NUMERIC, :LC_PAPER, :LC_TELEPHONE, :LC_TIME The use of these values is useful for users who have a character/time/collation/money handling set differently from the usual message handling. Note that when a CATEGORY argument is used, the message catalog location depends on the CATEGORY: it will be expected at TEXTDOMAINDIR/ll/category/domain.mo.

33.3.1.3. Internationalization Example

A non-internationalized program simulating a restaurant dialogue might look as follows.

prog.lisp. 

(setq n (parse-integer (first EXT:*ARGS*)))

(format t "~A~%" "'Your command, please?', asked the waiter.")

(format t "~@?~%"
          (if (= n 1) "a piece of cake" "~D pieces of cake")
          n)

After being internationalized, all strings are wrapped in I18N:GETTEXT calls, and I18N:NGETTEXT is used for plurals. Also, I18N:TEXTDOMAINDIR is assigned a value; in our case, for simplicity, the current directory.

prog.lisp. 

(setf (textdomain) "prog")
(setf (textdomaindir "prog") "./")

(setq n (parse-integer (first EXT:*ARGS*)))

(format t "~A~%"
          (gettext "'Your command, please?', asked the waiter."))

(format t "~@?~%"
          (ngettext "a piece of cake" "~D pieces of cake" n)
          n)

For ease of reading, it is customary to define an abbreviation for the I18N:GETTEXT function. An underscore is customary.

prog.lisp. 

(setf (textdomaindir "prog") "./")
(defun _ (msgid) (gettext msgid "prog"))

(setq n (parse-integer (first EXT:*ARGS*)))

(format t "~A~%"
          (_"'Your command, please?', asked the waiter."))

(format t "~@?~%"
          (ngettext "a piece of cake" "~D pieces of cake" n "prog")
          n)

Now the program's maintainer creates a message catalog template through the command

$ xgettext -o prog.pot prog.lisp

Note

xgettext version 0.11 or higher is required here.

The message catalog template looks roughly like this.

prog.pot. 

msgid "'Your command, please?', asked the waiter."
msgstr ""

msgid "a piece of cake"
msgid_plural "%d pieces of cake"
msgstr[0] ""
msgstr[1] ""

Then a French translator creates a French message catalog

prog.fr.po. 

msgid ""
msgstr ""
"Content-Type: text/plain; charset=ISO-8859-1\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\n"

msgid "'Your command, please?', asked the waiter."
msgstr "«Votre commande, s'il vous plait», dit le garçon."

# Les gateaux allemands sont les meilleurs du monde.
msgid "a piece of cake"
msgid_plural "%d pieces of cake"
msgstr[0] "un morceau de gateau"
msgstr[1] "%d morceaux de gateau"

and sends it to the program's maintainer.

The program's maintainer compiles the catalog as follows:

$ mkdir -p ./fr/LC_MESSAGES
$ msgfmt -o ./fr/LC_MESSAGES/prog.mo prog.fr.po

When a user in a french LOCALE then runs the program

$ clisp prog.lisp 2

she will get the output

    «Votre commande, s'il vous plait», dit le garçon.
    2 morceaux de gateau

33.3.2. Locale

(I18N:SET-LOCALE &OPTIONAL CATEGORY LOCALE)

This is an interface to setlocale.

When LOCALE is missing or NIL, return the current one.

When CATEGORY is missing or NIL, return all categories as a LIST.

(I18N:LOCALE-CONV)

This is an interface to localeconv.

Returns a I18N:LOCALE-CONV structure.

(I18N:LANGUAGE-INFORMATION &OPTIONAL item)

This is an interface to nl_langinfo (UNIX) and GetLocaleInfo (Win32).

When item is missing or NIL, return all available information as a LIST.


These notes document CLISP version 2.49.93+Last modified: 2018-02-19