Day 18 - I18N
Catalyst I18N and L10N using Catalyst::Plugin::I18N
Introduction
The task is fairly simple:
- * Allow the user to specify a language of choice,
- * Replace all messages with function calls to a translation module,
- * Write translations for every language you want.
The last part (L10N) should be very simple (using a graphical PO editor is preferred) in order to allow non-technical people to localise your software.
Setting the URLs , using chained actions
For this exercise, let's just make our app available to both English and Romanian viewers.
http://localhost:3000/en will greet English speakers, while http://localhost:3000/ro will greet Romanian speakers.
A nice way to setup URLs like that is using Catalyst::DispatchType::Chained (see day 10):
-=cut +sub language : PathPart('') Chained('/') CaptureArgs(1) { + my ($self , $c, $language ) =@_; +} -sub index : Private { +sub cheer : PathPart('') Chained('language') Args(0) {
If you run the development server with debug enabled, you should see the chain:
[debug] Loaded Chained actions: .-------------------------------------+--------------------------------------. | Path Spec | Private | +-------------------------------------+--------------------------------------+ | /* | /language (1) | | | => /cheer | '-------------------------------------+--------------------------------------'
Setting up I18N
First step, make sure you have Catalyst::Plugin::I18N installed and add it to your plugin list:
-use Catalyst qw/-Debug ConfigLoader Static::Simple/; +use Catalyst qw/ConfigLoader Static::Simple I18N/;
Then let Catalyst know what is your preffered language is:
sub language : PathPart('') Chained('/') CaptureArgs(1) { my ($self , $c, $language ) =@_; $c->languages( [ $language ] ); }
OK, that's it . Let's internationalise the messages now.
I18N with $c->loc
Just replace your messages with calls to $c->loc. Replace your interpolated variables with [_1] , [_2] etc. The patch below illustrates:
--- hi_there.tt +++ hi_there.tt @@ -1,17 +1,17 @@ -<html><head><title>Happy winter solstice</title></head> +<html><head><title>[% c.loc("Happy winter solstice") %]</title></head> <body> -[% IF c.stash.days_till_xmas > 0 %] -And best regards for the new year! There are [% c.stash.days_till_xmas %] -days left until santa comes. -[% END %] +[% IF c.stash.days_till_xmas > 0 ; +c.loc("And best regards for the new year! There are [_1] +days left until santa comes", c.stash.days_till_xmas ); +END %] -[% IF c.stash.days_till_xmas == 0 %] -It's already Christmas, go check your presents! -[% END %] +[% IF c.stash.days_till_xmas == 0 ; +c.loc("It's already Christmas, go check your presents!"); +END %] -[% IF c.stash.days_till_xmas < 0 %] -You just missed it, but there's one next year too! -[% END %] +[% IF c.stash.days_till_xmas < 0 ; +c.loc("You just missed it, but there's one next year too!"); +END %] </body> </html>
Everything should still be working, but now we are ready to localise it.
L10N with a little help from xgettext.pl
xgettext.pl comes with the Locale::Maketext::Lexicon distribution. You just pass it a list of files and it collects all localised strings and dumps them in a po file.
Catalyst::Plugin::I18N expects to find its messages in lib/Cheer/I18N/LANG.po, so here's the command:
mkdir lib/Cheer/I18N /path/to/xgettext.pl -o lib/Cheer/I18N/ro.po root/hi_there.tt
The file is generated:
# SOME DESCRIPTIVE TITLE. # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER # This file is distributed under the same license as the PACKAGE package. # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR. # #, fuzzy msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "POT-Creation-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=CHARSET\n" "Content-Transfer-Encoding: 8bit\n" #: root/hi_there.tt:4 #. (c.stash.days_till_xmas) msgid "" "And best regards for the new year! There are %1 \n" "days left until santa comes" msgstr "" #: root/hi_there.tt:1 msgid "Happy winter solstice" msgstr "" #: root/hi_there.tt:9 msgid "It's already Christmas, go check your presents!" msgstr "" #: root/hi_there.tt:13 msgid "You just missed it, but there's one next year too!" msgstr ""
Of course, in a real project you have tons of files. Here's what I do:
(find root/ -name '*.tt' ; find lib/MyApp/Controller -name '*.pm' )| xargs /path/to/xgettext.pl -o lib/MyApp/I18N/ro.po
Let's just translate the messages and we're done:
--- ro.po +++ ro.po @@ -12,7 +12,7 @@ "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Language-Team: LANGUAGE <LL@li.org>\n" "MIME-Version: 1.0\n" -"Content-Type: text/plain; charset=CHARSET\n" +"Content-Type: text/plain; charset=utf-8\n" "Content-Transfer-Encoding: 8bit\n" #: root/hi_there.tt:4 @@ -20,16 +20,16 @@ msgid "" "And best regards for the new year! There are %1 \n" "days left until santa comes" -msgstr "" +msgstr "Si La Multi Ani! Mai sunt %1\n zile pana vine Mos Craciun" #: root/hi_there.tt:1 msgid "Happy winter solstice" -msgstr "" +msgstr "Craciun Fericit!" #: root/hi_there.tt:9 msgid "It's already Christmas, go check your presents!" -msgstr "" +msgstr "E deja Craciun, desfaceti cadourile!" #: root/hi_there.tt:13 msgid "You just missed it, but there's one next year too!" -msgstr "" +msgstr "Craciunul a trecut, dar mai e unul anul viitor!"
Restart your server, http://localhost:3000/ro should give you the localised version. Craciun fericit :)
AUTHOR
Bogdan Lucaciu bogdan@sns.ro
COPYRIGHT
Copyright 2006 System & Network Solutions - http://www.sns.ro
This document can be freely redistributed and can be modified and re-distributed under the same conditions as Perl itself.