By the strings.dev team — maintainers of native .xcloc and strings.xml localization tooling for iOS and Android.
Where your strings live
Android keeps every user-facing string in res/values/strings.xml — your default (usually English) locale. Each additional language gets a parallel file in a qualified folder: res/values-fr/strings.xml, res/values-es/strings.xml, and so on. At runtime Android reads the device locale and loads the matching folder, falling back to the default values/ file for any string that hasn't been translated yet.
<!-- res/values/strings.xml (default locale) -->
<resources>
<string name="welcome_title">Welcome to Acme</string>
<string name="save_button">Save</string>
<string name="greeting">Hi, %1$s</string>
</resources>
The folder qualifier is the whole contract. Get values-fr or values-es-rMX right and the system does the rest — no registration, no code change.
Open the Translations Editor
You don't hand-edit those parallel files. Android Studio ships a purpose-built grid for them: the Translations Editor.
Two ways in:
- Open
res/values/strings.xml, and in the top-right of the editor pane click Open editor (the globe icon on the "Edit translations for all locales in the translations editor" banner).
- Right-click
res/values/strings.xml in the Project view and choose Open Translations Editor.
You get a spreadsheet: one row per string name, one column per locale, plus Key and Untranslatable columns. Empty cells are strings a locale is missing — the fastest visual audit of your coverage.
Add a language
In the Translations Editor toolbar, click the globe (+) "Add Locale" button. Pick a language from the list — for example French (fr). Android Studio immediately creates res/values-fr/strings.xml and a new fr column in the grid. Choosing a language with a region, like Spanish (es) in Mexico (MX), creates res/values-es-rMX/strings.xml.
That r prefix trips people up. The region qualifier is a lowercase r followed by the uppercase ISO region code:
| You want |
Folder Android Studio creates |
| French |
values-fr |
| Spanish |
values-es |
| Spanish (Mexico) |
values-es-rMX |
| Portuguese (Brazil) |
values-pt-rBR |
| French (Canada) |
values-fr-rCA |
For script variants like Simplified vs Traditional Chinese, Android Studio writes the BCP-47 form instead: values-b+zh+Hans and values-b+zh+Hant. If you're unsure which qualifier maps to which market, the iOS/Android locale code reference lists them side by side.
Edit strings and keep placeholders intact
Click any cell and type the translation, or select a row to edit it in the Translation field at the bottom. The one rule that matters: placeholders must survive the translation byte-for-byte. Android positional arguments — %1$s, %2$d — let a translator reorder the sentence without breaking the format call, which German and Japanese routinely need.
<!-- res/values-fr/strings.xml -->
<resources>
<string name="welcome_title">Bienvenue sur Acme</string>
<string name="save_button">Enregistrer</string>
<string name="greeting">Bonjour, %1$s</string>
</resources>
If a string contains an apostrophe or an ampersand, escape it (\', &) or Android Studio flags the resource as invalid.
Handle <plurals>
Quantity strings are where the Translations Editor gets thin — it surfaces <plurals> awkwardly, and it's often cleaner to edit them directly in the locale's strings.xml. Each language declares only the CLDR quantity categories it actually uses (zero, one, two, few, many, other):
<!-- res/values/strings.xml -->
<plurals name="song_count">
<item quantity="one">%d song</item>
<item quantity="other">%d songs</item>
</plurals>
<!-- res/values-ru/strings.xml — Russian needs few/many -->
<plurals name="song_count">
<item quantity="one">%d песня</item>
<item quantity="few">%d песни</item>
<item quantity="many">%d песен</item>
<item quantity="other">%d песни</item>
</plurals>
Your English source defines one/other; Russian and Polish need few/many that don't exist upstream. Resolve plurals with getResources().getQuantityString(R.plurals.song_count, count, count) — never string concatenation.
Fill every locale automatically
The Translations Editor is great for a few strings in one or two languages. It stops scaling the moment you have hundreds of keys and a dozen markets — that's manual typing, per cell, per locale, and it re-opens every release.
This is the hand-off to strings.dev. Export your res/values/strings.xml (it's already the native format — no XLIFF conversion), upload it, and pick your target locales. You get a strings.xml back per locale, same format in, same format out, with:
- Placeholders preserved —
%1$s, %2$d, URLs, emails, and @handles are never translated or reordered incorrectly.
<plurals> expanded per locale — the CLDR categories each language requires, not a mirror of English's two.
- Brand-phrase protection — mark your product name or feature words so "Save" (your mode) doesn't become a French verb.
- App-description context — every string is translated knowing what your app does, not dictionary-literal.
The free Indie tier covers 1 project and 1 language with unlimited word translations — enough to run the whole loop end to end. Going wider (unlimited languages, 2 projects, brand context, and the QA & analytics dashboard that flags failed translations for retry) is Indie Plus at $20/mo, or $10/mo billed annually.
Drop the returned files back into your values-* folders and you're done. To keep translations from lagging your code, wire the CLI or REST API into a git hook or CI stage so new strings come back translated on every push. For the broader iOS + Android walkthrough, see the mobile app localization guide.
One boundary worth stating: this covers in-app strings only. Your Google Play listing — title, description, screenshots — is translated in the Play Console, not in strings.xml.