# stringlane.yaml Reference

> Every field of the project config the desktop app and the CLI both read: format, path, layout, guarded words, per-key length limits, and the product and locale context that shapes every translation.

Source: https://stringlane.app/docs/reference/stringlane-yaml
Last updated: 2026-09-02

One file at the root of your project. The desktop app writes it, the CLI reads it, and both honour it identically. Commit it.

`stringlane init` creates it from what it detects, showing you the file before writing it.

```yaml
format: arb
path: lib/l10n
```

That is a complete config. Everything below is optional.

## Locating the files

### `format`

Which localization format this project uses. Required in practice, since it determines every parsing rule.

| Value | Files |
|---|---|
| `arb` | Flutter and Dart `.arb` |
| `ios-strings` | Apple `.strings`, with plural forms in the companion `.stringsdict` |
| `xcstrings` | Xcode 15+ `.xcstrings` String Catalogs |
| `android-xml` | Android `res/values*/strings.xml` |
| `i18next-json` | i18next JSON |

An unrecognised value is ignored rather than guessed at.

### `path`

Where the locale files live, relative to the project root. Omit it and StringLane detects a standard layout.

```yaml
path: lib/l10n
```

Paths are Unicode-normalised when read and written, so a directory name with a combining accent behaves the same whether it was typed by hand or produced by macOS.

### `layout`

For i18next only: whether each locale is a single file (`flat`) or a directory of namespace files (`namespaced`).

```yaml
layout: namespaced
```

Omit it and the layout is detected.

### `name`

A display name for the project. Cosmetic.

## Rules the validator applies

### `guardedWords`

Terms that must appear verbatim in every translation. Your product name, a trademark, a UI label that matches a hardware button.

```yaml
guardedWords:
  - StringLane
  - Xcode
```

A translation that changes one raises `guarded_word_violation`. This is a term-level constraint, not a whole-string freeze: the rest of the sentence is expected to be translated normally.

### `keyConstraints`

Per-key length limits, for strings that sit in a fixed space.

```yaml
keyConstraints:
  nav_home:
    maxLength: 12
  cta_button:
    maxLength: 20
```

A translation over the limit raises `max_length_exceeded`. If the **base** value is already over it, that is `source_length_exceeded`, because every translation will be too and the limit is the thing that is wrong.

Limits are also carried into translation: a plan tells the agent the budget rather than letting it discover the failure afterwards.

## Context that shapes translations

These fields do not gate anything. They travel into every translation request, from the app, from `stringlane update` and from `prepare_translation_plan` alike, which is why they belong in a committed file rather than in one person's settings.

### `productContext`

What the product actually is, so an ambiguous word is resolved the right way.

```yaml
productContext: >
  A desktop editor for app localization files. "Key" means a translation
  identifier, never a cryptographic key or a keyboard key.
```

### `brandingGuidelines`

How it should sound.

```yaml
brandingGuidelines: >
  Plain and direct. Address the user as "you". No exclamation marks.
```

### `localeContext`

Notes for one language, where a general instruction would be wrong.

```yaml
localeContext:
  de: 'Use the informal du throughout.'
  ja: 'Prefer です/ます. Keep technical terms in katakana.'
```

## What is deliberately not here

**AI provider, model and base URL.** They used to be, and moved out. This file is committed project intent, and two people on one repository legitimately use different providers: one has an Anthropic key, one runs Ollama locally. Committing one person's choice makes the other's run wrong. They come from flags, or from `STRINGLANE_AI_PROVIDER` and `STRINGLANE_AI_MODEL`.

**API keys.** Never. There is no field for one, and nothing reads a key from this file.

## The other files in `.stringlane/`

`stringlane.yaml` sits at the project root. Two sibling files live in a `.stringlane/` directory beside it, and both are also yours to commit:

| File | What it holds |
|---|---|
| `.stringlane/baseline.yaml` | Findings your project has accepted, so `check` fails only on new ones |
| `.stringlane/metadata.yaml` | Key descriptions and constraints for formats whose own files cannot carry them |

> A blanket `.*` rule in `.gitignore` swallows `.stringlane/` silently. The files are then written locally, never committed, and every description is missing on a teammate's machine. See [where key metadata is stored](/docs/key-metadata-storage).

StringLane's own working state — the apply lock, and a record of what each project looked like last time — is deliberately **not** in your repository. It lives in a per-user directory outside it, so nothing shows up in `git status`.

## Frequently asked questions

### Should stringlane.yaml be committed to git?

Yes. It is project intent: where the locale files live, which terms must never be translated, and what the product is. Committing it is what makes the app, the CLI and every teammate read the project the same way.

### Why is there no AI provider setting in stringlane.yaml?

Because it is committed and shared. Two people on one repository legitimately use different providers, so committing one person choice makes the other run wrong. Provider and model come from flags or environment variables instead.
