Android XML

Android strings.xml, from strings to plurals

strings.xml is mandatory for native Android: the resource system picks values-de, values-fr and friends by device locale on billions of devices. The format carries more than key and value: <plurals> blocks, translator comments, positional format arguments, and escaping rules that XML and the Android build both enforce.

This page covers the syntax and its traps: apostrophes, quantity classes, and the resources that should never reach a translator.

res/values-de/strings.xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Title of the checkout screen -->
    <string name="checkout_title">Bestellübersicht</string>
    <string name="menu_home">Startseite</string>
    <plurals name="cart_items">
        <item quantity="one">%d Artikel</item>
        <item quantity="other">%d Artikel</item>
    </plurals>
</resources>
Strings, a translator comment and a plurals block: everything the format can carry in one file.
Download a sample: strings.xml

Cheatsheet

strings.xml syntax in six rows

You writeWhat it means
l\'article Apostrophes must be escaped or the Android build fails; the classic broken string is a French translation typed by hand.
%1$s, %d Positional format arguments. With more than one argument, positions are required so translators can reorder them.
<plurals> with quantity Native plural syntax. Quantities map to CLDR categories, and which categories a locale needs comes from CLDR, not guesswork.
translatable="false" Marks a resource that should never reach translators. Importers are expected to skip it.
<string-array> Not imported: an indexed array has no stable key per item. Strings that need translation belong in individual <string> entries.
<!-- comment --> The comment above a string imports as its note and is written back above it on export.

Untranslated keys, three spellings

Dropping the key is usually right on Android, because the resource system falls back to the default locale at runtime.

exported empty
<string name="checkout_confirm"></string>
left out
<!-- key not exported -->
source fallback
<string name="checkout_confirm">Confirm purchase</string>

Conventions

Where strings.xml lives

LocalePath
Source language res/values/strings.xml
German res/values-de/strings.xml
Canadian French res/values-fr-rCA/strings.xml
Jetpack Compose Same resources; stringResource(R.string.checkout_title)

Typical flow: the repo keeps one file per locale under res/values-*, GitHub or GitLab imports new keys when developers push, and translated files return as a pull request formatted exactly like the source.

Positional arguments across languages

With two or more format arguments, positions let each language reorder them.

values/strings.xml
<string name="added_by">%1$s added by %2$s</string>
values-de/strings.xml
<string name="added_by">%2$s hat %1$s hinzugefügt</string>

German swaps the arguments; the positions keep them attached to the right values.

Plurals

Plurals in strings.xml

Android has real plural syntax, and locamorph reads it as such: each quantity becomes a CLDR form a translator fills in separately.

res/values-fr/strings.xml
<plurals name="cart_items">
    <item quantity="one">%d article</item>
    <item quantity="other">%d articles</item>
</plurals>
en · two quantities
<plurals name="cart_items">
    <item quantity="one">%d item</item>
    <item quantity="other">%d items</item>
</plurals>
pl · four quantities
<plurals name="cart_items">
    <item quantity="one">%d artykuł</item>
    <item quantity="few">%d artykuły</item>
    <item quantity="many">%d artykułów</item>
    <item quantity="other">%d artykułu</item>
</plurals>
ar · six quantities
<plurals name="cart_items">
    <item quantity="zero">لا عناصر</item>
    <item quantity="one">عنصر واحد</item>
    <item quantity="two">عنصران</item>
    <item quantity="few">%d عناصر</item>
    <item quantity="many">%d عنصرًا</item>
    <item quantity="other">%d عنصر</item>
</plurals>

Which quantities a locale needs comes from CLDR: Polish uses four, Arabic all six, Japanese only other. Android ignores quantities a locale never uses.

See the same plural in every format locamorph exports; the Android block converts losslessly to ICU and back.

In locamorph

How locamorph handles strings.xml

plurals

Quantities become CLDR forms

quantity="one" through "other" map onto the same plural model every other format uses, so a plural authored for Android exports cleanly to JSON or ARB.

comments

Comments ride along

The comment above a string imports as its note and is written back above the string on export. Context written for translators stays in the file.

escaping

Escaping handled, not hoped for

Apostrophes, backslashes and characters XML cannot represent are escaped the way Android expects. No more build breaks from a French apostrophe.

Convert

Convert strings.xml to and from other formats

Conversion is import plus export, with plurals mapped through CLDR forms instead of copied as text.

strings.xml to JSON

Android strings shared with a React Native or web build.

  1. Import strings.xml; comments become notes.
  2. plurals blocks become CLDR forms.
  3. Export as JSON with ICU plurals.

strings.xml to ARB

An Android app migrating to Flutter.

  1. Import each values-* file into its locale.
  2. Notes become ARB descriptions; plurals become ICU.
  3. Export app_*.arb ready for gen-l10n.

JSON to strings.xml

Web translations reused in the native app.

  1. Import the JSON locales.
  2. Dotted keys flatten to resource names.
  3. Export strings.xml with Android escaping applied.

Bring your values folder

Import a strings.xml on the free plan and watch plurals, comments and escaping arrive intact. Exports drop straight into res/values.