# How to Work with ARB Metadata Annotations

> What @description, x-max-length, x-guarded and x-no-translate mean in ARB files, and how StringLane reads and uses them.

Source: https://stringlane.app/docs/arb-metadata-fields
Last updated: 2026-08-21

ARB files support metadata annotations via `@key` objects: JSON objects whose names start with `@` and correspond to a translation key. StringLane reads these annotations and uses them to validate translations and improve AI output.

> Not sure what ARB is? See [What Is an ARB File?](/docs/what-is-an-arb-file) for the format basics before diving into metadata.

## Anatomy of an ARB annotation

```json
{
  "greetingMessage": "Hello, {name}!",
  "@greetingMessage": {
    "description": "Greeting shown at the top of the home screen",
    "x-max-length": 50,
    "placeholders": {
      "name": { "type": "String" }
    }
  }
}
```

StringLane reads the `@greetingMessage` object alongside `greetingMessage`. The annotation does not need to exist, StringLane works fine without it, but when present, it unlocks more accurate validation and better AI translations.

## Supported annotation fields

### `description`

A human-readable explanation of what the string is for: where it appears in the UI, any context that affects tone or length.

**How StringLane uses it:**
- Passed verbatim to the AI translation prompt as context for that specific key
- Shown in the key detail panel in StringLane (List View)

**Example:**
```json
"@submitButton": {
  "description": "Button label on the payment confirmation screen. Must be short: 2 words max."
}
```

### `x-max-length`

An integer maximum character count for the translation. Useful for UI elements with fixed display space: button labels, tab titles, notification titles.

**How StringLane uses it:**
- Translations exceeding this length show a **Length** badge (yellow)
- Also passed to the AI prompt: the model is asked to respect the limit

**Example:**
```json
"@doneButton": {
  "description": "Button in the navigation bar",
  "x-max-length": 10
}
```

### `x-guarded`

A boolean flag (`true`) marking this key as containing a brand name or guarded term that must never be translated.

**How StringLane uses it:**
- If a target locale's value differs from the base locale's value for a guarded key, a **Guarded** badge (yellow) appears
- The guarded check runs before the "untranslated" check: a guarded key with the same value as base is correct behavior, not a translation failure

**Example:**
```json
"appName": "StringLane",
"@appName": {
  "x-guarded": true
}
```

### `x-no-translate`

A boolean flag (`true`) marking this key as one nobody translates — an API path, a debug label, an identifier that happens to live in the locale file.

**How StringLane uses it:**
- The key's cells are locked in every locale, with a tooltip saying the flag was set in StringLane
- The key drops out of the Issues panel and out of every AI action, including bulk translate and bulk metadata generation
- It is still **counted**. The completion percentage and the key count in the status bar treat it like any other key, so a locked key with empty locales holds the project below 100%

It is a different rule from `x-guarded`, and both can be set on one key. `x-guarded` warns about a cell you can still edit; `x-no-translate` takes the key out of the workflow.

**Example:**
```json
"apiBaseUrl": "https://api.example.com",
"@apiBaseUrl": {
  "x-no-translate": true
}
```

## Editing annotations in StringLane

You do not have to hand-edit JSON. StringLane has a metadata editor for every key.

**To open it:** select a key, then click the **ℹ** button — in the detail-pane header, or on the key's row in the sidebar. The button is tinted when the key already has metadata, so you can see at a glance which keys are annotated.

The **Key Metadata** panel edits every field on this page:

| Field in the panel | Written to |
|---|---|
| Description | `description` |
| Max length | `x-max-length` |
| Must not change | `x-guarded` |
| Not for translation | `x-no-translate` |
| Placeholder rows — name, type, format, example, description | `placeholders` |

Changes are written back to the base-locale ARB file on save, the same way a translation edit is. Fields you leave empty are removed rather than written as `null`, so the file stays clean.

**Detect placeholders** scans the base value and suggests any `{placeholder}` that has no entry yet — click one to add it, or **Add all**.

![Key Metadata panel for authLoginSubmit: a Description box above Generate with AI, a Constraints group with Max length 20 and a Disable character budget checkbox, a Translation rules group with the Must not change and Not for translation checkboxes, and an empty Placeholders section with Detect and Add](/docs-screenshots/metadata-sheet-open.webp)

### Generate metadata with AI

Writing a description for every key by hand is the reason most ARB files have none.

- **One key:** click **Generate with AI** in the Key Metadata sheet. The result fills the form for you to read — nothing is written until you click **Apply**, and **Discard** puts your original values back.
- **Every key:** open the [Command Palette](/docs/use-command-palette) (⌘P) and run **Generate AI metadata for all keys**. It processes keys in batches, shows progress and an ETA, and can be cancelled mid-run.

The bulk run skips keys that are already complete — a description plus a typed entry for every placeholder detected in the base value — so re-running it only fills real gaps. Requires an AI provider; see [Set Up AI Translation](/docs/set-up-ai-translation).

### Setting max-length without opening each key

To set character limits across many keys at once, use **Settings → Project → Key Constraints** (⌘,). Type a key name, give it a limit, and it applies immediately. On an ARB project it is written to `x-max-length`, so it travels with the file.

It is the same limit as **Max length** in the metadata panel — one setting shown in two places. Earlier versions kept a second copy in the project settings file that won over the one on the key; opening a project now folds that copy onto the key and removes it from the settings file, once, on open.

## The `@key` block is ARB-only; the metadata panel is not

Annotations are a Flutter/Dart convention: `flutter gen-l10n` supports them and the `intl` package recognises them. StringLane extends them with `x-max-length`, `x-guarded` and `x-no-translate` for validation. No other format has an equivalent block, so everything on this page is specific to `.arb` files.

The **panel** that edits them is not. Description, max length, **Must not change** and **Not for translation** are settable on `.xcstrings`, `.strings`, Android XML and i18next JSON too — each format keeps whatever it has a field for, and StringLane keeps the rest in `.stringlane/metadata.yaml` beside your locale files. See [How to Add Key Descriptions and Limits on Any Format](/docs/annotate-keys-any-format) and [Where Key Metadata Is Stored](/docs/key-metadata-storage).

Only the **placeholder table** stays ARB-only. Its types are Dart type names (`String`, `int`, `DateTime`), which have no meaning for `%@` or `{{var}}`.

Guarded terms also work project-wide, on every format, via [Guarded Words](/docs/configure-guarded-words) — a list of terms rather than a flag on one key.

## Editing the files by hand

You can still do it. Edit the ARB in any text editor and StringLane picks the changes up the next time you open the project — or immediately, via [Reload from Disk](/docs/reload-from-disk) (⌘R) if it is already open.

StringLane preserves annotations it does not recognise. The parser keeps each `@key` object whole and writes it back unchanged, so custom `x-` fields your own tooling depends on survive a save. Your original key order is preserved too — saving does not reshuffle the file, which keeps diffs readable.
