1 FusionForge now uses GNU gettext for its internationalisation system,
2 instead of the previous home-grown system (the *.tab files).
7 To display a translatable string, the source files need to use the _()
8 function, which is an alias for gettext(). Its one parameter is the
9 translatable string itself, in English.
11 The gettext tools can then extract from the source files a list of
12 all translatable strings, and store them in a POT file
13 (translations/gforge.pot). POT stands for PO Template, because this
14 file can be used to generate PO files.
16 The PO files (PO standing for Portable Object) are stored in
17 translations/<ll>.po, <ll> being the locale name for the language.
18 There's fr.po for French, de.po for German, and pt_BR.po for
19 Portuguese as spoken in Brazil. They contain, for each translatable
20 string, a translated string in the appropriate language.
22 The PO files are then turned into MO (Machine Object) files, which
23 contain the same information, only in a binary format not meant for
24 human editing. These MO files are installed onto the server where
25 FusionForge runs, and gettext finds and uses them at runtime to convert
26 strings from English to another language -- if a string is missing in
27 the PO file, the English string is displayed.
29 So, the initial workflow is:
32 -> gforge.pot -> *.po -> *.mo
35 Now what happens when the source files are modified, new strings are
36 added, and existing ones are modified? Well, the gforge.pot file
37 needs to be updated to reflect the new "catalog" of translatable
38 strings. The *.po files also need to be updated. You'd think changed
39 strings would be lost, but the gettext tools do a nifty trick: since
40 both the gforge.pot and the *.po files contain comments that reference
41 where in the code a translatable string is used, gettext can infer
42 that some old translation probably still applies to the new string,
43 but it should be checked by a human. The string is then marked as
44 "fuzzy" in the *.po files, which makes it easy to find, check for
45 changes in meaning, and maybe update.
47 The "update" workflow is therefore:
51 *.class.php -/ -> new *.po -> new *.mo
54 How to use it when coding -- basic
55 ----------------------------------
57 To simply display a translated string, just use the _() function on a
58 hardcoded string. For instance, to display a welcome message, you'd
61 | echo _('Welcome to FusionForge!') ;
64 If you need to use a parameter, your best bet is to use printf
65 formats. Your translatable string is the format, and you then use
66 that format with sprintf(). For instance, for a more personalised
69 | echo sprintf(_('Hello, %s!'), $user_name) ;
71 ...or, since you're going to print that formatted string anyway:
73 | printf(_('Hello, %s!'), $user_name) ;
76 Sometimes you need several parameters. No problem, printf() and
77 friends can handle that:
79 | printf(_('Good morning, %s. Today is %s.'), $user_name, $weekday) ;
82 Sometimes you even need to reorder parameters, or use some of them
83 more than once; not to worry, you can also use positional
86 | printf(_('Good morning, Mr %1$s. Today is %22s.
87 | How are you feeling today, Mr %1$s?'), $last_name, $weekday) ;
90 Of course, the translated strings will need to contain the
91 appropriate placeholders too.
93 Gotcha #1 -- Plural forms
94 -------------------------
96 Gettext was designed with internationalisation in mind, by people who
97 thought of several things that might not be obvious to all of us.
98 They are worth keeping in mind.
100 One of those is the fact that not every language has the same idea
101 of what is a plural, and what numbers should use what plural form.
102 Some languages have no concept of plural forms, some have four forms
103 depending on the number you're talking about, and even French and
104 English (which only have two forms) disagree on what form to use for
105 zero (plural in English, singular in French). That area is handled by
106 the library's ngettext() function, which takes three parameters: a
107 singular (English) string, a plural (English) string, and a number.
108 Depending on the number, the appropriate result will be yielded at
109 run-time. For instance, to get a proper translation for bread
110 loaf/loaves depending on how many there are, you'd use
112 | ngettext('bread loaf', 'bread loaves', $n);
115 Of course, that doesn't actually print the number. So you usually
116 use that in conjunction with a *printf() call:
118 | printf(ngettext('There is %d bread loaf', 'There are %d bread loaves',
123 Note $n is there twice: the innermost occurrence helps gettext
124 choose what form to use, the outermost is used by printf to actually
125 put the number in there. If that code were called for values of $n in
126 {0,1,2}, you'd see the following result in English:
128 | There are 0 bread loaves
129 | There is 1 bread loaf
130 | There are 2 bread loaves
132 And, since French considers that zero of something is not a plural
133 number of that thing, the same program would yield the following
140 (I have no idea about Slovenian, but the documentation tells me it
141 has four different forms -- the good point is, only the Slovenian
142 translator needs to know about that, and the coder can happily ignore
143 it, since gettext will do the right thing at runtime.)
145 In summary: try to avoid testing for plurality in PHP (with
146 if-blocks), since your tests will only work in a select handful of
147 languages; use ngettext() instead, leave the rest to translators.
149 Gotcha #2 -- "domains"
150 ----------------------
152 At run-time, the program needs to know where to find the *.mo files.
153 Since different parts of a program could make use of several *.mo
154 files at the same time, gettext needs to know about them. So it uses
155 the concept of "domains".
157 Basically, a domain is a namespace for translatable strings. The
158 beginning of the program needs to tell gettext to use such-and-such
159 domain, whose *.mo files can be found in such-and-such location of the
160 filesystem. The very next instruction is usually a declaration that
161 one domain is to be used as default -- in FusionForge, that domain is
162 "gforge", and it corresponds to the various gforge.mo files, which
163 will usually be /usr/share/locale/*/LC_MESSAGES/gforge.mo. Strings
164 from the default domain can be translated by the _() function (or the
165 gettext() function, same thing).
167 If you want to access strings from another domain (maybe to re-use
168 translations from a library, or because your FusionForge plugin has
169 its own strings), you need to specify what domain when invoking
170 gettext, by using the dgettext() function. It takes an extra
171 parameter before the string to be translated, which is the domain
172 name. One could thus envision the following code:
174 | echo dgettext('gforge-plugin-superduper',
175 | 'Thank you for using the SuperDuper FusionForge plugin.') ;
178 (There's also a dngettext() function, for those times when you need
179 both explicit domain and plural form handling.)
181 How to regenerate stuff
182 -----------------------
184 The trunk/tools/update-gettext-files.sh script will update the strings
185 catalog (trunk/gforge/translations/gforge.pot) and the translation
186 files (trunk/gforge/translations/*.po). It should be run after new
187 strings have been introduced, or old strings have been changed --
188 although not too often, since it does generate a large diff (think of
189 all the line numbers that have been modified). You can then commit
190 the newly updated trunk/gforge/translations/* files.
192 When a translation file has been updated by a translator, the *.mo
193 files need to be regenerated. The trunk/tools/update-gettext-files.sh
194 script takes care of that, and prepares new
195 trunk/gforge/locales/<ll>/LC_MESSAGES/gforge.mo from the corresponding
196 trunk/gforge/translations/<ll>.po files.
198 How to edit translation files
199 -----------------------------
201 The PO format is designed to be usable by computer programs. That
202 means several helper applications exist to provide interfaces for
203 translators and make their life easier than if they had to edit text
204 files by hand. (The fact that these applications also ensures that
205 the files are always well-formed provides the added convenience of
206 making the developers' life easier, and nobody complains about that.)
207 Translators therefore usually use these programs, which allow them to
208 easily navigate through the *.po files, fix strings, look for
209 untranslated strings or strings that need review (fuzzy strings), and
212 Notable examples of these applications include POedit (a Gtk-based
213 app), Kbabel (this ones is Qt), and Pootle (web-based). Emacs
214 aficionados will, no doubt, use their favourite editor's PO-mode.
216 Note that the Debian internationalisation team have been kind enough
217 to host FusionForge translations on their Pootle instance. It is
218 therefore recommended that translators register an account on
219 https://pootle.debian.net/ and use it for their work. Pootle has a
220 web-based interface, but it also allows translators to download and
221 upload the *.po files for those who prefer working locally with the