# Android strings.xml: Plurals, Escaping and Format Args

> Android strings.xml holds an app''s strings, one values folder per language. A complete example with plurals, format arguments and escaping, and what breaks.

Source: https://stringlane.app/docs/what-is-strings-xml
Last updated: 2026-10-22

**`strings.xml`** is the resource file that holds an Android app's user-facing text. The default language lives in `res/values/strings.xml`, and each translation lives in a folder named for its language, such as `res/values-uk/strings.xml`. At run time Android picks the folder that matches the device language and falls back to `res/values/` for anything missing.

## What does an Android strings.xml file look like?

This `res/values/strings.xml` covers what you will write in practice: a plain string, a string with format arguments, a plural, an escaped apostrophe, a string array and a string that is never translated.

```xml
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name" translatable="false">Notes</string>

    <!-- Greeting on the home screen. -->
    <string name="welcome">Hello, %1$s! You have %2$d new notes.</string>

    <plurals name="file_count">
        <item quantity="one">%d file</item>
        <item quantity="other">%d files</item>
    </plurals>

    <string name="cant_sync">Can\'t sync right now</string>

    <string-array name="sort_options">
        <item>Newest first</item>
        <item>Oldest first</item>
    </string-array>
</resources>
```

- **`<string name="...">`** is one key. The `name` becomes the resource ID, `R.string.welcome`.
- **`<plurals>`** holds one `<item>` per plural quantity.
- **`<string-array>`** is an ordered list, read with `getStringArray`.
- **`translatable="false"`** marks a string that stays the same in every language, such as the app name.
- **A comment directly above a key** is the usual place for a note to translators.

## How do translations work in strings.xml?

Each language gets its own `values-` folder with a file of the same keys:

```text
res/
  values/strings.xml          default language
  values-uk/strings.xml       Ukrainian
  values-pt-rBR/strings.xml   Portuguese (Brazil)
  values-b+sr+Latn/strings.xml  Serbian in Latin script
```

A translation only needs the keys it translates. Anything it leaves out, Android takes from `res/values/`, which keeps the app running and shows that string in the default language.

## How do plurals work in strings.xml?

A `<plurals>` element has one `<item>` per CLDR quantity: `zero`, `one`, `two`, `few`, `many` and `other`. Each language needs its own set. English needs `one` and `other`; Ukrainian needs `one`, `few`, `many` and `other`:

```xml
<!-- res/values-uk/strings.xml -->
<plurals name="file_count">
    <item quantity="one">%d файл</item>
    <item quantity="few">%d файли</item>
    <item quantity="many">%d файлів</item>
    <item quantity="other">%d файлу</item>
</plurals>
```

Read it with the count twice:

```kotlin
resources.getQuantityString(R.plurals.file_count, count, count)
```

The first `count` picks the item. The second fills `%d`.

Two rules catch people out:

- **Android picks by grammatical category, not by number.** In English, 0 is `other`, so an English `zero` item is never used. Use a separate string for "No files".
- **A missing item falls back to `other`.** A Ukrainian block with only English's `one` and `other` still builds, and shows "3 файлу" where Ukrainian says "3 файли".

## How do format arguments work in strings.xml?

Values use Java format specifiers. Number them, `%1$s` and `%2$d`, so a translator can reorder them for their language:

```xml
<string name="welcome">Hello, %1$s! You have %2$d new notes.</string>
```

```kotlin
getString(R.string.welcome, name, count)
```

The arguments are matched by position, so the translation has to keep the same numbered specifiers:

- **A translation that drops `%2$d`** still formats, and the reader never sees the count.
- **A translation that adds a specifier** the code does not pass, such as a `%3$s`, throws a `MissingFormatArgumentException` when that screen formats the string.

## How do you escape characters in strings.xml?

| Character | Write it as |
| --- | --- |
| `'` apostrophe | `\'`, or wrap the whole value in double quotes |
| `"` double quote | `\"` |
| `@` or `?` at the start | `\@`, `\?` |
| new line | `\n` |
| `&` and `<` | `&amp;` and `&lt;` |

## What breaks strings.xml without failing the build?

- **A missing translation** shows the default-language string.
- **A plural with English's quantities** in a language that needs more falls back to `other`.
- **A dropped format argument** hides the value; **an extra one** crashes the screen that formats it.
- **A copied, untranslated value** looks fine in the file and ships in the default language.

Most of these are in a language you may not read.

## How do you check strings.xml across every language?

StringLane reads every `res/values*/strings.xml` in the project and checks each language against the default: missing strings, plural quantities that language requires, numbered format arguments compared per string and per plural item, by count and by the type each argument is read as, and values identical to the default. A regional folder such as `values-fr-rCA/` or `values-b+es+419/` owes only the strings it defines, because Android shows the parent language's folder for the rest; the parent must be the same language in the same script, so `values-zh-rTW/` still owes every string. A folder named with `b+`, such as `values-b+sr+Latn/`, owes every plural quantity its language needs. The free CLI runs the same checks from a terminal or CI and writes nothing into your repository:

```sh
npx @stringlane/cli@latest check .
```

The [Android strings.xml editor](/android-xml-editor) shows the same checks while you edit, with every `values` folder side by side.

## Next steps

- **[Fix placeholder mismatches](/docs/fix-placeholder-mismatches)**: find and restore a dropped `%1$s`.
- **[Add a language](/docs/add-a-locale)**: create a new `values-` folder from a preset.
- **[Flutter ARB File Format](/docs/what-is-an-arb-file)**: the same problems in a Flutter project.
- **[Inspect and Check a Project](/docs/cli/inspect-and-check)**: run the checks in CI.

## Frequently asked questions

### Where does strings.xml go in an Android project?

The default language goes in res/values/strings.xml. Each translation goes in a folder named for its language, such as res/values-uk/strings.xml or res/values-pt-rBR/strings.xml. The file name itself is arbitrary; Android reads every XML file in the folder.

### Why is my zero plural ignored in English?

Android picks the plural item by grammatical category, not by the number. English has only one and other, so 0 uses other and a zero item is never selected. Use a separate string for the empty state.

### Why does getQuantityString take the count twice?

The first count picks the plural item. The arguments after it fill the format specifiers in that item, so for "%d songs" you pass count again as the value for %d.

### How do I escape an apostrophe in strings.xml?

Write \' or wrap the whole value in double quotes. Double quotes inside a value are written \", a leading @ or ? is written \@ or \?, and & and < use the XML entities &amp; and &lt;.

### What happens when a translation is missing in strings.xml?

Android uses the string from the default res/values folder, so the screen shows the default language. A plural item missing from a translation falls back to that translation's other item.
