@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

at recaptime-dev/main 381 lines 13 kB view raw
1@title Internationalization 2@group developer 3 4Describes Phorge translation and localization. 5 6Overview 7======== 8 9Phorge partially supports internationalization, but many of the tools 10are missing or in a prototype state. 11 12This document describes what tools exist today, how to add new translations, 13and how to use the translation tools to make a codebase translatable. 14 15 16Adding a New Locale 17=================== 18 19To add a new locale, subclass @{class:PhutilLocale}. This allows you to 20introduce a new locale, like "German" or "Klingon". 21 22Once you've created a locale, applications can add translations for that 23locale. 24 25For instructions on adding new classes, see 26@{article@contrib:Adding New Classes}. 27 28 29Adding Translations to Locale 30============================= 31 32To translate strings, subclass @{class:PhutilTranslation}. Translations need 33to belong to a locale: the locale defines an available language, and each 34translation subclass provides strings for it. 35 36Translations are separated from locales so that third-party applications can 37provide translations into different locales without needing to define those 38locales themselves. 39 40For instructions on adding new classes, see 41@{article@contrib:Adding New Classes}. 42 43 44Writing Translatable Code 45========================= 46 47Strings are marked for translation with @{function@arcanist:pht}. 48 49The `pht()` function takes a string (and possibly some parameters) and returns 50the translated version of that string in the current viewer's locale, if a 51translation is available. 52 53If text strings will ultimately be read by humans, they should essentially 54always be wrapped in `pht()`. For example: 55 56```lang=php 57$dialog->appendParagraph(pht('This is an example.')); 58``` 59 60This allows the code to return the correct Spanish or German or Russian 61version of the text, if the viewer is using Phorge in one of those 62languages and a translation is available. 63 64Using `pht()` properly so that strings are translatable can be tricky. Briefly, 65the major rules are: 66 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@arcanist:PhutilNumber} for numbers. 72 - Let the translation framework handle subject gender rules. 73 - Translate all human-readable text, even exceptions and error messages. 74 75See the next few sections for details on these rules. 76 77 78Use Static Strings 79================== 80 81The first parameter to `pht()` must always be a static string. Broadly, this 82means it should not contain variables or function or method calls (it's OK to 83split it across multiple lines and concatenate the parts together). 84 85These are good: 86 87```lang=php 88pht('The night is dark.'); 89pht( 90 'Two roads diverged in a yellow wood, '. 91 'and sorry I could not travel both '. 92 'and be one traveler, long I stood.'); 93 94``` 95 96These won't work (they might appear to work, but are wrong): 97 98```lang=php, counterexample 99pht(some_function()); 100pht('The duck says, '.$quack); 101pht($string); 102``` 103 104The first argument must be a static string so it can be extracted by static 105analysis tools and dumped in a big file for translators. If it contains 106functions or variables, it can't be extracted, so translators won't be able to 107translate it. 108 109Lint will warn you about problems with use of static strings in calls to 110`pht()`. 111 112 113Parameters 114========== 115 116You can provide parameters to a translation string by using `sprintf()`-style 117patterns in the input string. For example: 118 119```lang=php 120pht('%s earned an award.', $actor); 121pht('%s closed %s.', $actor, $task); 122``` 123 124This is primarily appropriate for usernames, object names, counts, and 125untranslatable strings like URIs or instructions to run commands from the CLI. 126 127Parameters normally should not be used to combine two pieces of translated 128text: see the next section for guidance. 129 130Sentence Fragments 131================== 132 133You should almost always pass the largest block of text to `pht()` that you 134can. Particularly, it's important to pass complete sentences, not try to build 135a translation by stringing together sentence fragments. 136 137There are several reasons for this: 138 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. 146 147For example, suppose we want to translate these sentence to give the user some 148instructions about how to use an interface: 149 150> Turn the switch to the right. 151 152> Turn the switch to the left. 153 154> Turn the dial to the right. 155 156> Turn the dial to the left. 157 158Maybe we have a function like this: 159 160``` 161function get_string($is_switch, $is_right) { 162 // ... 163} 164``` 165 166One way to write the function body would be like this: 167 168```lang=php, counterexample 169$what = $is_switch ? pht('switch') : pht('dial'); 170$dir = $is_right ? pht('right') : pht('left'); 171 172return pht('Turn the ').$what.pht(' to the ').$dir.pht('.'); 173``` 174 175This will work fine in English, but won't work well in other languages. 176 177One problem with doing this is handling gendered nouns. Languages like Spanish 178have 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. 181 182In English, we say "**the** knob" and "**the** switch", but a Spanish speaker 183would 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 185noun for "switch" is masculine (so it is used with the article "el"). 186 187A Spanish speaker can not translate the string "Turn the" correctly without 188knowing which gender the noun has. Spanish has //two// translations for this 189string ("Gira el", "Gira la"), and the form depends on which noun is being 190used. 191 192Another problem is that this reduces flexibility. Translating fragments like 193this locks translators into a specific word order, when rearranging the words 194might make the sentence sound much more natural to a native speaker. 195 196For example, if the string read "The knob, to the right, turn it.", it 197would technically be English and most English readers would understand the 198meaning, but no native English speaker would speak or write like this. 199 200However, some languages have different subject-verb order rules or 201colloquialisms, and a word order which transliterates like this may sound more 202natural to a native speaker. By translating fragments instead of complete 203sentences, you lock translators into English word order. 204 205Finally, the last fragment is just a period. If a translator is presented with 206this string in an interface without much context, they have no hope of guessing 207how it is used in the software (it could be an end-of-sentence marker, or a 208decimal point, or a date separator, or a currency separator, all of which have 209very different translations in many locales). It will also conflict with all 210other translations of the same string in the codebase, so even if they are 211given context they can't translate it without technical problems. 212 213To avoid these issues, provide complete sentences for translation. This almost 214always takes the form of writing out alternatives in full. This is a good way 215to implement the example function: 216 217```lang=php 218if ($is_switch) { 219 if ($is_right) { 220 return pht('Turn the switch to the right.'); 221 } else { 222 return pht('Turn the switch to the left.'); 223 } 224} else { 225 if ($is_right) { 226 return pht('Turn the dial to the right.'); 227 } else { 228 return pht('Turn the dial to the left.'); 229 } 230} 231``` 232 233Although this is more verbose, translators can now get genders correct, 234rearrange word order, and have far more context when translating. This enables 235better, natural-sounding translations which are more satisfying to native 236speakers. 237 238 239Singular and Plural 240=================== 241 242Different languages have various rules for plural nouns. 243 244In English there are usually two plural noun forms: for one thing, and any 245other number of things. For example, we say that one chair is a "chair" and any 246other number of chairs are "chairs": "0 chairs", "1 chair", "2 chairs", etc. 247 248In other languages, there are different (and, in some cases, more) plural 249forms. For example, in Czech, there are separate forms for "one", "several", 250and "many". 251 252Because plural noun rules depend on the language, you should not write code 253which hard-codes English rules. For example, this won't translate well: 254 255```lang=php, counterexample 256if ($count == 1) { 257 return pht('This will take an hour.'); 258} else { 259 return pht('This will take hours.'); 260} 261``` 262 263This code is hard-coding the English rule for plural nouns. In languages like 264Czech, the correct word for "hours" may be different if the count is 2 or 15, 265but a translator won't be able to provide the correct translation if the string 266is written like this. 267 268Instead, pass a generic string to the translation engine which //includes// the 269number of objects, and let it handle plural nouns. This is the correct way to 270write the translation: 271 272```lang=php 273return pht('This will take %s hour(s).', new PhutilNumber($count)); 274``` 275 276If you now load the web UI, you'll see "hour(s)" literally in the UI. To fix 277this so the translation sounds better in English, provide translations for this 278string in the @{class:PhabricatorUSEnglishTranslation} file: 279 280```lang=php 281'This will take %s hour(s).' => array( 282 'This will take an hour.', 283 'This will take hours.', 284), 285``` 286 287The string will then sound natural in English, but non-English translators will 288also be able to produce a natural translation. 289 290Note that the translations don't actually include the number in this case. The 291number is being passed from the code, but that just lets the translation engine 292get the rules right: the number does not need to appear in the final 293translations shown to the user. 294 295Using PhutilNumber 296================== 297 298When translating numbers, you should almost always use `%s` and wrap the count 299or number in `new PhutilNumber($count)`. For example: 300 301```lang=php 302pht('You have %s experience point(s).', new PhutilNumber($xp)); 303``` 304 305This will let the translation engine handle plural noun rules correctly, and 306also format large numbers correctly in a locale-aware way with proper unit and 307decimal separators (for example, `1000000` may be printed as "1,000,000", 308with commas for readability). 309 310The exception to this rule is IDs which should not be written with unit 311separators. For example, this is correct for an object ID: 312 313```lang=php 314pht('This diff has ID %d.', $diff->getID()); 315``` 316 317Male and Female 318=============== 319 320Different languages also use different words for talking about subjects who are 321male, female or have an unknown gender. In English this is mostly just 322pronouns (like "he" and "she") but there are more complex rules in other 323languages, and languages like Czech also require verb agreement. 324 325When a parameter refers to a gendered person, pass an object which implements 326@{interface@arcanist:PhutilPerson} to `pht()` so translators can provide 327gendered translation variants. 328 329```lang=php 330pht('%s wrote', $actor); 331``` 332 333Translators will create these translations: 334 335```lang=php 336// English translation 337'%s wrote'; 338 339// Czech translation 340array('%s napsal', '%s napsala'); 341``` 342 343(You usually don't need to worry very much about this rule, it is difficult to 344get wrong in standard code.) 345 346 347Exceptions and Errors 348===================== 349 350You should translate all human-readable text, even exceptions and error 351messages. This is primarily a rule of convenience which is straightforward 352and easy to follow, not a technical rule. 353 354Some exceptions and error messages don't //technically// need to be translated, 355as they will never be shown to a user, but many exceptions and error messages 356are (or will become) user-facing on some way. When writing a message, there is 357often no clear and objective way to determine which type of message you are 358writing. Rather than try to distinguish which are which, we simply translate 359all human-readable text. This rule is unambiguous and easy to follow. 360 361In cases where similar error or exception text is often repeated, it is 362probably appropriate to define an exception for that category of error rather 363than write the text out repeatedly, anyway. Two examples are 364@{class@arcanist:PhutilInvalidStateException} and 365@{class@arcanist:PhutilMethodNotImplementedException}, which mostly exist to 366produce a consistent message about a common error state in a convenient way. 367 368There are a handful of error strings in the codebase which may be used before 369the translation framework is loaded, or may be used during handling other 370errors, possibly raised from within the translation framework. This handful 371of special cases are left untranslated to prevent fatals and cycles in the 372error handler. 373 374 375Next Steps 376========== 377 378Continue by: 379 380 - adding a new locale or translation file with 381 @{article@contrib:Adding New Classes}.