free-gettext is a drop-in compatible replacement for the gettext egg implemented from scratch purely in Scheme and thus available with a BSD license. free-gettext is also easier to use, allowing you to optionally load .po files directly without compiling them to .mo files first, and has many advanced features such as multi-language locales and cascaded message domains.
Introduction[procedure] (textdomain [<name>])
[procedure] (gettext <message>)
(use free-gettext) (textdomain "myapp") (print (gettext "Hello, world!"))
The above is the most basic usage and is equivalent to the GNU gettext module. TEXTDOMAIN sets the name of the current "domain", which is the name of the message catalog (typically the name of the application). With no arguments it returns the name of that domain. GETTEXT then fetches the translation of the message in that domain in the current locale (from the LANG environment variable), or returns the original string if the message can't be found.
Apart from actually creating the message files (which you can put off indefinitely), that's all you need to internationalize your message strings. Since every natural language string needs to be translated, a common idiom is to give a short name to GETTEXT:
(define _ gettext) (print (_"Hello, world!"))
Alternately, you could make , a prefix for all message strings:
(define-syntax unquote (syntax-rules () ((_ str) (gettext str)))) (print ,"Hello, world!")
Note that free-gettext converts all strings to utf8 internally, to work best with the utf8 egg and other eggs which assume Chicken strings are utf8. This is a feature - all strings are consistently the same encoding, and you don't need to create separate message files for every different encoding a user might want. Many GUI toolkits like Gtk also assume utf8. On the other hand, if you want to output text to a non-utf8 terminal you'll need to perform the translation manually.
There is also a procedure NGETTEXT for working with plural forms:[procedure] (ngettext <msg-singular> <msg-plural> <n>)
(format #t (ngettext "~D file removed" "~D files removed" n) n)
In the case of English (or if no translation is found), this applies the familiar logic of using the first string (singular) if N is 1, and the second string (plural) otherwise. Not all languages have two plural forms, however, and the NGETTEXT interface allows you to write message files with the proper inflections for any language and any value of N. The exact details of how to write a message file is beyond the scope of this document - see the GNU gettext documentation.
As with GNU gettext, the following are provided:[procedure] (dgettext <domain> <message>)
[procedure] (dcgettext <domain> <message> <locale>)
[procedure] (dngettext <domain> <msg-singular> <msg-plural> <n>)
[procedure] (dcngettext <domain> <msg-singular> <msg-plural> <n> <locale>)
These let you lookup messages in domains other than that specified by TEXTDOMAIN. This is just a clumsy way to make up for inadequacies in the traditional gettext design - if you want to work with multiple domains, you should use the cascaded domains described below.[procedure] (bindtextdomain <domain> <dirname>)
Override the directory to look for the given domain in.
A major inconvenience of gettext is that it's essentially a one message file per application design. Related applications, applications with the same menu entries, or same error messages, all of these need to have their own duplicated translations of all the same messages.
free-gettext, however, lets you specify a list of strings as the text domain, and for any message lookup these domains are searched in order.
(textdomain '("myapp" "gimp")) ; search 1st myapp, then gimp (gettext "/File/Close") ; "Close" from gimp unless overridden
You can thus share messages freely between applications, and effectively have collections of message dictionaries.
First-class Lookup Interface
One of the most common types of application to write these days is a web application, for which gettext is poorly suited. Gettext assumes a single locale, but for any kind of server the clients may each have their own locale. free-gettext therefore provides a way to generate separate first class gettext procedures.[procedure] (make-gettext domain [locale] [dirs] [cdir] [cached?] [lookup-cached?])
DOMAIN is the same as the first argument to TEXTDOMAIN, and may be similarly cascaded.
LOCALE is the locale, as a string or list of strings of the form LANG[_REGION][.ENCODING], and defaults to the LANG or LC_ALL environment variable, or C if neither is set. Multiple locales are also searched in order, which can be useful when you have incomplete translations in similar languages.
DIRS (again a string or list of strings) is the search path of directories which should hold the LOCALE/CDIR/ directories which contain the actual message catalogs. This is always appended with the system default, e.g. "/usr/share/locale", and may also inherit from the GETTEXT_PATH colon-delimited environment variable.
CDIR is the category directory, defaulting to either the LC_CATEGORY environment variable or the appropriate system default (e.g. LC_MESSAGES). You generally won't need to specify this.
CACHED? means to cache individual messages, and defaults to #t. This is a natural default (GNU gettext similarly caches messages), but during development it can be handy to disable caching if you intend to edit messages while coding.
LOOKUP-CACHED? means to cache the lookup dispatch generated by these parameters, and defaults to #t. Thus by default multiple calls to MAKE-GETTEXT with the same parameters return the same object, and they in turn share the same message cache.
MAKE-GETTEXT returns a dispatch closure with the following parameters:
- (<self> 'getter) - returns a gettext-style procedure
- (<self> 'ngetter) - returns an ngettext-style procedure
- (<self> 'setter) - returns a procedure for manually setting message translations
- (<self> 'get <message>) => ((<self> 'getter) <message>)
- (<self> 'nget <msg-singular> <msg-plural> <n>) => ((<self> 'ngetter) <msg-singular> <msg-plural> <n>)
- (<self> 'set! <message> <value>) => ((<self> 'set!) <message> <value>)
- (<self> 'locale)
- (<self> 'domain)
- (<self> 'dirs)
- (<self> 'files)
- (<self> 'use-cache <true-or-false>) - enable or disable message caching
- (<self> 'clear) - clears the cache
Note that TEXTDOMAIN actually accepts all of the same parameters as MAKE-GETTEXT, so that you can specify the locale and other settings manually if you want.
Quick Start Tutorial
- (use free-gettext)
- Replace any messages with (gettext <message>) or a shortcut.
- Generate the .po file with the command "xgettext myapp.scm".
- If you use a shortcut its "xgettext --keyword=_ myapp.scm".
- The -a parameter will extract _all_ strings.
- The default output is messages.po, but the -d<name> parameter overrides this.
- Make a "locale" subdirectory tree, and move the file to "./locale/en/LC_MESSAGES/myapp.po".
- When testing, "export GETTEXT_PATH=./locale/" so it can access the uninstalled files.
- Optionally, compile the .po with the "msgfmt" command.
- Write your .setup file to install the locale files in /usr/share/locale/.
Because you need some file format for your messages, and gettext is widely recognized and has extensive tool support.
- initial release