> ## Documentation Index
> Fetch the complete documentation index at: https://developer.dittowords.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> These are Ditto's developer docs. Ditto is a single source of truth for product copy across design, review, localization, and code.
> The first step on any codebase is a scan: https://developer.dittowords.com/get-started/scan-your-repo.md. That page also covers reviewing the style guide Ditto drafts from the scan. After it, the follow-on guides are Connect Figma files and Set up localization (under /guides/) and Connect the Ditto MCP (the Setup page of the MCP reference).
> Two different MCP servers: the Ditto MCP server at https://api.dittowords.com/v2/mcp gives an agent a workspace's text and style guide rules; the documentation MCP server at https://developer.dittowords.com/mcp searches these docs.
> Prefer logging in with a Ditto account (npx -y @dittowords/cli@latest login; OAuth in MCP clients) over API keys. API keys are for CI and headless clients only.
> Commands are complete as written and use npx -y @dittowords/cli@latest; nothing needs a global install.

# Ditto CLI configuration

> Setting what text and formats the CLI pulls down

## Overview

The `config.yml` file is the source of truth for a given directory about how the CLI should fetch and store data from Ditto based on the specified properties. It includes information about which Ditto components the CLI should pull text from, the string file formats, and more.

This is the default configuration file that is generated the first time that the CLI is run in a given directory:

```yml theme={null}
projects: []
variants: []
components:
  folders: []
outputs:
	- format: json
		framework: i18next
```

## Examples

Most teams end up with one of these shapes. Copy the one that matches your platform, replace the developer IDs, and run `npx @dittowords/cli pull`.

**A web app pulling one project as i18next JSON, in three locales:**

```yaml theme={null}
projects:
  - id: checkout
variants:
  - id: base
  - id: german
  - id: french
statuses:
  - FINAL
outputs:
  - format: json
    framework: i18next
    outDir: ./src/locales
```

Result: `src/locales/checkout___base.json`, `checkout___german.json`, `checkout___french.json`, and an i18next driver file. Only text marked `FINAL` is included.

**An iOS app pulling the whole component library into `.lproj` bundles:**

```yaml theme={null}
components:
  folders: []
variants:
  - id: all
iosLocales:
  - base: en
  - german: de
  - french: fr
iosLocalesOutDir: ./MyApp/Localization
outputs:
  - format: ios-strings
  - format: ios-stringsdict
```

Result: `MyApp/Localization/en.lproj/`, `de.lproj/`, and `fr.lproj/`, each with `components.strings` and `components.stringsdict`, plus `MyApp/Localization/Ditto.swift`.

**An Android app pulling the component library into locale resource directories.** Base text always goes to the default `values/` directory, so `base` needs no mapping.

```yaml theme={null}
components:
  folders: []
variants:
  - id: all
androidLocales:
  - german: de
  - french: fr
androidLocalesOutDir: ./app/src/main/res
outputs:
  - format: android
```

Result: `app/src/main/res/values/components.xml`, `values-de/components.xml`, and `values-fr/components.xml`.

## Supported Properties

Listed below are each of the properties that you can specify in your CLI config.

```typescript theme={null}
// Individual property definitions
type ProjectConfig = { id: DeveloperId };
type ComponentFolderConfig = {
  id: "root" | DeveloperId,
  excludeNestedFolders?: boolean
}
type VariantConfig = { id: "all" | "base" | DeveloperId }
type TagsFilter = {
  values: string[],
  operator?: "AND" | "OR"
}
type OutputConfig = {
  // "json_icu" is deprecated — use format: "json" with framework: "icu" instead
  format: "json" | "json_icu" | "android" | "ios-strings" | "ios-stringsdict",
  framework: "i18next" | "vue-i18n" | "icu" | "arb",
  projects?: ProjectConfig,
  components?: ComponentsConfig,
  variants?: VariantsConfig,
  statuses?: TextStatus[],
  integrated?: boolean,
  tags?: TagsFilter,
  outDir?: string
}
type TextStatus = "NONE" | "WIP" | "REVIEW" | "FINAL"
// Maps a variant Developer ID (or "base") to a platform locale code, e.g. { base: "en" }
type LocaleMapping = { [variantId: string]: string }

// Full config object
{
  projects?: ProjectConfig[],
  components?: {
    folders?: ComponentFolderConfig[]
  },
  variants?: VariantConfig[],
  statuses?: TextStatus[],
  integrated?: boolean,
  tags?: TagsFilter,
  richText?: “html” | false,
  outDir?: string,
  iosLocales?: LocaleMapping[],
  iosLocalesOutDir?: string,
  androidLocales?: LocaleMapping[],
  androidLocalesOutDir?: string,
  outputs: OutputConfig[],
}
```

### `projects`

An array of Developer IDs of the projects to pull from. If provided an empty array, will pull text from all projects. The text from each included project will be written to its own json file, per variant.

For more details, see the `filter.projects` param for `GET /v2/textItems` .

**Required: false**. If omitted, will not pull text from any projects.

**Example:** The following will pull in all text from the projects with Developer ID "project-1" and "project-2"

```yaml theme={null}
projects:
  - id: project-1
  - id: project-2
```

### `components`

An object with a single property, `folders`, which takes a list of component folders to pull text from. If provided `{}` or `folders: []`, will pull all components from all folders. Unlike project text, all fetched components will be written to a single file per variant, regardless of the specified folder configuration.

**Required: false**. If omitted, will not pull any components.

By default, will return all components within the specified folders, including those found inside any subfolders. If you do not want to include components within subfolders, add `excludeNestedFolders: true` to that folder's specification.

To pull top-level components (those not inside a folder), you can use the reserved word `"root"` as the folder id. This is best used in combination with `excludeNestedFolders: true`.

**Example:** The following will return all top-level components as well as all components inside "folder-1" along with all components inside all folders inside "folder-1" (no matter how deeply nested). For more details, see the `filter.folders` param for `GET /v2/components` .

```yaml theme={null}
components:
  folders:
    - id: root
      excludeNestedFolders: true
    - id: folder-1
```

### `variants`

An array of variants to return. The value will be applied to both projects and components.

By default, or if passed `[]`, will return base text only. Provide `id: all` to return base and all variants. Or, you may provide a list of the Developer IDs of the specific variants you wish to return; in this case, you must explicitly include `base` in the list to fetch base text.

For more details, see the `filter.variants` param for `GET /v2/textItems` and `GET /v2/components` .

**Required: false**. If omitted, will return base text only.

**Examples**:

```yml theme={null}
# Returns base text only
variants: []

# Returns base text and all variants
variants:
  - id: all

# Returns french and spanish variant text
variants:
  - id: french
  - id: spanish

# Returns base text, french and spanish variants
variants:
  - id: base
  - id: french
  - id: spanish
```

### `statuses`

An array of statuses to return. The value will be applied to both projects and components.

By default, or if passed `[]`, will return all statuses. Provide an array of statuses to selectively filter values you wish to have returned.
When the statuses filter is used with the variant filter, only variants that match the variant and status filters AND are associated with base text that matches the status filter will be returned.
For more details, see the `filter.statuses` param for `GET /v2/textItems` and `GET /v2/components` .

**Required: false**. If omitted, will return all statuses.

**Examples**:

```yml theme={null}
# Returns without any filtering
statuses: []

# Returns base text that has "FINAL" or "WIP" statuses
variants: []
statuses:
  - "FINAL"
  - "WIP"

# Returns base text that has "FINAL" or "WIP" statuses and any related variant text of that base text that also has "FINAL" or "WIP" statuses
variants:
  - id: all
statuses:
  - "FINAL"
  - "WIP"

# Returns spanish variant text that has a "FINAL" or "WIP" status and has base text that also has a "FINAL" or "WIP" status
variants:
  - id: spanish
statuses:
  - "FINAL"
  - "WIP"
```

### `tags`

An object specifying one or more tags to filter by. The value will be applied to both projects and components.

The `values` field is an array of tag strings. The optional `operator` field controls how multiple tags are combined: `"AND"` returns only items with all specified tags, while `"OR"` (the default) returns items with any of the specified tags.

**Required: false**. If omitted, will return all items regardless of tags.

**Examples**:

```yml theme={null}
# Returns items tagged with "tag-1" or "tag-2"
tags:
  values:
    - "tag-1"
    - "tag-2"

# Returns only items tagged with both "tag-1" and "tag-2"
tags:
  values:
    - "tag-1"
    - "tag-2"
  operator: "AND"
```

### `integrated`

A boolean value (true | false) to filter the results only to those items whose `integrated` property matches the provided value. The value will be applied to all projects and components.

**Required: false**. If omitted, will return all items, regardless of integration status.

### `richText: "html" | false`

Flag to determine if the pulled text is represented as plain text or rich text (in HTML format).

**Required: false**. If omitted, will return plain text only.

```typescript theme={null}
// richText: false
{
  "dev-id-1": "This is awesome text",
  "dev-id-2": "This is other text. It has two sentences."
}

// richText: html
{
  "dev-id-1": "This is <strong>awesome</strong> text",
  "dev-id-2": "<p>This is other text.</p><p>It has two sentences</p>"
}
```

### `outDir: string`

The relative path from where the CLI is run to the directory the generated files should be created in. If the provided directory does not exist, it will be created automatically.

**Required: false.** If omitted, the default location is `./ditto`.

### `iosLocales`

An array that maps Ditto variant IDs to [iOS locale IDs](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html). When used in conjunction with `ios-strings` or `ios-stringsdict` output formats, this configuration enables the CLI to generate a `Ditto.swift` driver file and organize locale files into the standard iOS `.lproj` localization bundles.

**Required: false**. If omitted, iOS formats will be generated without locale-specific directory organization.

Each entry in the array maps a variant ID to its corresponding locale code. The variant ID must match a variant defined in your `variants` configuration, or be included via variants `id: all`. The iOS locale code should be a valid iOS locale identifier (e.g., `en`, `es`, `fr`, `de`).

**Behavior:**

* When `iosLocales` is configured and used with `ios-strings` or `ios-stringsdict` formats, the CLI generates a `Ditto.swift` driver file
* The `Ditto.swift` file and `.lproj` directories are placed at the root-level `outDir` (`./ditto` if not configured) and NOT at the `ios-strings` or `ios-stringsdict` output `outDir` — unless `iosLocalesOutDir` is configured, in which case they're placed there instead. See [`iosLocalesOutDir`](#ioslocalesoutdir) below.
* Variants mapped in `iosLocales` are written to `<locale>.lproj` directories
* Both `.strings` and `.stringsdict` files are placed within the same `.lproj` directories
* Variants not mapped in `iosLocales` will be written to the output directory with their variant suffix (e.g., `my_project___french.strings`)

**Example:** Basic configuration with matching output directory

```yaml theme={null}
iosLocales:
  - base: en
  - spanish: es
  - french: fr

outputs:
  - format: ios-strings
  - format: ios-stringsdict
```

This configuration will generate:

```text theme={null}
./ditto/
├── Ditto.swift
├── en.lproj/
│   ├── my_project.strings
│   ├── my_project.stringsdict
│   ├── components.strings
│   └── components.stringsdict
├── es.lproj/
│   ├── my_project.strings
│   ├── my_project.stringsdict
│   ├── components.strings
│   └── components.stringsdict
└── fr.lproj/
    ├── my_project.strings
    ├── my_project.stringsdict
    ├── components.strings
    └── components.stringsdict
```

**Example:** With root-level `outDir` and multiple iOS formats

```yaml theme={null}
outDir: "./my-text"

iosLocales:
  - base: en
  - spanish: es
  - french: fr

outputs:
  - format: ios-strings
  - format: ios-stringsdict
```

The `Ditto.swift` file and `.lproj` directories will be placed in `./my-text/`, with both `.strings` and `.stringsdict` files organized within each locale directory.

**Example:** Mismatched `outDir` with unmapped variants

When an output specifies a different `outDir` than where mapped locales are written, only variants not mapped in `iosLocales` will be written to that output's directory:

```yaml theme={null}
outDir: "./my-text"

variants:
  - id: base
  - id: spanish
  - id: french

iosLocales:
  - base: en
  - spanish: es

outputs:
  - format: ios-strings
    outDir: "./ios"
```

This will generate:

```text theme={null}
./my-text/
├── Ditto.swift
├── en.lproj/
│   ├── my_project.strings
│   ├── components.strings
│   └── ...
├── es.lproj/
│   ├── my_project.strings
│   ├── components.strings
│   └── ...
└── ios/
    ├── my_project___french.strings
    └── components___french.strings
```

In this case, `base` and `spanish` variants are mapped to iOS locales and written to `.lproj` directories in `./my-text/`, while the `french` variant (not mapped in `iosLocales`) is written to `./ios/` with its variant suffix.

### `iosLocalesOutDir`

A string. Overrides where the `Ditto.swift` driver file and `iosLocales`-mapped `.lproj` directories are written, independent of the root-level `outDir`. This is useful since iOS output typically needs to live in a different part of a codebase than other platforms' output (e.g., inside an Xcode project's source directory).

**Required: false**. If omitted, `Ditto.swift` and the `.lproj` directories are placed at the root-level `outDir` (`./ditto` if not configured), as described above.

**Example:**

```yaml theme={null}
outDir: "./ditto"
iosLocalesOutDir: "./MyApp/Localization"

iosLocales:
  - base: en
  - spanish: es

outputs:
  - format: ios-strings
```

This will generate the `Ditto.swift` file and `en.lproj`/`es.lproj` directories inside `./MyApp/Localization`, while any other configured outputs (e.g., `android`) still write to `./ditto`.

### `androidLocales`

An array that maps Ditto variant IDs to [Android locale-qualified resource directories](https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources). When used with the `android` output format, this configuration organizes locale files into the standard `values-<locale>/` resource directory structure.

**Required: false**. If omitted, Android formats will be generated without locale-specific directory organization.

Each entry in the array maps a variant ID to its corresponding locale qualifier (e.g., `en`, `es`, `fr`, `zh-rCN`). The variant ID must match a variant defined in your `variants` configuration, or be included via variants `id: all`.

Unlike `iosLocales`, there's no need to map `base` — Android already has a default resource directory (`values/`, with no locale qualifier) that the platform falls back to automatically, so base text is always written there without an explicit mapping entry.

**Behavior:**

* Variants mapped in `androidLocales` are written to `values-<locale>/` directories
* Base text is always written to the default `values/` directory
* Variants not mapped in `androidLocales` are written to the output directory with their variant suffix (e.g., `my_project___german.xml`)

**Example:**

```yaml theme={null}
androidLocales:
  - spanish: es
  - french: fr

outputs:
  - format: android
```

See [Files](/cli-reference/files#android-locale-directories) for the resulting directory structure.

### `androidLocalesOutDir`

A string. Overrides where `androidLocales`-mapped `values-<locale>/` directories (and the default `values/` directory) are written, independent of the root-level `outDir`. Like `iosLocalesOutDir`, this is useful for placing Android output in a different part of a codebase than other platforms' output (e.g., inside an Android module's `res/` directory).

**Required: false**. If omitted, the locale-organized directories are placed at the root-level `outDir` (`./ditto` if not configured).

**Example:**

```yaml theme={null}
outDir: "./ditto"
androidLocalesOutDir: "./app/src/main/res"

androidLocales:
  - spanish: es
  - french: fr

outputs:
  - format: android
```

### `outputs`

An array of output configurations. Each one will lead to its own set of files being generated in the specified location.

**format**: `json | android | ios-strings | ios-stringsdict` - Required. The file format for the exported data. For details on each format's file structure, see the [Files documentation](/cli-reference/files).

**framework**: `i18next | vue-i18n | icu | arb` - Optional. Only applies to `json`-format outputs. `i18next` and `vue-i18n` generate additional helper files to assist in integrating the Ditto data into the specific framework. `icu` and `arb` instead change the JSON strings themselves into [ICU message format](https://icu.unicode.org/home) syntax (`arb` additionally produces [ARB](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification)-style metadata). See [frameworks](/cli-reference/files#framework-specific-files) section for more details.

All top-level fields can also be defined within each provided output, with the exception of `iosLocales`, `iosLocalesOutDir`, `androidLocales`, and `androidLocalesOutDir`. Providing a value at the output level will override any fields defined at the global level.

For example, if `outDir` is defined at the root of the config to be `./customPath1`, and is set to `./customPath2` within an output, the files generated by that specific output will be created within the `./customPath2` directory, while any other outputs will go to `./customPath1`.

Here is a much more complex example:

```yaml theme={null}
projects:
  - id: my-project
components:
  folders:
    - id: root
      excludeNestedFolders: true
variants: []
richText: html
outDir: ./ditto/top
outputs:
  - format: json
  - format: json
    framework: vue-i18n
    variants:
      - id: french
    outDir: ./ditto/french
  - format: json
    framework: i18next
    outDir: ./ditto/i18next
    type: module
    projects: []
    components:
      - id: special-folder
    variants:
      - id: french
      - id: spanish
      - id: base
    richText: false
```

The config file above will work as follows:

1. There are three outputs specified, so the query will run three times, each with different filters
2. The first output has no special overrides, so it will create all the `json` files for the top-level query — just the base text from the project with Developer ID “my-project” along with top-level components. These files will be saved to the directory `./ditto/top` with rich text enabled.
3. The second output contains some overrides. This will still apply the same projects and components filters, but will request the french variant and save its files to `./ditto/french` with rich text enabled. It will also create the `vue-i18n` driver files.
4. The third output overrides all of the filters. It will query for the french, spanish and base variants from all projects and all components inside the folder with Developer ID "special-folder", including all components in any nested folders. It will also create the `i18next` driver file(s). All of these files will be saved to the `./ditto/i18next` directory and will only include plaintext.

```shell Output files for example config theme={null}
.
└── ditto/
    └── top/
        ├── my_project__base.json
        └── components__base.json
    └── french/
        ├── my_project__french.json
        ├── components__french.json
        ├── variables.json
        └── index.js
    └── i18next/
        ├── my_project__french.json
        ├── my_project__base.json
        ├── my_project__french.json
        ├── my_project__spanish.json
        ├── my_other_project__base.json
        ├── my_other_project__french.json
        ├── my_other_project__spanish.json
        ├── components__base.json
        ├── components__french.json
        ├── components__spanish.json
        ├── variables.json
        └── index.js
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.