Woo! xcowsay is now finished! Download it here.
Please add comments, bug reports, suggestions, whatever.
UPDATE: Now on freshmeat!
May 23rd, 2008
May 23rd, 2008
I’ve recently struggled with GNU gettext for internationalizing some of my programs. This, for the benefit of other people who, like me, can’t be bovered to read the documentation, is the method I’ve used successfully:
I’m assuming you have a standard GNU autoconf/automake setup. If you don’t, there’s a lot of manual work to do. I’ve no idea how to do that: better read the manual instead.
There’s a handy tool
gettextize which handles a lot of the monkey work for you. Run it in the top-level project directory:
This will create a bunch of files (in the
po directories), make a few changes to your
Makefile.am (backups are created with a ~ suffix) and also print some instructions. Let’s follow them.
The first thing to do is add
configure.ac in order to cause autoconfiguration
to look for an external libintl (apparently).
po/Makevars and open it up. You’ll probably want to change the
MSGID_BUGS_ADDRESS to something relevant. I usually add
XGETTEXT_OPTIONS because I think the default
_ looks pretty ugly.
Now you need to add every file that contains translatable strings to
po/POTFILES.in. You can’t use wildcards or directory names — yuk! Something like this should do the trick:
$ find src -name '*.[ch]pp' > po/POTFILES.in
Now it’s time to regenerate the
configure script with the gettext macros:
$ aclocal -I m4 $ autoconf
If you try to run
configure now, it will complain about a missing
automake can add this for us:
$ automake --add-missing configure.ac:10: installing `./config.guess' configure.ac:10: installing `./config.sub'
configure in your build directory. You should see some additional NLS messages.
Next we mark some strings as translatable. Make sure you include
libintl.h in every source file with translatable strings. Surround the translatable strings with whatever you passed as a
_ is default, but I prefer
i18n). These macros aren’t defined by default, and they need to be aliases to
gettext. I usually make a header file
i18n.h like this:
#ifndef INC_18N_H #define INC_18N_H #include <locale.h> #include <libintl.h> // Macros which xgettext extracts translatable strings from #define i18n(s) gettext(s) #endif
i18ns around your translatable strings like this:
Before any call to
gettext you need to execute the following setup code or it won’t work (assuming you’ve included
setlocale(LC_ALL, ""); bindtextdomain(PACKAGE, LOCALEDIR); textdomain(PACKAGE);
LOCALEDIR is passed to the compiler from my
Makefile.am where I have something like:
localedir = $(datadir)/locale DEFS = -DLOCALEDIR=\"$(localedir)\" @DEFS@
Now we need to (re-)generate the master
package.pot file which contains all the translatable strings (where `package’ is the name of your package). We can do this with
make dist in the build directory, but the following command will update the
.pot and nothing else:
$ make -C po package.pot-update
Again, substitute `package’ for whatever your package name is. Notice that even if you run this in the build tree it will update the file in the source tree (ewww!!).
Finally, we can actually translate something. In the
po directory of the source tree run:
$ msginit -l de_DE -i package.pot
de_DE with whatever locale you wish to translate into. It’ll ask you for your email address and create a new
de_DE.po file. Open it up and you’ll see a bunch of lines with
msgstr pairs. The
msgid is the English (or whatever language you’re translating from) and the
msgstr line is the language you’re translating into. So for our example, we change it to:
msgid "Hello world" msgstr "Hallo Welt"
.gmo files rather than
.po files that get distributed and installed. So how do we generate them? This is the bit that really confused me, and the documentation was not helpful. In the end I looked in the makefile source to see how it works. You need to create a file
po/LINGUAS listing the available locales, one per line. Like this:
# List of available locales de_DE en_GB
There might be an easier way to do this, however. Now run
configure to regenerate your makefiles and run:
$ make dist
.gmo files for each locale should be generated in the
po directory of the source tree.
Now if we install the package with
make install the translations should be copied to
$PREFIX/share/locale (or whatever your
LOCALEDIR was). To test it out you can run your program with a non-default
LANG environment variable — it should automagically select the correct set of strings, if a translation exists.
For example, to test our German translation we would set
de_DE.UTF-8. You might have problems if your C library is not set up to support this locale (
dpkg-reconfigure locales on Debian).
Finally, whenever you make a change to the translatable strings in your program, run the following to update the
$ make -C po package.pot-update
Now in the
po directory of the source tree, merge the changes into each translation with the following command:
$ msgmerge -U en_GB.po package.pot
May 17th, 2008
So… let’s suppose you’re hacking away in Ruby and you mistype a method name:
irb(main):001:0> 5.clas NoMethodError: undefined method `clas' for 5:Fixnum from (irb):1
Obviously I *meant* to type ‘class’, but I got the arity (0) right and the spelling wrong.
I propose an extension to Ruby based on the hypothesis that typos are more common than arity errors: if I call an undefined method with arity A on an object then I probably meant to call *one* of the object’s methods of arity A. Just printing an error message is pretty doof since its certainly not what you wanted to happen. A statistically much better solution is to pick one method of the correct arity at random and execute that — it’s going to be right *some* of the time!
How would we do this in Ruby? After little bit of experimentation I produced this:
class Object def method_missing(name, *args) choices = self.class.instance_methods.collect do |name| self.method name end right_arity = choices.select do |method| method.arity == args.length end which = right_arity[rand(right_arity.length)] which.call(*args) end end
Just include this in your program and *all* Ruby objects will have this amazing feature!!!!
irb(main):017:0> 4.asdsa nil irb(main):018:0> 4.asdsa 4 irb(main):019:0> 4.asdsa Fixnum irb(main):020:0> 4.asdsa -5 irb(main):023:0> 4.asdsa(7) 11
:D :D :D