Platform Internationalization Best Practices
Here are some rough guidelines and lessons learned by the internationalization team at Facebook. These are in no specific order of importance.
Be Descriptive
A word like "Poke" can vary if it is used as a noun or a verb. FBT identifies a phrase by a hash of its text and description. That means that even a slight change to the original text or description will cause your string to be counted as a completely new one, which has to be translated again. With the eloquent driver, the old phrase and its translations stay in the database, but they aren't used anymore. So err on the side of starting off with a complete description you won't have to clarify later.
Note that translators often translate strings in a list or in a translation tool, so they will not see your text in the context of your application -- that means your descriptions need to give translators all the information they need to make correct translation decisions.
For example, do this:
fbt('Name:', 'Label for name of photo album')
Instead of this:
fbt('Name:', '')
// or
fbt('Name:', 'Name')
In some languages, the word for name is different depending on whether it's the name of a person, a place, or an object. A description here allows a translator to choose the correct word for this label.
Descriptions should usually indicate context as well as meaning (but see the next point). This is especially important for things like link text that are presented as part of a larger grammatical structure like a sentence.
So do this:
fbt(
fbt::param('name', $name) . "'s photos",
'Link to the photos of a person, in: "Jana\'s photos are ready to view."',
)
But not this:
fbt(fbt::param('name', $name) . "'s photos", '')
In languages where nouns change depending on whether they're used as the subject or object of a sentence, this description will allow translators to use the correct form.
Reuse Common Elements
You should reuse common text and descriptions rather than repeating the same text over and over with different descriptions; it's less work for translators and will tend to result in higher-quality translations. This is sometimes slightly at odds with using specific descriptions; use your judgment about where to draw the line.
So this:
fbt('Cancel', 'Button/link: cancel an action')
Is usually better than this:
fbt('Cancel', 'Button label: cancel sending a message to event owner')
Strings used all over your application can be defined once as common strings (fbt::c('Cancel')).
Avoid Translating Markup
If you have two sentences and a <br /> in between, split them up into two translatable phrases. Otherwise translators will be able to mess with your markup and the results may not be what you expect.
However, if you really want to use a <br />, we recommend doing it this way:
<fbt desc="Address of the event, one part per line">
first line<fbt:param name="lineBreak"><br /></fbt:param>
second line<fbt:same-param name="lineBreak" />
last line.
</fbt>
Use CSS instead of Markup
Use CSS rather than markup to confine text to particular parts of the page. (See also the next item.) For example, if you have the text "Next Page" and you want each word on a separate line, put it in a box with a maximum width rather than putting a tag in between the two words. Don't split the text into separately translatable units since it will prevent translators from changing word order if needed.
Don't do this:
fbt('Next<br/>Page', 'Pagination: link to the next page')
Because a translator may ignore your formatting.
And don't do this:
fbt('Next', 'Pagination: first word of "Next Page"') . '<br/>' . fbt('Page', 'Pagination: second word of "Next Page"');
If a language needs the word for "Page" to come before the word for "Next", it is impossible to translate correctly.
Rather, do this:
<div class="limited-width-box">@fbt('Next Page', 'Pagination: link to the next page')</div>
With appropriate CSS, the browser will word-wrap the string appropriately.
Avoid Layouts Relying on Precise Sizing
Try not to use layouts that depend on the precise onscreen sizes of pieces of text in the original language. For any piece of text, in some languages it is likely to be shorter and in some it will be longer (sometimes significantly so in either direction.) If you have sized your user interface elements such that your text just barely fits, your application will probably not work well in a language with longer words.
Avoid Long Pieces of Text
Large chunks of text like multiple paragraphs should be split up among multiple <fbt> tags for ease of translation. Similarly, a single long paragraph should be broken up into several smaller paragraphs. This allows reviewers of translations to more precisely pinpoint problems.
Assume Word Order Will Change
Assume that a translator will have to change the word order of every sentence. In particular, don't try to assemble sentences from smaller separately-translatable fragments, because even if you provide excellent descriptions, it's likely you will make it impossible for a translator to come up with a grammatically correct translation. Instead, expand all the possible cases out into separate translatable sentences and choose a complete sentence in your code.
Here's a simple example to avoid:
<fbt desc="Beginning of a sentence about where the viewer is eating">You are eating</fbt> <fbt desc="Place where the viewer is eating">at home.</fbt>
<fbt desc="Beginning of a sentence about where the viewer is eating">You are eating</fbt> <fbt desc="Place where the viewer is eating">at a restaurant.</fbt>
Here the code is printing the beginning of the sentence, which doesn't change in English, then choosing one of two possible endings. This is impossible to translate correctly to Chinese, where the phrases for "at home" and "at a restaurant" need to come before the word for "eating".
In this case, use separate phrases:
<fbt desc="Where the viewer is eating">You are eating at home.</fbt>
<fbt desc="Where the viewer is eating">You are eating at a restaurant.</fbt>
Or an enum, which generates the same complete sentences for translators:
fbt(
'You are eating ' .
fbt::enum($place, [
'home' => 'at home',
'restaurant' => 'at a restaurant',
]) . '.',
'Where the viewer is eating',
)
Here the code chooses one of two complete sentences. The translator can adjust the word order of both sentences as needed, and these can be correctly translated into every language.
Along the lines of the previous item, if you have a phrase like "You have {number} photos." where you use the word "photo" when the number is 1, expand this out into separate complete sentences line, "You have one photo." and "You have {number} photos.", like this:
<fbt desc="Number of photos of the viewer">You have <fbt:plural many="photos" showCount="ifMany" count="3">one photo</fbt:plural>.</fbt>
If several plurals (or enums) of a phrase depend on the same value, give them the same key. Only the combinations which can happen are generated for translation:
fbt(
'There ' . fbt::plural('is', $count, ['many' => 'are', 'key' => 'likes']) . ' ' .
fbt::plural('a like', $count, ['showCount' => 'ifMany', 'many' => 'likes', 'key' => 'likes']) . '.',
'Number of likes of a photo',
)
Use Names and Pronouns for People
Many languages change verbs, adjectives or pronouns with the gender of a person, even where English doesn't. Use fbt::name for names of people, and fbt::pronoun instead of hardcoding "his" or "her":
fbt(
fbt::name('name', $user->getName(), $user->getGender()) . ' updated ' .
fbt::pronoun('possessive', $user->getPronounGender(), ['human' => true]) . ' profile.',
'Notification about an updated profile',
)
The signed-in user is the viewer context, when the User model implements IntlViewerContextInterface (see Getting started), so that texts addressed to the viewer can use their gender.
Pass Numbers, Not Numeric Strings
Numbers are formatted by the conventions of the locale (e.g. 12,345 or 12 345) only when they're numbers. Cast numeric strings (e.g. from a database or a form) to numbers:
fbt('You have ' . fbt::param('count', (int)$points, ['number' => true]) . ' points.', 'Points of the viewer')
Use fbs for Plain Text
Attributes like title, placeholder, alt or aria-label can only contain plain text. Use fbs for them, so that a translation can never contain markup:
<input placeholder="{{ fbs('Search photos', 'Placeholder of the search field') }}">
Avoid Tiny Fonts
Font sizes under 10 pixels can be difficult to read in some languages, especially Chinese and Japanese.
Don't Hardcode Punctuation
Different languages use different punctuation symbols; for example, Chinese has two different comma characters that are used in different contexts. In general if you allow translators to translate complete sentences (including periods and commas) this won't be as big an issue for you.
So you should include the punctuation within the fbt tags:
<fbt desc="Notification about new mail">You have mail.</fbt>
Don't exclude it from the tags:
<fbt desc="Notification about new mail">You have mail</fbt>.
Japanese translators, among others, will want to use their language's end-of-sentence character, which is not an English-style period.
Similarly, you should do this:
<fbt desc="Form label">Favorite color:</fbt> <input ...>
And not do this:
<fbt desc="Form label">Favorite color</fbt>: <input ...>
Including the colon as part of the translatable string means translators can substitute another punctuation mark if applicable, or can insert whitespace between the text and the colon (as is done in French, for example.)
Using Icons Instead of Images with Text
Using icons rather than images with prerendered text can sometimes save you the trouble of having to generate your graphics in different languages. But be aware that some symbols are culture-specific and may not mean the same thing to people in different countries -- for example, a hand with a raised thumb indicates "good" in some cultures but is an obscene gesture in others. An icon whose meaning is obscure is actually worse than using untranslated text, since the latter can at least be looked up in a dictionary as a last resort.
Based on the Platform Internationalization Best Practices of Facebook.