How to localize an app, step by step

To localize an app, move every string into resource files, handle plurals (Unicode CLDR has 6 categories), translate, review and ship translated builds; Android has offered per-app language settings since Android 13. Translinga walks through each step for Android, iOS and the web below.

All guidesUpdated

Where each step happens

StepAndroidiOSWeb
Source stringsres/values/strings.xmlLocalizable.xcstringsJSON locale files
Translationsres/values-xx/strings.xmlSame string catalogOne JSON file per language
Plurals<plurals> resourceVary by PluralDepends on the library
Machine translationAny translation APIAny translation APITranslinga CLI or GitHub Action

Step 1: get every string out of the code

Start with the text your app shows, and move it into your platform's string resources or locale files. Give each string a key that says what it's for. Keep the text in the resource, not mixed into the code that displays it.

On Android, your default strings go in res/values/strings.xml. Translated strings go in language folders, such as res/values-pl/strings.xml for Polish. Keep the keys matched across those files as you add translations.

On Apple platforms, use Xcode string catalogs, Localizable.xcstrings, to hold every string. You'll also use the catalog when a message needs different plural forms or device-specific wording.

For a web app, put the text in the locale files your app uses. Keep the keys descriptive here too: a key can give a translator context before they've even seen the screen.

Step 2: handle plurals and variables

Don't build a translated sentence by joining separate translated fragments. Keep the sentence together, with variables inside it, and handle its plural forms as part of that message.

Which forms do you need? Unicode CLDR defines zero, one, two, few, many and other. A language doesn't necessarily use all of them, and English isn't a template for the rest.

Android's <plurals> resource supports those categories. In an Apple string catalog, choose Vary by Plural: English uses one and other, while Russian uses one, few, many and other. There's also Vary by Device when the wording needs to differ by device.

For messages using ICU MessageFormat, put plural and select choices inside the same message. Translators then get the whole sentence, including its choices, rather than fragments they have to fit together.

Keep placeholders intact. They aren't prose.

Step 3: translate

Send the strings with enough context to show what they mean in your app. A short label can be hard to interpret on its own; include what the text is for, and use the key name to carry that information too.

For web locale files, Translinga's CLI reads i18next, next-intl, vue-i18n or any JSON. You can add a $context note to a JSON file, and key names count as context too. It removes Liquid tags, HTML and placeholders before translation, checks replies tag by tag, and retries a string if a tag is missing.

As of November 2026, Fast costs $2.50 per million characters and Quality costs $5.00 per million. Spaces count, and each target language is counted separately. Credit is prepaid from $5 and never expires; the first $1 is free, with no card needed for a key.

It's in early access. Use the Get early access button on translinga.com to request a key.

Step 4: review in context

Put the translations into a build and read them on the screens where they appear. Review the full message, not just the value in the locale file, and check that the wording matches what the control or screen does.

Try the plural forms with the variables filled in. Check the placeholders too.

If a translation needs changing, update the corresponding string in your locale resources. If its meaning was unclear, add that context before sending it through translation again (not just a shorter version of the same ambiguous label).

Step 5: language settings

Decide how you want users to choose a language, then connect that choice to the translated resources.

Since Android 13, users can choose a language per app. Use LocaleManager, or AppCompatDelegate.setApplicationLocales with AndroidX AppCompat 1.6.0 and later.

But that setting doesn't translate anything. Google's docs make this distinction explicit: adding per-app language settings doesn't translate the app's resources by itself. You still need the translated strings from the earlier steps.

Test the language choice in your build and review the screens in that language, not just the settings control.

Step 6: automate it in CI

Once you've got the resource files and review process in place, add locale syncing to CI.

Translinga's CLI and GitHub Action can sync your locale files on every push, translating only new and changed strings:

npx translinga sync --to nl,de,fr

Use the synced locale files in your translated builds, and keep the in-context review in your workflow. A completed translation sync isn't the same check as opening the app and reading the changed text on its screen.

FAQ

Questions, answered

Do I need every plural category for every language?

No. CLDR defines six categories, but English uses one and other, while Russian uses one, few, many and other in Xcode's Vary by Plural.

Does Android's per-app language setting translate my app?

No, it only provides the language choice. You still need translated resources in folders such as res/values-pl/strings.xml.

How do I give translators context without changing the text?

Use descriptive key names and include a note about what the strings are for. For the JSON workflow above, you can put that note in $context.

Do I have to translate all the strings again after every change?

The CLI and GitHub Action described above translate only new and changed strings. Strings already translated in the same context come back from memory for free.

Start free

Try it on your messiest theme file

Your first $1 is free, about 400k characters on Fast. No card needed for a key.

Printed proof sheets marked up in blue pencil, with one word struck through in red, on a dark navy desk