1 @title Internationalization
4 Describes Phabricator translation and localization.
9 Phabricator partially supports internationalization, but many of the tools
10 are missing or in a prototype state.
12 This document describes what tools exist today, how to add new translations,
13 and how to use the translation tools to make a codebase translatable.
19 To add a new locale, subclass @{class:PhutilLocale}. This allows you to
20 introduce a new locale, like "German" or "Klingon".
22 Once you've created a locale, applications can add translations for that
25 For instructions on adding new classes, see
26 @{article@phabcontrib:Adding New Classes}.
29 Adding Translations to Locale
30 =============================
32 To translate strings, subclass @{class:PhutilTranslation}. Translations need
33 to belong to a locale: the locale defines an available language, and each
34 translation subclass provides strings for it.
36 Translations are separated from locales so that third-party applications can
37 provide translations into different locales without needing to define those
40 For instructions on adding new classes, see
41 @{article@phabcontrib:Adding New Classes}.
44 Writing Translatable Code
45 =========================
47 Strings are marked for translation with @{function@libphutil:pht}.
49 The `pht()` function takes a string (and possibly some parameters) and returns
50 the translated version of that string in the current viewer's locale, if a
51 translation is available.
53 If text strings will ultimately be read by humans, they should essentially
54 always be wrapped in `pht()`. For example:
57 $dialog->appendParagraph(pht('This is an example.'));
60 This allows the code to return the correct Spanish or German or Russian
61 version of the text, if the viewer is using Phabricator in one of those
62 languages and a translation is available.
64 Using `pht()` properly so that strings are translatable can be tricky. Briefly,
67 - Only pass static strings as the first parameter to `pht()`.
68 - Use parameters to create strings containing user names, object names, etc.
69 - Translate full sentences, not sentence fragments.
70 - Let the translation framework handle plural rules.
71 - Use @{class@libphutil:PhutilNumber} for numbers.
72 - Let the translation framework handle subject gender rules.
73 - Translate all human-readable text, even exceptions and error messages.
75 See the next few sections for details on these rules.
81 The first parameter to `pht()` must always be a static string. Broadly, this
82 means it should not contain variables or function or method calls (it's OK to
83 split it across multiple lines and concatenate the parts together).
88 pht('The night is dark.');
90 'Two roads diverged in a yellow wood, '.
91 'and sorry I could not travel both '.
92 'and be one traveler, long I stood.');
96 These won't work (they might appear to work, but are wrong):
98 ```lang=php, counterexample
100 pht('The duck says, '.$quack);
104 The first argument must be a static string so it can be extracted by static
105 analysis tools and dumped in a big file for translators. If it contains
106 functions or variables, it can't be extracted, so translators won't be able to
109 Lint will warn you about problems with use of static strings in calls to
116 You can provide parameters to a translation string by using `sprintf()`-style
117 patterns in the input string. For example:
120 pht('%s earned an award.', $actor);
121 pht('%s closed %s.', $actor, $task);
124 This is primarily appropriate for usernames, object names, counts, and
125 untranslatable strings like URIs or instructions to run commands from the CLI.
127 Parameters normally should not be used to combine two pieces of translated
128 text: see the next section for guidance.
133 You should almost always pass the largest block of text to `pht()` that you
134 can. Particularly, it's important to pass complete sentences, not try to build
135 a translation by stringing together sentence fragments.
137 There are several reasons for this:
139 - It gives translators more context, so they can be more confident they are
140 producing a satisfying, natural-sounding translation which will make sense
141 and sound good to native speakers.
142 - In some languages, one fragment may need to translate differently depending
143 on what the other fragment says.
144 - In some languages, the most natural-sounding translation may change the
145 order of words in the sentence.
147 For example, suppose we want to translate these sentence to give the user some
148 instructions about how to use an interface:
150 > Turn the switch to the right.
152 > Turn the switch to the left.
154 > Turn the dial to the right.
156 > Turn the dial to the left.
158 Maybe we have a function like this:
161 function get_string($is_switch, $is_right) {
166 One way to write the function body would be like this:
168 ```lang=php, counterexample
169 $what = $is_switch ? pht('switch') : pht('dial');
170 $dir = $is_right ? pht('right') : pht('left');
172 return pht('Turn the ').$what.pht(' to the ').$dir.pht('.');
175 This will work fine in English, but won't work well in other languages.
177 One problem with doing this is handling gendered nouns. Languages like Spanish
178 have gendered nouns, where some nouns are "masculine" and others are
179 "feminine". The gender of a noun affects which article (in English, the word
180 "the" is an article) should be used with it.
182 In English, we say "**the** knob" and "**the** switch", but a Spanish speaker
183 would say "**la** perilla" and "**el** interruptor", because the noun for
184 "knob" in Spanish is feminine (so it is used with the article "la") while the
185 noun for "switch" is masculine (so it is used with the article "el").
187 A Spanish speaker can not translate the string "Turn the" correctly without
188 knowing which gender the noun has. Spanish has //two// translations for this
189 string ("Gira el", "Gira la"), and the form depends on which noun is being
192 Another problem is that this reduces flexibility. Translating fragments like
193 this locks translators into a specific word order, when rearranging the words
194 might make the sentence sound much more natural to a native speaker.
196 For example, if the string read "The knob, to the right, turn it.", it
197 would technically be English and most English readers would understand the
198 meaning, but no native English speaker would speak or write like this.
200 However, some languages have different subject-verb order rules or
201 colloquialisms, and a word order which transliterates like this may sound more
202 natural to a native speaker. By translating fragments instead of complete
203 sentences, you lock translators into English word order.
205 Finally, the last fragment is just a period. If a translator is presented with
206 this string in an interface without much context, they have no hope of guessing
207 how it is used in the software (it could be an end-of-sentence marker, or a
208 decimal point, or a date separator, or a currency separator, all of which have
209 very different translations in many locales). It will also conflict with all
210 other translations of the same string in the codebase, so even if they are
211 given context they can't translate it without technical problems.
213 To avoid these issues, provide complete sentences for translation. This almost
214 always takes the form of writing out alternatives in full. This is a good way
215 to implement the example function:
220 return pht('Turn the switch to the right.');
222 return pht('Turn the switch to the left.');
226 return pht('Turn the dial to the right.');
228 return pht('Turn the dial to the left.');
233 Although this is more verbose, translators can now get genders correct,
234 rearrange word order, and have far more context when translating. This enables
235 better, natural-sounding translations which are more satisfying to native
242 Different languages have various rules for plural nouns.
244 In English there are usually two plural noun forms: for one thing, and any
245 other number of things. For example, we say that one chair is a "chair" and any
246 other number of chairs are "chairs": "0 chairs", "1 chair", "2 chairs", etc.
248 In other languages, there are different (and, in some cases, more) plural
249 forms. For example, in Czech, there are separate forms for "one", "several",
252 Because plural noun rules depend on the language, you should not write code
253 which hard-codes English rules. For example, this won't translate well:
255 ```lang=php, counterexample
257 return pht('This will take an hour.');
259 return pht('This will take hours.');
263 This code is hard-coding the English rule for plural nouns. In languages like
264 Czech, the correct word for "hours" may be different if the count is 2 or 15,
265 but a translator won't be able to provide the correct translation if the string
266 is written like this.
268 Instead, pass a generic string to the translation engine which //includes// the
269 number of objects, and let it handle plural nouns. This is the correct way to
270 write the translation:
273 return pht('This will take %s hour(s).', new PhutilNumber($count));
276 If you now load the web UI, you'll see "hour(s)" literally in the UI. To fix
277 this so the translation sounds better in English, provide translations for this
278 string in the @{class@phabricator:PhabricatorUSEnglishTranslation} file:
281 'This will take %s hour(s).' => array(
282 'This will take an hour.',
283 'This will take hours.',
287 The string will then sound natural in English, but non-English translators will
288 also be able to produce a natural translation.
290 Note that the translations don't actually include the number in this case. The
291 number is being passed from the code, but that just lets the translation engine
292 get the rules right: the number does not need to appear in the final
293 translations shown to the user.
298 When translating numbers, you should almost always use `%s` and wrap the count
299 or number in `new PhutilNumber($count)`. For example:
302 pht('You have %s experience point(s).', new PhutilNumber($xp));
305 This will let the translation engine handle plural noun rules correctly, and
306 also format large numbers correctly in a locale-aware way with proper unit and
307 decimal separators (for example, `1000000` may be printed as "1,000,000",
308 with commas for readability).
310 The exception to this rule is IDs which should not be written with unit
311 separators. For example, this is correct for an object ID:
314 pht('This diff has ID %d.', $diff->getID());
320 Different languages also use different words for talking about subjects who are
321 male, female or have an unknown gender. In English this is mostly just
322 pronouns (like "he" and "she") but there are more complex rules in other
323 languages, and languages like Czech also require verb agreement.
325 When a parameter refers to a gendered person, pass an object which implements
326 @{interface@libphutil:PhutilPerson} to `pht()` so translators can provide
327 gendered translation variants.
330 pht('%s wrote', $actor);
333 Translators will create these translations:
336 // English translation
340 array('%s napsal', '%s napsala');
343 (You usually don't need to worry very much about this rule, it is difficult to
344 get wrong in standard code.)
347 Exceptions and Errors
348 =====================
350 You should translate all human-readable text, even exceptions and error
351 messages. This is primarily a rule of convenience which is straightforward
352 and easy to follow, not a technical rule.
354 Some exceptions and error messages don't //technically// need to be translated,
355 as they will never be shown to a user, but many exceptions and error messages
356 are (or will become) user-facing on some way. When writing a message, there is
357 often no clear and objective way to determine which type of message you are
358 writing. Rather than try to distinguish which are which, we simply translate
359 all human-readable text. This rule is unambiguous and easy to follow.
361 In cases where similar error or exception text is often repeated, it is
362 probably appropriate to define an exception for that category of error rather
363 than write the text out repeatedly, anyway. Two examples are
364 @{class@libphutil:PhutilInvalidStateException} and
365 @{class@libphutil:PhutilMethodNotImplementedException}, which mostly exist to
366 produce a consistent message about a common error state in a convenient way.
368 There are a handful of error strings in the codebase which may be used before
369 the translation framework is loaded, or may be used during handling other
370 errors, possibly raised from within the translation framework. This handful
371 of special cases are left untranslated to prevent fatals and cycles in the
380 - adding a new locale or translation file with
381 @{article@phabcontrib:Adding New Classes}.