Localization at mobile scale
Meta's mobile apps serve a majority of users in non-English locales: about 57 percent of Facebook for Android users and 49 percent of Facebook for iOS users operate the app in a language other than English. Supporting accurate translations across dozens of languages — over 100 for some interfaces — becomes a challenge of both correctness and app size. The company's answer is a downloadable language pack system that keeps translation data out of the binary and pulls it on demand.
Two problems with native localization
Grammatical accuracy
Native Android and iOS frameworks handle simple and pluralized text but struggle with gender-specific phrasing without considerable boilerplate. To express grammatically correct translations including viewer and subject gender, Meta uses FBT, its own string API supporting PHP, Hack, JavaScript, and React Native.
fbt(
'Write on ' +
fbt.pronoun('possessive', gender) +
' timeline...',
'Placeholder text for inline composer',
)
<string name="title_male" description="Placeholder text for inline composer">Write on his timeline...</string>
<string name="title_female" description="Placeholder text for inline composer">Write on her timeline...</string>
<string name="title_other" description="Placeholder text for inline composer">Write on their timeline...</string>
if (gender == Male) {
FBLocalizedString("Write on his timeline...", "Placeholder text for inline composer");
} else if (gender == Female) {
FBLocalizedString("Write on her timeline...", "Placeholder text for inline composer");
} else {
FBLocalizedString("Write on their timeline...", "Placeholder text for inline composer");
}
Language coverage vs. install size
With native localization, all strings must be baked into the app bundle before submission. As language count and feature scope grow, so does the binary. That bloat discourages users with bandwidth or storage constraints from updating, which in turn keeps features and security fixes from reaching devices.
An app-size test on Facebook for iOS (when it supported 35 locales) showed that stripping translation files entirely saved 16.6 MB of download size. Since most users need just one language, the remaining files were largely unused dead weight. The language pack approach — initially built on Android — fetches only the pack for the device's active language. Since adoption, Meta added close to a dozen languages (including Burmese, Georgian, Latvian, and Sinhala) to Facebook for Android with no impact on app size.
How the pipeline works
Language pack processing splits into two phases: one before build release, one after. When engineers build app binaries, they also construct language packs for the FBT strings in that build. Each release must have its associated pack uploaded to cloud storage before the app ships. On launch, the client initializes localization asynchronously, either reading a cached pack from disk or fetching it over the network. Once loaded into memory, the string API resolves translations from the parsed data at runtime.
An automated pipeline handles daily string churn: it extracts strings from the codebase, pulls translations from a database, and bundles them. Each mobile release triggers a build step that generates packs for all supported locales and uploads them to storage.
Authoring and extracting FBT strings
FBT wraps English text in a function call — simple by design. A straightforward string is just a wrapper; a complex string adds a table of variations keyed by gender or plural tokens.
fbt('Hello, World', 'a simple example', {project:"foo"})
// Translation: "Hello, World"
fbt("View " +
fbt.name('user', shortName, gender) +
"'s Timeline - " +
fbt.plural('follower', count, {many: 'followers', showCount: 'yes'}),
'In user composer, gives details about the person to who the post is directed.',
)
// Translation: "View Lu's timeline - 1 follower"
// Translation: "View Lu's timeline - 5 follower"
Extraction transforms each fbt() call into an abstract object carrying two pieces of information used by pack construction:
id— a hashed key from text, description, and metadatatext_or_table— a multi-level lookup table whose levels mirror the FBT callsite's variation tokens
[
{
"description":"In user composer, gives details about the person to who the post is directed.",
"id":"4sIjkwerw",
"text_or_table":{
"UNKNOWN":{
"ONE":"View {user}'s Timeline - 1 follower",
"OTHER":"View {user}'s Timeline - {number} followers"
}
},
"variations": [
{
"type": "GENDER",
"token": "user"
},
{
"type": "NUMBER",
"token": "number"
}
],
"tokens": {
"name": "%1$@"
"number": "%2$ld"
}
},
]
Pack structure
The build step fetches translations from the database and encodes them as a hash map binary file per language. Two fields matter:
resource_id— matches the extracted object'sid, used by the client to find translationsnested_fbt_resource_data— a multi-level table where each token level carries type and possible variations (gender, plural, etc.), with lookup values set by the FBT callsite
Language_pack | |____ resource_id_1 : nested_fbt_resource_data | |____ resource_id_2 : nested_fbt_resource_data | |...
Tokens are only known at runtime, so dictionary lookup happens then rather than at compile time.
{
"4sIjkwerw":{
"male":{
"one":"Δείτε το Χρονολόγιο του {user} - 1 ακόλουθος",
"other":"Δείτε το Χρονολόγιο του {user} - {number} ακόλουθοι"
},
"female":{
"one":"Δείτε το Χρονολόγιο της {user} - 1 ακόλουθος",
"other":"Δείτε το Χρονολόγιο της {user} - {number} ακόλουθοι"
},
"default":{
"one":"Δείτε το Χρονολόγιο του χρήστη {user} - 1 ακόλουθος",
"other":"Δείτε το Χρονολόγιο του χρήστη {user} - {number} ακόλουθοι"
}
}
}
Client-side behavior
Localization initialization must finish before the first UI view renders. After network setup completes, the client checks for a language pack matching both locale and client version on disk. If present, it decompresses, parses, and loads the data into memory; otherwise it initiates a network download. Users get a seamless experience when startup finishes in time — but spotty connectivity can leave them stuck retrying or falling back to a previous locale.
Two mitigations improve offline and slow-network cases:
- Prefetch: existing client builds schedule a background download of the language pack for the newer version before that version releases.
- Stale fallback: since most translations persist across versions, the initializer loads an older pack when the target version is unavailable, then fetches the correct one in the background.
Both approaches increase the odds that the next session has current translations without interrupting the user.
Measuring impact on Facebook
Packs range from 600 KB to 2 MB depending on language. On Facebook for iOS, the infrastructure loads successfully over 99.99 percent of the time; over 99.8 percent of users load from disk with an average time around 80 ms. Android's success rate exceeds 99.7 percent, with average disk load time near 780 ms across device classes. Over six months of measurement, downloads showed no negative effect on startup or overall Facebook app metrics.
Not a universal fit
Facebook and Workplace run this infrastructure today, but Meta acknowledges it isn't the right answer everywhere. Apps needing few translations or highly sensitive to launch performance may still prefer bundled packs. The next step is weighing which apps benefit from download-on-demand versus which keep translations in the binary. FBT itself is available publicly at facebook.github.io/fbt/.



