# Ditto > Developer documentation for Ditto, the source of truth for product text. Scan a codebase to import its strings, then connect coding agents through the Ditto MCP server, link Figma files, and pull localized string files with the CLI. > ## 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. # GitHub Action Source: https://developer.dittowords.com/additional-tools/gh-action Automatically create pull requests with the latest text updates ## Overview Our GitHub Action utilizes the Ditto CLI to automatically create pull requests on GitHub with the latest text updates in any repository where the action is triggered. Check it out [here](https://github.com/marketplace/actions/ditto-github-action). ![Ditto Github Action](https://mintcdn.com/ditto/WgRtoF9rLXWv6hNh/images/gh-action.png?fit=max\&auto=format\&n=WgRtoF9rLXWv6hNh\&q=85\&s=d2068ed61df1595ca1378765113926fb) ## Additional Information For setup instructions and example workflows, see the instructions listed [on the Github Action](https://github.com/marketplace/actions/ditto-github-action). For instructions on configuring the Ditto CLI (including formats and selecting the set of text to sync), see our [CLI documentation](/cli-reference/authentication). # GitHub PR Review Bot Source: https://developer.dittowords.com/additional-tools/github-pr-review-bot Automatically review pull requests for style guide violations and text re-use suggestions ## Overview The Ditto GitHub PR Review Bot automatically reviews pull request text and leaves inline suggestions directly in GitHub. When triggered, the bot checks the diff for: * **Style guide violations:** text that doesn't match your active Ditto style guide rules * **Text re-use opportunities:** text that already exists in Ditto and could be reused instead of introducing new text ![GitHub bot review example](https://mintcdn.com/ditto/pv6I3Jp8MPYLQ3Dm/images/github-bot-review-example.png?fit=max\&auto=format\&n=pv6I3Jp8MPYLQ3Dm\&q=85\&s=c4ca85503e8da2c04c19d63ca3ff18c3) ## Prerequisites Before setting up the integration, make sure you have: * **Style guides enabled** in your Ditto workspace with at least one active rule. The bot uses these to evaluate PR text, so without them there's nothing to check against. * **GitHub admin access** for the account or organization you want to install the app on. If you're a GitHub org member (not a GitHub admin), you can still request installation, but a GitHub admin with Ditto access will need to approve it before the bot activates. ## Installation **1. Open the GitHub integration in Ditto** In your Ditto workspace, go to **Integrations** and click the **GitHub** card. ![GitHub integration installation card](https://mintcdn.com/ditto/TGur27g5aZb9YsZN/images/github-integration-not-installed.png?fit=max\&auto=format\&n=TGur27g5aZb9YsZN\&q=85\&s=456dac01e177d53bd2cba92284ebf246) **2. Install Ditto's GitHub App** Click **Install Ditto's GitHub App**. You'll be redirected to GitHub to complete the installation. For more details on this flow, see [GitHub's documentation on installing GitHub Apps](https://docs.github.com/en/apps/using-github-apps/installing-a-github-app-from-a-third-party). Choose the account or organization where you want to install the bot: * **GitHub org member:** submitting will send an approval request to your GitHub org's admins. You'll finish linking the app to Ditto in the next step, once a GitHub admin approves the request. * **GitHub org admin:** you can install directly. ![GitHub installation repository access card](https://mintcdn.com/ditto/pv6I3Jp8MPYLQ3Dm/images/github-installation-repository-permissions.png?fit=max\&auto=format\&n=pv6I3Jp8MPYLQ3Dm\&q=85\&s=aa397e1c6bd8a2ae47ad03e5d6088f71) Select which repositories you want the bot to have access to, then confirm the following permissions: * Read access to **code** and **metadata** * Read and write access to **issues** and **pull requests** The bot will **never write or modify code**; it only comments on pull requests. Click **Install** (or **Request**, for GitHub org members) to confirm. **3. Confirm the connection in Ditto** After installing, you'll be redirected back to Ditto. * **GitHub org member:** the GitHub integration will show as **Pending** while the request awaits GitHub admin approval. Once a GitHub admin approves the install, comment **`/ditto connect`** on any pull request in a covered repository to finish linking the app to your Ditto workspace. * **GitHub org admin:** the GitHub integration will show as **Connected** and the bot is ready to use. ![GitHub integration successful install card](https://mintcdn.com/ditto/TGur27g5aZb9YsZN/images/github-integration-connected.png?fit=max\&auto=format\&n=TGur27g5aZb9YsZN\&q=85\&s=b22dbec041be320be86aea9fda191425) ## Configuration Once connected, you can configure the bot's behavior under **Settings > Connections** in Ditto. ![GitHub app configuration in Ditto](https://mintcdn.com/ditto/TGur27g5aZb9YsZN/images/github-integration-configuration.png?fit=max\&auto=format\&n=TGur27g5aZb9YsZN\&q=85\&s=1fb4b098bea4eee1607747aa0b69e5ce) | Setting | Description | | - | - | | **PR Review Tag** | The GitHub label that triggers a review. Defaults to `ditto`. Change this to any label name that fits your workflow. | | **Style Guides** | Select which style guides the bot should check against. Only the selected guides will be used during review. | ## Using the Bot Once installed and configured, a review can be triggered in two ways: * **Label:** apply your configured label to the PR * **Comment:** post `/ditto review` as a comment on the PR **1. Open a pull request** Create or open a pull request in a repository where the bot is installed. **2. Trigger a review** Either apply your configured label to the PR, or post a **`/ditto review`** comment. Both will kick off a review. ![Adding configured Ditto review label to GitHub PR](https://mintcdn.com/ditto/pv6I3Jp8MPYLQ3Dm/images/github-adding-label.png?fit=max\&auto=format\&n=pv6I3Jp8MPYLQ3Dm\&q=85\&s=7285dddc19479050d3d57979a439f700) **3. Review the bot's suggestions** In a few moments, the Ditto bot will comment on the PR with a summary of its findings, including any style guide violations and existing text in Ditto that could be reused. If nothing is flagged, it will let you know the text looks good. **4. Accept or ignore suggestions** You can commit suggestions directly from GitHub's PR interface. The changes will be associated with the user that accepts the suggestions. If the PR has the triggering label applied, the bot will automatically re-review on each new commit and won't re-flag suggestions that have already been addressed. ![Committing a Ditto suggestion in GitHub](https://mintcdn.com/ditto/pv6I3Jp8MPYLQ3Dm/images/github-committing-suggestion.png?fit=max\&auto=format\&n=pv6I3Jp8MPYLQ3Dm\&q=85\&s=abd95bfb50bb8394b3270f0e9561d5b6) # Webhooks Source: https://developer.dittowords.com/additional-tools/webhooks Webhooks enable HTTP payloads to be sent to an external service when data in Ditto is changed. ## Introduction Webhooks allow you to receive notifications when changes are made to text stored in Ditto. You can set up a webhook by providing us a URL that you control, and telling us which events you want sent to that endpoint. ## Setup You can configure webhooks for your workspace in the **Webhooks** section of your team's [developer integrations](https://app.dittowords.com/developers/webhooks). ### Signing Key Prior to creating any webhook endpoints, it's essential to generate and securely store a **signing key**. This key plays a crucial role in appending a signature to all outbound webhook payloads, allowing your server to authenticate that a particular request originates from Ditto. > [accordion] Signing key requirements > > Signing keys must be a minimum of 16 characters long. We also recommend that they be randomly generated and stored in a secure environment. > [accordion] Generating a signing key > > The **Generate a key for me** button will generate a random signing key and copy it to your clipboard. > > Alternatively, you can easily generate a random string of characters to serve as a signing key using built-in command line utilities. For example, on most Unix systems, `uuidgen` is available: > > ```bash theme={null} > $ uuidgen > 29D634FF-7831-4468-AA70-0C4997031777 > ``` > [accordion] Rotating your signing key > > When changing your signing key, it’s recommended that you anticipate the key change on your server to avoid missed events. > > **Signing key rotation without missed events:** > > 1. Generate a new signing key. > 2. Update your webhook server’s code to temporarily accept requests signed with either the old signing key or the new signing key. > 3. Save the new signing key in Ditto. > 4. Update your webhook server’s code to only accept requests using the new signing key. Once you’ve saved your key in Ditto, it is encrypted and cannot be directly accessed. However, you can preview the last four characters of your saved key. ### Creating a Webhook You can open the modal for creating webhooks via the **Create Webhook** button. Each webhook must be assigned **a name, a URL, and have at least one trigger activated**. While there are no strict guidelines for URLs, it is strongly advised that they direct to secure endpoints utilizing HTTPS. Before saving a webhook, the URL must be validated with a test request. See [Example Webhook Server](#example-webhook-server) below for details on configuring your webhook server to process a test request. ## Request Headers Every request sent to a webhook endpoint will have the following three headers included: * `x-ditto-request-id` — a v4 uuid uniquely identifying a given request. This identifier can be cross-referenced 1:1 with delivery history exported from inside of Ditto. * `x-ditto-timestamp` — a Unix timestamp identifying the time at which the request was made. This timestamp is included in the signature and can therefore be used to guard against replay attacks. * `x-ditto-signature` — a signature (HMAC-SHA256) created using your workspace’s webhook signing key, by concatenating the request ID, the timestamp of the request and the request body (each separated by a period). The JavaScript code below demonstrates how the values for these headers are generated: ```jsx theme={null} const crypto = require("crypto"); const uuid = require("uuid"); const requestId = uuid.v4(); const timestamp = new Date().getTime(); const strBody = JSON.stringify(request.body); const signatureData = `${requestId}.${timestamp}.${strBody}`; const signature = crypto.createHmac("sha256", signingKey).update(signatureData).digest("hex"); const headers = { "x-ditto-request-id": requestId, "x-ditto-timestamp": timestamp, "x-ditto-signature": signature, }; ``` ## Request Validation Ditto expects all webhook consumers to return a status code between 200 and 299 to indicate successful payload processing. To enhance your security, it is strongly advised to perform the following actions on your server prior to accepting a webhook payload and issuing a success response: 1. **Validate the signature included in the `x-ditto-signature` header.** 1. Concatenate the request ID, the timestamp, and a string representation of the request body into a single string, with each component separated by the `.` character `${requestId}.${timestamp}.${JSON.stringify(req.body)}`; 2. Using your signing key, sign the concatenated value using HMAC-256, encoded as hex. 3. Validate that the signed value you’ve generated matches the value in the `x-ditto-signature` header. If it does not match, then the request is not valid and an error response should be returned. 2. **(Optional) Ensure idempotence by tracking request IDs.** 1. After validating the signature of a request, track the request ID in a persistent data store (like Redis). 2. Before processing new requests, check your data store to see if a given request has been seen before; if it has already been processed, then the request is not valid and an error response should be returned. 3. **(Optional) Validate the timestamp included in the `x-ditto-timestamp` header.** 1. Decide on a time window (e.g. 6 minutes to allow for retries) outside of which you will not accept requests. 2. Check that the time elapsed since the timestamp in the x-ditto-timestamp header does not exceed your established time window. If it exceeds the time window, then the request is not valid and an error response should be returned. See [Example Webhook Server](#example-webhook-server) below for example code for request validation in a Node.js environment. ## Error Handling If Ditto receives an error response from your server, it will retry sending a payload 3 additional times: * 1 minute following the initial request * 2 minutes following the first retry * 3 minutes following the second retry. If a given endpoint returns a non-success response for more than 10 requests in a 10 minute window, the webhook will be automatically disabled. If a webhook has been disabled, it can be re-enabled by revalidating with a test request. ## Event Reference All request bodies sent to webhook endpoints will have `event` and `data` properties. ### Text Item Creation ```tsx theme={null} { "event": "TextItem_Created", "data": { "textItemId": "text-item-id", "projectId": "project-id", "text": "This is some product copy.", "integrated": true, } } ``` ### Text Item Deletion ```tsx theme={null} { "event": "TextItem_Deleted", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": true, } } ``` ### Text Item Developer ID Change ```tsx theme={null} { "event": "TextItem_Developer_ID_Changed", "data": { "developerIdBefore": "text-item-id-old", "developerIdAfter": "text-item-id-new", "projectId": "project-id", "integrated": true, } } ``` ### Text Item Integration Status Change ```tsx theme={null} { "event": "TextItem_Integrated_State_Changed", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": false, } } ``` ### Text Item Base Text Change ```tsx theme={null} { "event": "TextItem_Base_Text_Changed", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": true, "textBefore": "This is some product copy.", "textAfter": "This is some updated product copy." } } ``` ### Text Item Status Change ```tsx theme={null} { "event": "TextItem_Status_Changed", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": true, "statusBefore": "TODO", "statusAfter": "REVIEW" } } ``` ### Text Item Tags Change ```tsx theme={null} { "event": "TextItem_Tags_Changed", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": true, "tagsBefore": ["old-tag"], "tagsAfter": ["new-tag", "another-tag"] } } ``` ### Text Item Variants Change ```tsx theme={null} { "event": "TextItem_Variants_Changed", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": true, "variantIdsBefore": ["variant-1"], "variantIdsAfter": ["variant-1", "variant-2"] } } ``` ### Text Item Variant Text Change ```tsx theme={null} { "event": "TextItem_Variant_Text_Changed", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": true, "variantId": "variant-id", "variantTextBefore": "Original variant text", "variantTextAfter": "Updated variant text" } } ``` ### Text Item Variant Status Change ```tsx theme={null} { "event": "TextItem_Variant_Status_Changed", "data": { "textItemId": "text-item-id", "projectId": "project-id", "integrated": true, "variantId": "variant-id", "statusBefore": "TODO", "statusAfter": "REVIEW" } } ``` ## Example Webhook Server Here is an annotated code sample showing a simple Node.js server to process webhook events from Ditto: ```tsx server.js theme={null} const express = require("express"); const crypto = require("crypto"); const app = express(); // Payloads from Ditto will always be JSON, so ensure your server // is configured to parse JSON bodies app.use(express.json()); // The port for the webhook test server to run on const port = process.env.PORT || 4321; // This is the key that you've provided const WEBHOOK_SIGNING_KEY = process.env.WEBHOOK_SIGNING_KEY || "xxxxxxx"; // Webhook events will always be sent via POST request app.post("/", (req, res) => { console.log(`--------\nReceived event: '${req.body.event}'`); const { "x-ditto-request-id": requestId, "x-ditto-timestamp": timestamp, "x-ditto-signature": signature, } = req.headers; const threeMinutesAgo = new Date().getTime() - 1000 * 60 * 3; // (Optional) // Prevent replay attacks by verifying that the timestamp // of the webhook is within a reasonable threshold if (timestamp < threeMinutesAgo) { console.error("❌ Payload is expired"); return res.status(400).send(); } else { console.log(`✅ Timestamp (${timestamp}) is within past 3 minutes`); } // Get the input data by concatenating three things, with '.' separating them: // - the request id // - the timestamp // - the stringified request body const inputData = `${requestId}.${timestamp}.${JSON.stringify(req.body)}`; // Compute a signature using your webhook signing key and the // input data const computedSignature = crypto.createHmac("sha256", WEBHOOK_SIGNING_KEY).update(inputData).digest("hex"); // Validate that the payload is from Ditto and hasn't been tampered // with by checking that the signature from the header matches your // computed signature if (signature !== computedSignature) { console.error("❌ Invalid signature"); return res.status(400).send(); } else { console.log(`✅ Signature is valid`); } // (Optional) // Ensure we never process the same request more than once. // // const key = `ditto-webhook_${requestId}`; // const value = await redis.get(key); // if (value) { // console.error(`❌ Request ${requestId} has already been processed`); // return; // } // // await redis.set(key, true, "EX", 0); console.log(`Payload: ${JSON.stringify(req.body, null, 2)}`); // do something with the payload! return res.status(200).send("Received successfully!"); }); app.listen(port, () => console.log(`✍️ Ditto webhook test server listening @ http://localhost:${port}`)); ``` # Fetch activity Source: https://developer.dittowords.com/api-reference/activity/get-activity openapi.json get /activity Returns a paginated list of activity items (change events) in your workspace, optionally filtered by project, text item, library component, or entity type. # Ditto API authentication Source: https://developer.dittowords.com/api-reference/authentication Create a Ditto API key and pass it in the Authorization header. Covers the token format, key management, and how the CLI and MCP log in instead. Requests to the Ditto API authenticate with an API key in the `Authorization` header. API keys give programmatic access to all data in your workspace, so keep them secret: never commit them or ship them in client-side code. ## Create an API key 1. In Ditto, open [**Developers**](https://app.dittowords.com/developers). If the page offers to **Enable Developer Integrations**, enable them first. Plans that do not include developer integrations can start a trial from this page. 2. Under **API keys**, click **+ Create API key**. 3. Give the key a name and click **Create**. 4. Copy the key now. It is not shown again after you close the dialog. Keys are managed from the same page. Creating one key per system (CI, an agent, an integration) makes it easy to rotate or remove one without affecting the others. ## Authenticate a request Pass the key in the `Authorization` header with the `token` scheme: ```text theme={null} Authorization: token ``` Example: ```sh theme={null} curl "https://api.dittowords.com/v2/projects" \ --header "Authorization: token " ``` ## The CLI, the MCP server, and agents API keys are for calling the REST API directly and for CI. The developer-facing tools log in with your Ditto account instead, so no key is needed on a developer's machine: * The **CLI** logs in through your browser with `npx @dittowords/cli login`. Only a CI job without a browser needs an API key, set as `DITTO_TOKEN`. See [Install and log in](/cli-reference/authentication). * The **MCP server** logs you in with OAuth the first time a client connects. It accepts `Authorization: token ` for CI and headless clients. See [Connect the Ditto MCP](/guides/connect-the-mcp#api-key-for-ci-and-headless-clients). * The **Ditto Specs CLI** has no login flow yet and reads `DITTO_TOKEN`. See [Specs CLI setup](/ditto-specs-cli-reference/setup). # Create bases Source: https://developer.dittowords.com/api-reference/bases/create-bases openapi.json post /bases Creates one or more bases in your workspace. Pass `githubRepo` to attach a GitHub repository to a base as it's created. The repository must already be available to Ditto's GitHub app in your workspace, and each repository can only be attached to one base. Attaching a repository also kicks off an initial scan of it. BETA This endpoint is part of a feature that is still in development and will not be available until the feature has been released. # Fetch a base Source: https://developer.dittowords.com/api-reference/bases/get-base openapi.json get /bases/{baseId} Returns a single base in your workspace, with its text item count, its folders, and the projects connected to it. BETA This endpoint is part of a feature that is still in development and will not be available until the feature has been released. # Fetch bases Source: https://developer.dittowords.com/api-reference/bases/get-bases openapi.json get /bases Returns a list of the bases in your workspace. A base holds the text items scanned out of a codebase; projects connected to a base draw their text items from it. BETA This endpoint is part of a feature that is still in development and will not be available until the feature has been released. # Update a base Source: https://developer.dittowords.com/api-reference/bases/patch-bases openapi.json patch /bases/{baseId} Updates a base in your workspace. At least one of `name`, `newDeveloperId`, or `githubRepo` must be provided. Changing a base's Developer ID changes how it's addressed in future requests, so any stored references to the old ID need updating too. BETA This endpoint is part of a feature that is still in development and will not be available until the feature has been released. # Create component folders Source: https://developer.dittowords.com/api-reference/component-folders/create-component-folders openapi.json post /componentFolders Creates one or more folders in the component library. # Delete component folders Source: https://developer.dittowords.com/api-reference/component-folders/delete-component-folders openapi.json delete /componentFolders Deletes folders from the component library. # Fetch component folders Source: https://developer.dittowords.com/api-reference/component-folders/get-component-folders openapi.json get /componentFolders Returns all folders in your component library, with their developer IDs, names, and parent folder (by developer ID; null when the folder sits directly under the library root). Folders are a separate namespace from project folders. # Update component folders Source: https://developer.dittowords.com/api-reference/component-folders/patch-component-folders openapi.json patch /componentFolders Updates a set of component folders. Each folder is identified by its developer ID. Only provided fields are modified — omitted fields remain unchanged. # Create components Source: https://developer.dittowords.com/api-reference/components/create-components openapi.json post /components Creates one or more library components in your workspace. The operation is all-or-nothing: if any component is invalid, none are created. # Delete components Source: https://developer.dittowords.com/api-reference/components/delete-components openapi.json delete /components Deletes library components from the workspace by their developer IDs. # Discard unpublished component changes Source: https://developer.dittowords.com/api-reference/components/discard-unpublished-component-changes openapi.json delete /components/staged Discards the unpublished changes on library components by their Developer IDs. The components' published values, versions, and instances are unchanged. Components with no unpublished changes are left as they are and returned in `skippedIds`. The operation is all-or-nothing: if any Developer ID does not match a component, nothing is discarded. Only available in workspaces that have opted into library component versioning. # Export components Source: https://developer.dittowords.com/api-reference/components/export-components openapi.json get /components/export Returns components in your workspace in the provided format. # Fetch components Source: https://developer.dittowords.com/api-reference/components/get-components openapi.json get /components Returns a list of components in your workspace. # Fetch unpublished component changes Source: https://developer.dittowords.com/api-reference/components/get-unpublished-component-changes openapi.json get /components/staged Returns the components in your workspace that have unpublished changes, with those changes applied. Only available in workspaces that have opted into library component versioning. # Link text items to a component Source: https://developer.dittowords.com/api-reference/components/link-components openapi.json patch /components/link Links one or more existing text items to an existing library component by developer ID, so they share the component's text going forward. Each linked text item takes on the component's current text, variables, variants, and other fields. Text items already linked to a different component are skipped. # Update components Source: https://developer.dittowords.com/api-reference/components/patch-components openapi.json patch /components Updates a set of components' base or variant text, plural forms, tags, status, assignee, notes, character limit, integrated, and variables. Each component is identified by its Developer ID. Only provided fields are modified — omitted fields remain unchanged. A Developer ID can appear at most once in `updates`. A request that repeats a Developer ID is rejected with a 400 rather than silently applying the last write and discarding the rest — combine the changes into a single update instead. In workspaces that have opted into library component versioning, a component with unpublished changes can't have its `text`, `plurals`, `tags`, `notes`, or `characterLimit` updated directly, because publishing those changes would overwrite the unpublished updates. # Publish a text item to the library Source: https://developer.dittowords.com/api-reference/components/publish-component openapi.json post /components/publish Publishes an existing project text item to the component library, turning it into a reusable library component. The text item's current text, variables, variants, and other fields are carried over to the new component, and the text item becomes its first linked instance. The text item must not already be a library instance. The new component keeps the text item's existing Developer ID. In workspaces that have opted into library component versioning, the new component instead gets a new Developer ID generated from its name, and the text item keeps its own. # Publish component changes Source: https://developer.dittowords.com/api-reference/components/publish-component-changes openapi.json post /components/staged/publish Publishes the unpublished changes on library components by their Developer IDs. Components with no unpublished changes are left as they are and returned in `skippedIds`. The operation is all-or-nothing: if any Developer ID does not match a component, nothing is published. Only available in workspaces that have opted into library component versioning. # Save unpublished component changes Source: https://developer.dittowords.com/api-reference/components/save-unpublished-component-changes openapi.json patch /components/staged Saves changes to library components without publishing them. The changes are not available to instances until they're published. The operation is all-or-nothing: if any update is invalid, nothing is saved. Only available in workspaces that have opted into library component versioning. # Unlink text items from a component Source: https://developer.dittowords.com/api-reference/components/unlink-components openapi.json patch /components/unlink Unlinks one or more text items from their library component by developer ID. Each unlinked text item keeps its current text but gets a freshly generated developer ID, and future edits to the library component no longer affect it. Every text item linked to a component shares that component's developer ID with its sibling instances, so a given developer ID unlinks every text item that currently carries it, not just one. Pass `projectId` to scope the unlink to only the instances in one project, leaving instances elsewhere untouched. # Upgrade component instances Source: https://developer.dittowords.com/api-reference/components/upgrade-component-instances openapi.json patch /components/upgrade Brings library component instances in projects up to date with the latest published version of their component, by the text items' Developer IDs. Instances that are already up to date are returned in `unchangedTextItemIds`. The operation is all-or-nothing: if any instance can't be upgraded due to an error condition, none are. Only available in workspaces that have opted into library component versioning. # Create project blocks Source: https://developer.dittowords.com/api-reference/project-blocks/create-project-blocks openapi.json post /projectBlocks Creates one or more blocks in a Ditto project. New blocks are appended to the end of the project. # Delete project blocks Source: https://developer.dittowords.com/api-reference/project-blocks/delete-project-blocks openapi.json delete /projectBlocks Deletes blocks from a Ditto project. # Fetch project blocks Source: https://developer.dittowords.com/api-reference/project-blocks/get-project-blocks openapi.json get /projectBlocks Returns the blocks in a Ditto project, in display order. Blocks group text items within a project. # Update project blocks Source: https://developer.dittowords.com/api-reference/project-blocks/patch-project-blocks openapi.json patch /projectBlocks Updates a set of blocks in a Ditto project. Each block is identified by its developer ID. Only provided fields are modified — omitted fields remain unchanged. # Create a project Source: https://developer.dittowords.com/api-reference/projects/create-projects openapi.json post /projects Creates a new Ditto project in your workspace. Projects hold project-scoped text items, as opposed to library components which are reusable across projects. # Fetch projects Source: https://developer.dittowords.com/api-reference/projects/get-projects openapi.json get /projects Returns a list of Ditto projects in your workspace. # Update a project Source: https://developer.dittowords.com/api-reference/projects/patch-projects openapi.json patch /projects/{projectId} Updates a project's name, Developer ID, and/or connected base. The project is identified by its current Developer ID in the URL path. Only provided fields are modified. # Publish to base Source: https://developer.dittowords.com/api-reference/projects/publish-project openapi.json post /projects/{projectId}/publish Publishes a project's text items to the base it's connected to. Pass `textItemIds` to publish specific text items, or omit it to publish every text item that is Modified or New. Text items in Conflict (changed in both the project and the base since they last synced) are only acted on when named in `conflictResolutions`: `project` publishes the project's version to the base, and `base` reverts the project's text item to the base's version. Text items that can't be published are skipped rather than failing the request; the response lists each one with the reason. BETA This endpoint is part of a feature that is still in development and will not be available until the feature has been released. # Update text items from base Source: https://developer.dittowords.com/api-reference/projects/update-text-items-from-base openapi.json post /projects/{projectId}/updateFromBase Updates a project's text items to their base text item's version. Each text item must be Outdated (only the base has changed) or in Conflict (both the project and the base have changed). Updating a text item in Conflict discards the project's changes to it. Text items in any other state are skipped rather than failing the request; the response lists each one with the reason. BETA This endpoint is part of a feature that is still in development and will not be available until the feature has been released. # Fetch statuses Source: https://developer.dittowords.com/api-reference/statuses/get-statuses openapi.json get /statuses Returns the statuses which text items and library components in the workspace can be set to, in workflow order — from earliest (a first draft) to latest (ready to ship). A workspace can rename the default statuses or replace the set entirely, so none of the defaults are guaranteed to exist. Read the available statuses here before setting a status or filtering by one rather than assuming any particular key is valid. # Create style guide rules Source: https://developer.dittowords.com/api-reference/styleguides/create-styleguide-rules openapi.json post /styleguides/rules Creates one or more rules in a style guide section. The operation is all-or-nothing: if any rule is invalid, none are created. # Create a style guide Source: https://developer.dittowords.com/api-reference/styleguides/create-styleguides openapi.json post /styleguides Creates a new style guide in your workspace. Use POST /styleguides/rules afterward to add rules to a section. # Delete style guide rules Source: https://developer.dittowords.com/api-reference/styleguides/delete-styleguide-rules openapi.json delete /styleguides/rules Deletes style guide rules by their Developer IDs. This cannot be undone. The operation is all-or-nothing: if any ruleId does not exist, none are deleted. # Get style guides Source: https://developer.dittowords.com/api-reference/styleguides/get-styleguides openapi.json get /styleguides Returns the style guides created in the workspace, with their sections and rules. Each rule includes its developer ID, which can be used with PATCH/DELETE /styleguides/rules. The built-in default style guide is not included. # Update style guide rules Source: https://developer.dittowords.com/api-reference/styleguides/patch-styleguide-rules openapi.json patch /styleguides/rules Updates a set of style guide rules, each identified by its Developer ID. Only provided fields are modified. The operation is all-or-nothing: if any update is invalid, none are applied. # Update a style guide Source: https://developer.dittowords.com/api-reference/styleguides/patch-styleguides openapi.json patch /styleguides Updates a style guide's name, description, enabled-by-default setting, or sections. Each style guide is identified by its developer ID. Only provided fields are modified. # Create text items Source: https://developer.dittowords.com/api-reference/text-items/create-text-items openapi.json post /textItems Creates a set of text items in a project. Supports plural forms, variant text, variable placeholders, notes, character limits, tags, status, assignee, and placing the item inside a block. # Create text items from a base Source: https://developer.dittowords.com/api-reference/text-items/create-text-items-from-base openapi.json post /textItems/fromBase Copies text items from a base into a project connected to that base. The target project must already be connected to a base, and every ID in `baseTextItemIds` must belong to that base. Each base text item can only be copied into a project once; the request fails if any of them is already in the project. By default the new text items will be placed in the project root. Pass `blockId` to place them in an existing block, or `newBlockName` to create a block for them. The two are mutually exclusive. BETA This endpoint is part of a feature that is still in development and will not be available until the feature has been released. # Delete text items Source: https://developer.dittowords.com/api-reference/text-items/delete-text-items openapi.json delete /textItems Deletes text items by their developer IDs. # Export text items Source: https://developer.dittowords.com/api-reference/text-items/export-text-items openapi.json get /textItems/export Returns text items in your workspace in the desired export format. # Fetch text items Source: https://developer.dittowords.com/api-reference/text-items/get-text-items openapi.json get /textItems Returns a list of text items in your workspace. # Link Figma nodes to text items Source: https://developer.dittowords.com/api-reference/text-items/link-figma-nodes openapi.json post /textItems/linkFigmaNodes Links Figma text nodes to existing text items in a Figma-linked project, so those nodes become Figma instances of their text items. Takes a mapping of text item developer IDs to Figma node IDs. The nodes must already have been synced to Ditto through the Ditto Figma plugin. Get node IDs from Figma (its REST API or MCP server). Pass `branchId` to link nodes inside a branch file; if not provided, nodes are looked up in the project's main Figma file. A max of 500 distinct node IDs are allowed per request across all link mappings. The link is recorded in Ditto right away, but Ditto writes to a Figma file only from the plugin. So the Figma layer doesn't reflect the link — and later edits to the text item don't reach that layer — until someone next opens the Ditto Figma plugin on the file. Unknown text item IDs, or a node ID mapped to more than one text item, reject the whole request. Otherwise nodes are handled independently: the response reports an outcome per node ID. `linked` indicates a successful outcome. `already_linked` indicates the node is already linked to the requested text item, and `linked_to_other_text_item` indicates the node's already linked to a different text item. Outcomes `not_found` (sync the file from the plugin, then retry), `not_text_node`, `wrong_file_or_branch`, or `main_component_text_node` (link instance text nodes directly, or link the main component's text from the Figma plugin) all indicate the node was not linked. # Update text items Source: https://developer.dittowords.com/api-reference/text-items/patch-text-items openapi.json patch /textItems Updates a set of text items' base or variant text, plural forms, tags, status, assignee, notes, character limit, integrated, variables, and block placement. Each text item is identified by its developer ID. Only provided fields are modified — omitted fields remain unchanged. # Update text items Source: https://developer.dittowords.com/api-reference/text-items/update-text-items openapi.json put /textItems Updates a set of text items' base or variant text, tags, status, assignee, and plural forms. Deprecated — use PATCH /textItems instead. # Create variables Source: https://developer.dittowords.com/api-reference/variables/create-variables openapi.json post /variables Creates variables in your workspace. The shape of `data` depends on `type`: string and number take an `example` and an optional `fallback`, hyperlink takes `text` and `url`, list takes an array of strings, and map takes an object of string keys to string values. Either every variable is created or none is. # Delete variables Source: https://developer.dittowords.com/api-reference/variables/delete-variables openapi.json delete /variables Permanently deletes variables from your workspace. This can't be undone, and either every variable in the request is deleted or none is. # Fetch variables Source: https://developer.dittowords.com/api-reference/variables/get-variables openapi.json get /variables Returns a list of the variables in the workspace. # Update variables Source: https://developer.dittowords.com/api-reference/variables/patch-variables openapi.json patch /variables Renames variables or changes their type or value. Either every update is applied or none is. # Create variants Source: https://developer.dittowords.com/api-reference/variants/create-variants openapi.json post /variants Creates one or more variants in the workspace. A variant is a named alternative that text items and library components can each have their own text for — most often a locale to translate into, but also a tone or brand variation. Creating a variant adds no text on its own; use the text item endpoints to set a text item's text for that variant. Read the developer IDs off the response rather than assuming them — a developer ID you don't specify is generated from the name. # Delete variants Source: https://developer.dittowords.com/api-reference/variants/delete-variants openapi.json delete /variants Deletes variants from the workspace by their developer IDs. This can't be undone. # Fetch variants Source: https://developer.dittowords.com/api-reference/variants/get-variants openapi.json get /variants Returns a list of the variants in the workspace. # Update variants Source: https://developer.dittowords.com/api-reference/variants/patch-variants openapi.json patch /variants Updates one or more variants in the workspace, identified by their current developer IDs. Only provided fields are modified. The whole batch is rejected if any update is invalid. # Install and log in to the Ditto CLI Source: https://developer.dittowords.com/cli-reference/authentication Install the Ditto CLI and log in with your Ditto account so you can scan a codebase and pull string files. ## What the CLI does The Ditto CLI is the command-line surface for two jobs: importing the strings already in a codebase (`scan`) and writing Ditto's text to disk as string files your app loads (`pull`). It wraps the [Ditto API](/api-reference/authentication), so anything the CLI does can also be done with direct API calls. Teams run it locally and in CI. ## Install > [!NOTE] > > **Node.js 20 or higher** is required as of version `5.6.0`. You can run every command with `npx` and install nothing: ```bash theme={null} npx -y @dittowords/cli@latest --help ``` To pin a version in a project, install it as a development dependency: ```bash npm theme={null} npm i --save-dev @dittowords/cli ``` ```bash yarn theme={null} yarn add --dev @dittowords/cli ``` Then `npx @dittowords/cli ` uses the installed version. ## Log in Log in through your browser: ```bash theme={null} npx -y @dittowords/cli@latest login ``` A browser tab opens for you to log in to Ditto and approve access. The session is saved to `$HOME/.config/ditto`, so you only do this once per machine. There is no API key to create or paste, and commands act as **you**, with your Ditto account's permissions. To sign out, run [`logout`](/cli-reference/commands#logout). > [!NOTE] > > `login` and `logout` require version `5.7.0` or higher. ## In CI, or anywhere without a browser Browser login is the recommended way to authenticate, including for coding agents working on your machine (the agent tells you when the tab is about to open). Use an API key only where no browser is available, such as a CI job, and set it in the `DITTO_TOKEN` environment variable. > [accordion] Use an API key in CI > > **1. Create an API key (a workspace admin does this once)** > > The steps are in [API authentication](/api-reference/authentication#create-an-api-key). > > **2. Set the variable** > > ```bash theme={null} > export DITTO_TOKEN='' > ``` > > In CI, store the key as a secret and expose it as `DITTO_TOKEN`. The [Ditto Specs CLI](/ditto-specs-cli-reference/setup) reads the same variable, and the [MCP server](/guides/connect-the-mcp#api-key-for-ci-and-headless-clients) accepts the key as an `Authorization: token` header. `DITTO_TOKEN` takes precedence over a saved login, so a machine with both keeps using the API key. Commands then act as the user who generated the key rather than the person running them. Use it in pipelines, not on developer machines. ## Credential precedence The CLI uses the first credential it finds: 1. The `DITTO_TOKEN` environment variable 2. A session saved by `login` 3. An API key saved by an earlier run 4. A prompt for an API key > [accordion] Where credentials are stored > > Sessions and API keys are saved to `$HOME/.config/ditto`. The file holds credentials, so treat it like any other secret: keep it out of commits and out of shared images. To use a different file, set `DITTO_CONFIG_FILE` to its path. > [accordion] Switching accounts or keys > > To switch Ditto accounts, run `logout` and then `login` again. If `DITTO_TOKEN` is set, unset or replace it first; it outranks a saved session, so your commands would keep using the API key. > > We don't recommend editing the credentials file by hand. To replace a saved API key, delete `$HOME/.config/ditto`; the CLI prompts for a new key the next time it runs. > [accordion] Expired sessions > > A session renews itself as you keep using the CLI. If one sits unused long enough to expire, the next command stops and asks you to run `login` again. # Ditto CLI commands Source: https://developer.dittowords.com/cli-reference/commands Reference for every Ditto CLI command and its options. All commands run through `npx`, so nothing needs to be installed globally: ```bash theme={null} npx -y @dittowords/cli@latest ``` The examples below omit `-y` and `@latest` for brevity. Every command that talks to Ditto needs a credential: a saved browser login or the `DITTO_TOKEN` environment variable. See [Install and log in](/cli-reference/authentication). ## scan Analyzes a path for user-facing text and sends the candidates to Ditto, where you review them and turn them into library components. This is the first step of setting up Ditto on a codebase; the walkthrough is [Scan your repo](/get-started/scan-your-repo). ```bash theme={null} npx @dittowords/cli scan [path] ``` `[path]` is the file or directory to scan; it defaults to the current directory. The CLI extracts candidate strings, uploads them, and prints a link to the review page: ```text theme={null} Scan initiated! Visit https://app.dittowords.com/scan/ to view progress and see results. ``` > [!NOTE] > > **Requires Node.js 20 or higher** and a credential. Without a saved login or `DITTO_TOKEN`, `scan` stops and prompts for an API key. `--list-directories` and `--local` need no credential. **What is uploaded.** For each candidate: the text, file path and line, how it was detected (markup text, attribute value, localization resource value, or other), the i18n key and locale if it came from a translation file, surrounding source lines, and nearby identifiers. In a Git checkout with a remote, also the remote's host and path, commit, branch, scanned paths, and file renames since the last scan. Never credentials. **Requires a clean default branch.** Scan records the commit and branch it read, and the code links Ditto builds point at them. To keep those links on code your team shares, `scan` runs only from a clean checkout of your repository's default branch. It stops, without uploading, on uncommitted changes, a non-default branch, or a detached `HEAD`: ```text theme={null} On branch "my-feature", but this repo's default branch is "main". Check out "main" and scan again. ``` The default branch comes from `origin/HEAD`, falling back to a local `main` or `master`. Because that reference is set when you clone and doesn't update on its own, a mismatch is confirmed against your remote first, so a branch renamed after you cloned won't block you. Scanning outside a Git repository is unaffected. **Preview counts by directory.** Extract candidates, print a per-directory count, and exit without uploading: ```bash theme={null} npx @dittowords/cli scan . --list-directories ``` ```text theme={null} Strings by directory (rolled up), under .: 1,284 . (whole scan) 382 packages 382 packages/ui 902 src 418 src/components 344 src/lib 140 src/pages ``` Counts are rolled up, so each directory includes its subdirectories. Directories are listed in tree order, indented by depth, with the whole-scan total first. **Write candidates locally instead of uploading.** ```bash theme={null} npx @dittowords/cli scan [path] --local --out-dir ./out ``` Writes `/candidates.ndjson`, one JSON object per line. Add `--prefix ` to prefix the file name. Each object has `value_raw`, `snapshot_text`, `detection_kind` (`markup_text`, `markup_attr`, `resource_value`, `other`), `location` (`file`, `line`, `column`), `language`, `framework`, `i18n_key`, `locale_key`, `source_context`, `context_identifiers`, and `usage_evidence`. **Plan limits.** Trial workspaces can classify up to 100,000 strings in total across all of their scans; paid plans have no cap. Re-scanning a directory does not count twice. If a scan would exceed the remaining budget, the CLI stops before uploading and suggests the largest subdirectories that fit (up to 10): ```text theme={null} This scan found 101,234 strings, but your Trial plan allows 100,000 scanned strings. Scan one of these subdirectories instead. Each fits within your plan: npx @dittowords/cli scan src/components (418 strings) npx @dittowords/cli scan src/lib (344 strings) npx @dittowords/cli scan src/pages (140 strings) Run `npx @dittowords/cli scan . --list-directories` for the full breakdown. ``` **Options** | Flag | Description | | - | - | | `--list-directories` | Print the number of candidate strings per directory and exit, without uploading. | | `--local` | Write the extracted candidates to disk instead of uploading them. Requires `--out-dir`. | | `--out-dir ` | Directory to write candidates to when using `--local`. | | `--prefix ` | Prefix for the names of the output files. | ## pull Fetches the latest text from Ditto and writes string files to disk, as described by your [config file](/cli-reference/configuration). ```bash theme={null} npx @dittowords/cli pull ``` Files are written to the output directory in your config (`./ditto` by default). For the structure of the generated files, see [Files](/cli-reference/files). Because `pull` updates files in your repo, you manage the result like any other change: commit it, open a pull request, or run it in CI before a build. **Custom config location.** By default `pull` reads `./ditto/config.yml` relative to where you run it. Use `-c` / `--config` for another path, or set the `DITTO_PROJECT_CONFIG_FILE` environment variable: ```bash theme={null} npx @dittowords/cli pull -c ./path/to/config.yml ``` **Request metadata.** `-m` / `--meta` attaches arbitrary key-value data to the API requests, which shows in Ditto's activity history: ```bash theme={null} npx @dittowords/cli pull -m githubActionRequest:true trigger:manual ``` ## login Logs in to Ditto through your browser and saves the session to `$HOME/.config/ditto`. ```bash theme={null} npx @dittowords/cli login ``` A browser tab opens for you to log in and approve access. Later commands reuse the session and renew it as needed. If `DITTO_TOKEN` is set, the CLI keeps using that API key instead, because an environment variable outranks a saved session; see [credential precedence](/cli-reference/authentication#credential-precedence). > [!NOTE] > > `login` and `logout` require version `5.7.0` or higher. ## logout Forgets the session saved on this machine and revokes it with Ditto. ```bash theme={null} npx @dittowords/cli logout ``` Reports back if `DITTO_TOKEN` is still set, because commands would keep working with that API key. # Ditto CLI configuration Source: https://developer.dittowords.com/cli-reference/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 awesome text", "dev-id-2": "

This is other text.

It has two sentences

" } ``` ### `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 `.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-/` 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-/` 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-/` 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 ``` # Ditto CLI output files Source: https://developer.dittowords.com/cli-reference/files The file structure of text pulled down via the CLI ## Output Directory The CLI outputs data from Ditto by writing files to disk. By default, these files are written to a `./ditto`  folder relative to the current working directory, but the location of the output files can be customized by defining the `outDir` property in your config.yml file or by setting the environment variable `DITTO_OUT_DIR`. ## `./ditto` Directory By default, this directory houses the configuration file (`./ditto/config.yml`) used by the CLI and is also the default write destination for any output files the CLI writes to disk. You can override the default location of the config file by using the `--config` flag when running any command or by setting the environment variable `DITTO_PROJECT_CONFIG_FILE` . If you run the CLI and the resolved config file does not exist, the CLI will automatically create a default config.yml file at the path specified. ## String File Naming Conventions All string files created will generally adhere to the following template: ```typescript theme={null} {project_id}___{variant_id}.{extension} ``` * `project_id`: the Developer ID of the project. * `variant_id`: the Developer ID of a variant or `base` for files generated for non-variant text * `extension`: the file extension that corresponds to the configured format(s) (e.g., **json**, **xml**, **strings**, **stringsdict**) If `components` is included in the config, a single, combined file for all fetched components will also be created for each variant with the format `components__{variant_id}.{extension}`. To keep cross-platform behavior consistent, file names will be fully lowercase, have whitespace replaced with hyphens, and have non-word characters (with the exception of `-` and `_`) removed. ## Format-specific Files ### JSON If one or more JSON string formats are configured, a series of json files will be generated, on a project-variant/components-variant pair basis. Generally each file will follow the shown format: ```json theme={null} { "text-item-developer-id": "string with {{variableId}} variables escaped", ..., "text-item-with-plural-dev-id": "string with {{count}} characters", "text-item-with-plural-dev-id_other": "string with {{count}} characters", "text-item-with-plural-dev-id_one": "string with a character", ..., "welcome": "Hello {{userFirstName}}!" } ``` Additionally, a `variables.json` file will be generated that contains information about any variables used within the fetched text. The file has the following format: ```json theme={null} { "text-variable-id": { "example": "example_value", "fallback": "default_value" }, "number-variable-id": { "example": "1", "fallback": "10" }, "link-variable-id": { "text": "URL display text", "url": "https://dittowords.com" }, "list-variable-id": ["value1", "value2", "value3"], "map-variable-id": { "key1": "value1", "key2": "value2", "key3": "value3" } } ``` ### Android XML If `android` format is configured for an output, a series of `.xml` files will be generated, on a project-variant/components-variant pair basis. Generally each file will follow the shown format: ```xml theme={null} string with %1$s variables Continue Email Hello, %1$s Log In Save Submit The %1$s rings to rule them all A few rings to rule them all Welcome, %1$s! ``` ### iOS Strings If iOS Strings format is configured, a series of `.strings` files will be generated, on a project-variant/components-variant pair basis. Generally each file will follow the shown format: ```text theme={null} "text-item-developer-id" = "string with %1$@ variables"; "continue" = "Continue"; "email" = "Email"; "hello" = "Hello, %1$@"; "log-in" = "Log In"; "save" = "Save"; "submit" = "Submit"; "welcome" = "Welcome, %1$@!"; ``` ### iOS StringsDict If iOS StringsDict format is configured, a series of `.stringsdict` files will be generated, on a project-variant/components-variant pair basis. Generally each file will follow the shown format: ```xml theme={null} the-one-ring NSStringLocalizedFormatKey %1$#@count@ count NSStringFormatSpecTypeKey NSStringPluralRuleType NSStringFormatValueTypeKey d one The one ring to rule them all other The %2$@ rings to rule them all ``` ## Framework-specific Files Within each output format, an optional `framework` can be provided. Frameworks come in two kinds: * [i18next](https://www.i18next.com/) and [vue-i18n](https://github.com/i18next/i18next-vue) generate the standard flat JSON strings files (see [Format-specific Files](#format-specific-files) above) **plus** additional driver files to assist with using those strings files with the corresponding library. * `icu` and `arb` don't generate any additional files. Instead, they change the shape of the JSON string values themselves — using [ICU message format](https://icu.unicode.org/home) syntax for pluralization and variable interpolation. See the [JSON string format docs](/string-formats/json) for the exact content shape each one produces. ### i18next Formats supported: `JSON` Supported Properties: #### `type: “commonjs” | “module” (default “commonjs”)` By default, additional javascript files will be generated using `commonjs` syntax. This property configures the output to use `commonjs` or `module` syntax in the generated files. **Details:** Generates an additional javascript file that imports the generated strings files, and exports an object with the json strings grouped by their variants. If included, components will always be applied last. ```javascript theme={null} // CommonJS const exampleProject1Base = require("./example-project-1__root__base.json"); const exampleProject1Spanish = require("./example-project-1__root__spanish.json"); const exampleProject2Base = require("./example-project-2__base.json"); const exampleProject2Spanish = require("./example-project-2__spanish.json"); const componentsBase = require("./components__base.json"); const componentsSpanish = require("./components__spanish.json"); module.exports = { base: { ...exampleProject1Base, ...exampleProject2Base, ...componentsBase, }, spanish: { ...exampleProject1Spanish, ...exampleProject2Spanish, ...componentsSpanish, }, }; // ESM import exampleProject1Base from "./example-project-1__root__base.json"; import exampleProject1Spanish from "./example-project-1__root__spanish.json"; import exampleProject2Base from "./example-project-2__base.json"; import exampleProject2Spanish from "./example-project-2__spanish.json"; import componentsBase from "./components__base.json"; import componentsSpanish from "./components__spanish.json"; export default { base: { ...exampleProject1Base, ...exampleProject2Base, ...componentsBase, }, spanish: { ...exampleProject1Spanish, ...exampleProject2Spanish, ...componentsSpanish, }, }; ``` ### vue-i18n Formats supported: `JSON` Functions very similarly and accepts all the same config properties as the `i18next` framework. The main difference is variable interpolation uses single brackets instead of double. ```typescript theme={null} { "text-item-developer-id": "string with {variableId} variables escaped", ..., "welcome": "Hello {userFirstName}!" } ``` ### icu Formats supported: `JSON` No additional properties are supported. Rather than generating an additional driver file, this framework changes the generated JSON string files to use [ICU message format](https://icu.unicode.org/home) syntax for variable interpolation and pluralization, instead of the default flat JSON shape. ```json theme={null} { "onboarding.header.title": "Welcome, {username}!", "cart.count": "{count, plural, =0 {No items in the cart} =1 {One item in the cart} other {{count} items in the cart}}" } ``` See [ICU JSON](/string-formats/json#icu-json) for the full content shape, including how Map/List variables and plurals are represented. ### arb Formats supported: `JSON` No additional properties are supported. Generates [ARB (Application Resource Bundle)](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification) files instead of the default flat JSON shape — the format used by Flutter's `intl` package. Message text uses the same ICU message format syntax as the `icu` framework, plus `@`-prefixed metadata entries for variable placeholders and text item notes, and a top-level `@@locale` key when a base or variant locale is known. ```json theme={null} { "@@locale": "en", "cart.count": "{count, plural, =0 {No items in the cart} =1 {One item in the cart} other {{count} items in the cart}}", "@cart.count": { "placeholders": { "count": { "type": "num" } } } } ``` See [ARB JSON](/string-formats/json#arb-json) for the full content shape — including a note on why `@@locale` is the only thing identifying a generated ARB file's language, and where to configure it. ## Ditto-generated Files ### iOS Locale Directories + Ditto.swift If `iosLocales` is configured at the root level of the project config, a `Ditto.swift` file will be auto-generated in conjunction with the respective locale `.lproj` directories. ```swift theme={null} import SwiftUI struct Ditto { static func getBundle(_ locale: String? = nil) -> Bundle { var bundle: Bundle? = nil if (locale != nil) { let path = Bundle.main.path(forResource: locale, ofType: "lproj") if (path != nil) { bundle = Bundle(path: path!) } } return bundle ?? Bundle.main } /// [Open component in Ditto](http://app.dittowords.com/library?selectedLibraryComponentIds=) /// /// - Returns: The localized string for the Ditto identifier "continue" public static func continue(_ localeOverride: String? = nil) -> String { String.localizedStringWithFormat(NSLocalizedString("continue", tableName: "components", bundle: getBundle(localeOverride), comment: "")) } /// [Open component in Ditto](http://app.dittowords.com/library/?selectedLibraryComponentIds=) /// /// - Returns: The localized string for the Ditto identifier "hello" public static func hello(_ localeOverride: String? = nil, FirstName: String) -> String { String.localizedStringWithFormat(NSLocalizedString("hello", tableName: "components", bundle: getBundle(localeOverride), comment: ""), FirstName) } struct myDittoProject { /// [Open text item in Ditto](http://app.dittowords.com/projects-beta/?viewState=text&selectedTextItemIds=) /// /// - Returns: The localized string for the Ditto identifier "email" public static func email(_ localeOverride: String? = nil) -> String { String.localizedStringWithFormat(NSLocalizedString("email", tableName: "my-ditto-project", bundle: Ditto.getBundle(localeOverride), comment: "")) } /// [Open text item in Ditto](http://app.dittowords.com/projects-beta/?viewState=text&selectedTextItemIds=) /// /// - Returns: The localized string for the Ditto identifier "submit" public static func submit(_ localeOverride: String? = nil) -> String { String.localizedStringWithFormat(NSLocalizedString("submit", tableName: "my-ditto-project", bundle: Ditto.getBundle(localeOverride), comment: "")) } } } ``` By default, `Ditto.swift` and the `.lproj` directories are placed at the root-level `outDir`, alongside any other configured outputs. Configuring [`iosLocalesOutDir`](/cli-reference/configuration#ioslocalesoutdir) sends them to a separate directory instead — useful since iOS output typically needs to live somewhere different in a codebase than other platforms' output (e.g., inside an Xcode project's source directory, rather than a general assets folder). **Example:** ```yaml theme={null} outDir: "./ditto" iosLocalesOutDir: "./MyApp/Localization" iosLocales: - base: en - spanish: es outputs: - format: ios-strings - format: android ``` ```text theme={null} . ├── MyApp/ │ └── Localization/ │ ├── Ditto.swift │ ├── en.lproj/ │ │ ├── my_project.strings │ │ └── components.strings │ └── es.lproj/ │ ├── my_project.strings │ └── components.strings └── ditto/ ├── my_project___base.xml └── components__base.xml ``` The `ios-strings` output's `.lproj` directories and `Ditto.swift` are written to `./MyApp/Localization` (because of `iosLocalesOutDir`), while the unrelated `android` output still writes to the root-level `outDir` (`./ditto`). ### Android Locale Directories If `androidLocales` is configured at the root level of the project config, `android`-format XML files will be organized into the standard Android [locale-qualified resource directories](https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources) (`values-/`), instead of being written flat with a variant suffix. Unlike iOS, Android already has a default resource directory (`values/`, with no locale qualifier) that the platform falls back to automatically. Because of this, base text doesn't need its own entry in `androidLocales` the way it does for `iosLocales` — it's always written to `values/` automatically. Only variants you want organized into a locale-specific directory need to be mapped. **Example:** ```yaml theme={null} androidLocales: - spanish: es - french: fr outputs: - format: android ``` This configuration will generate: ```text theme={null} ./ditto/ ├── values/ │ ├── my_project.xml │ └── components.xml ├── values-es/ │ ├── my_project.xml │ └── components.xml └── values-fr/ ├── my_project.xml └── components.xml ``` Variants not mapped in `androidLocales` are written to the output directory with their variant suffix instead (e.g., `my_project___german.xml`), the same as unmapped variants with `iosLocales`. By default, the `values/` and `values-/` directories are placed at the root-level `outDir`, alongside any other configured outputs. Configuring [`androidLocalesOutDir`](/cli-reference/configuration#androidlocalesoutdir) sends them to a separate directory instead — useful since Android output typically needs to live somewhere different in a codebase than other platforms' output (e.g., inside an Android module's `res/` directory). **Example:** ```yaml theme={null} outDir: "./ditto" androidLocalesOutDir: "./app/src/main/res" androidLocales: - spanish: es - french: fr outputs: - format: android - format: ios-strings ``` ```text theme={null} . ├── app/ │ └── src/ │ └── main/ │ └── res/ │ ├── values/ │ │ ├── my_project.xml │ │ └── components.xml │ ├── values-es/ │ │ ├── my_project.xml │ │ └── components.xml │ └── values-fr/ │ ├── my_project.xml │ └── components.xml └── ditto/ ├── my_project___base.strings └── components__base.strings ``` The `android` output's `values/` and `values-/` directories are written to `./app/src/main/res` (because of `androidLocalesOutDir`), while the unrelated `ios-strings` output still writes to the root-level `outDir` (`./ditto`). # Key concepts Source: https://developer.dittowords.com/concepts Definitions for Ditto's data model: projects, text items, library components, component versioning, developer IDs, style guides, variants, variables, plurals, and scans. This page defines the terms the CLI, API, MCP tools, and guides use. ## Projects, text items, and the component library * A **project** holds the text for one product surface, usually one Figma file or one feature. Text inside a project is a set of **text items**. A text item is one string plus its metadata: status, tags, notes, assignee, character limit, variants, plurals, and the Figma layers it is linked to. Editing a text item updates every linked layer. * **Blocks** group text items within a project, in display order. They are how a project's text is organized on screen; the API and MCP address them by developer ID. * The **component library** is one per workspace. A **library component** is reusable text: attach it to text items in any number of projects and editing the component updates every instance. With [component versioning](#component-versioning), instances update when they're upgraded instead. Components live in **folders**, which can nest. A [scan](/get-started/scan-your-repo) creates library components, in folders, from the strings in your codebase. * Text items and library components share the same developer ID space; a component and its attached instances share one developer ID, except with [component versioning](#component-versioning), where each instance has its own. For designers' view of these concepts, see [Text items and linking](https://help.dittowords.com/en/articles/11502480-text-items-and-linking) and [Component library](https://help.dittowords.com/en/articles/11502584-component-library) in the help center. ## Component versioning **Component versioning** (called library component versioning in the API reference) lets you change a library component without changing its instances right away. Each published change gives the component a new **version**, and each instance stays on its version until it's **upgraded**. A workspace admin or editor turns on versioning from the component library in the web app. Turning it on can't be undone. More detail: [Component versioning](https://help.dittowords.com/en/articles/17663939-component-versioning). In a workspace with component versioning: * **Unpublished changes.** A component's text, plurals, tags, notes, and character limit can be changed without publishing, using [Save unpublished component changes](/api-reference/components/save-unpublished-component-changes) in the API. Instances don't see those changes. [Fetch components](/api-reference/components/get-components) still returns the published component, with `hasUnpublishedChanges` set, and [Fetch unpublished component changes](/api-reference/components/get-unpublished-component-changes) returns it with the changes applied. [Discard unpublished component changes](/api-reference/components/discard-unpublished-component-changes) throws them away. * **Publishing.** [Publish component changes](/api-reference/components/publish-component-changes) turns a component's unpublished changes into a new version. [Update components](/api-reference/components/patch-components) creates a new published version, but can't change a component's content while it has existing unpublished changes. Changing only status or assignee can happen independently of publishing constraints. * **Instances** don't change when their component gets a new version. Each instance keeps its own status, assignee, and integrated flag. An instance has **local edits** when its text was changed on the instance rather than on the component. Local edits occur in a limited capacity through indirect means like figma canvas updates. [Fetch text items](/api-reference/text-items/get-text-items) reports where each instance stands in `component.state`: | State | Meaning | | - | - | | `current` | On the component's latest version, with no local edits | | `outdated` | The component has a newer version; no local edits | | `modified` | On the component's latest version, with local edits | | `conflict` | Has local edits, and the component has a newer version | * **Upgrading** moves an instance to its component's latest version, with that version's text. Upgrade instances from the web app or with [Upgrade component instances](/api-reference/components/upgrade-component-instances). Upgrading replaces local edits, so the API refuses to upgrade a `modified` or `conflict` instance unless you set `discardLocalEdits`. * **Developer IDs.** Each instance has its own developer ID instead of sharing its component's, so it can be referenced in code separately from the component. A text item attached to a component keeps the developer ID it had, and publishing a text item to the library gives the new component a newly generated one. Text items fetched from a project hold each instance's own text, so a component's new version reaches that code only once its instances are upgraded. Components fetched from the library hold each component's latest published version. The versioning endpoints return a `404` in workspaces that haven't turned on component versioning. ## Developer IDs Developer IDs are unique, usually human-readable identifiers for projects, text items, library components, component folders, blocks, variables, variants, and style guides. They are the key you reference in code and the key you pass to the API, CLI, and MCP tools. Developer IDs are generated when an item is created: projects, components, and folders from their name, text items from their text. To keep references stable, Ditto never changes a developer ID automatically when the underlying data changes. Editing a text item's text does not change its developer ID. You can edit a developer ID by hand at any time. > [!WARNING] > > If you change a developer ID in Ditto, update every reference to it in your application. Make such edits before an > item is in use in code. ### Uniqueness Developer IDs for a given entity type are unique within a workspace. Two projects cannot share an ID, and two pieces of text cannot share an ID, but a project and a text item may. Text items and library components draw from the same pool: a library component and an unrelated text item cannot share a developer ID, and similar text items in different projects cannot either, with one exception: a library component and the text items attached to it all share the component's developer ID, because their content is kept identical. **Example.** A library component has developer ID `hello`. Two projects each get a text item with the text "hello"; they are assigned `hello-1` and `hello-2`. Attaching both to the component gives all three the developer ID `hello`. With [component versioning](#component-versioning), the exception doesn't apply: each instance keeps its own developer ID. In the example, the two text items stay `hello-1` and `hello-2` after they're attached. ### Configuration Developer IDs are generated and validated against a workspace-wide **Developer ID configuration**, editable on the [Developers page](https://app.dittowords.com/developers/configure-dev-ids): * **Allowed characters**: the set of characters permitted in a developer ID. * **Character replacement**: an ordered list of substring replacements applied to the source text during generation. * **Casing adjustment**: optionally transform the case of the source text. Options: lowercase, UPPERCASE, camelCase, PascalCase. * **Maximum length**: the maximum number of characters. Deduplication may append a short suffix *after* truncation, so generated IDs can exceed the limit by a few characters. If your system has a hard limit, set the maximum three or more characters below it. Manual edits over the limit are rejected. A scan proposes this configuration for you from the keys it finds in your code (see [What a scan produces](#what-a-scan-produces)). More detail: [Developer ID configuration](https://help.dittowords.com/en/articles/14529160-developer-id-configuration). ### Marking text as integrated Text items and library components can be marked **integrated** to signal that their developer ID is in use in code: changing the ID would be a breaking change, and changing the text changes production copy. The flag is a filter on every API endpoint that fetches text, on the CLI config (`integrated: true`), and on every webhook payload (`integrated: boolean`), so you can limit what reaches your codebase to text that is known to be in use. ![The Integrated toggle in a text item's details panel](https://mintcdn.com/ditto/dDYr8fLNLi9vWSv6/images/dev-id-integrated-toggle.png?fit=max\&auto=format\&n=dDYr8fLNLi9vWSv6\&q=85\&s=6611b2ec1d9ec9dbb293b524c4fc8ac3) ### Where to find and edit developer IDs **Projects**: open the project's main menu (top right of the project in the web app) and choose **Development integration**. ![Project menu with the Development integration entry](https://mintcdn.com/ditto/pXBmKmaeW4wg1sef/images/project-menu.png?fit=max\&auto=format\&n=pXBmKmaeW4wg1sef\&q=85\&s=eecd0d72bb4cc22d6d9d8923d0ec8f49) ![Project developer integration modal showing the project developer ID](https://mintcdn.com/ditto/pXBmKmaeW4wg1sef/images/project-dev-integration-modal.png?fit=max\&auto=format\&n=pXBmKmaeW4wg1sef\&q=85\&s=6a5bf52d92416a6bdf926de0f3ef041a) **Text items and library components**: select the item and edit the **Developer ID** field in the details panel. ![Editing a text item's developer ID](https://mintcdn.com/ditto/7aMEipg7bpvcHX8V/images/dev-id-edit-text-item.png?fit=max\&auto=format\&n=7aMEipg7bpvcHX8V\&q=85\&s=b26b634a5d45f251f3eefba100a66100) Select several items to open the **Bulk editor**, which edits many IDs at once and supports operations such as adding a prefix. Details: [Developer ID bulk editor](https://help.dittowords.com/en/articles/14792979-developer-id-bulk-editor). ![The developer ID bulk editor](https://mintcdn.com/ditto/7aMEipg7bpvcHX8V/images/dev-id-bulk-editor.png?fit=max\&auto=format\&n=7aMEipg7bpvcHX8V\&q=85\&s=c4cd2342a4d353da5e2cdfc4a58cb61e) **Library folders**: open the folder's parent, choose **Folder properties…** from the folder's menu, and edit the **Developer ID** in the details panel. ![Editing a library folder's developer ID](https://mintcdn.com/ditto/pXBmKmaeW4wg1sef/images/library-folder-dev-ids.gif?s=97a5dbf0aeea0eaf02dfd4af980d26ed) **Variants**: open [Variants](https://app.dittowords.com/variants), select a variant, and edit the **Developer ID** in the details panel. ![Editing a variant's developer ID](https://mintcdn.com/ditto/7aMEipg7bpvcHX8V/images/dev-id-variant.png?fit=max\&auto=format\&n=7aMEipg7bpvcHX8V\&q=85\&s=008ec03e66c2413ba96439c4b0bc0726) ## Style guides and rules A **style guide** is a named ruleset for how your product's text reads. A workspace can have several. Each has a description and an **enabled by default** flag; enabled-by-default guides apply to every project unless a project turns them off, and to the component library, which has no per-guide setting of its own. * A style guide is divided into **sections**, each of kind `rules` or `wordlist`. * A **rule** (in a `rules` section) has a `name`, a `description` of what to do, one or more `examples` as from → to pairs, optional `tags` that limit it to text carrying those tags, and an `enabled` flag. * A **word list entry** (in a `wordlist` section) has a preferred `term`, `disallowed` alternatives, and a `description`. * A style guide can be **scoped to a variant with a locale code**, which is how locale-specific rules (formality, terminology) are attached to a language. Agents fetch rules with `get_styleguide_rules` and check text with `suggest_edit`; the [PR review bot](/additional-tools/github-pr-review-bot) enforces them on pull requests; the Ditto app applies them through Magic Edit and Magic Translate. After a scan, Ditto drafts a starter style guide from your strings; see [Get a first style guide from your scan](/get-started/scan-your-repo#get-a-first-style-guide). ## Variants and locales A **variant** is a named alternative for a text item's text. Every text item and library component has base text and any number of variant texts. Variants are workspace-level objects with a name, description, developer ID, and optional **locale code**. A **locale** is simply a variant with a locale code (`de-DE`, `fr`, `es-419`). Ditto's localization features key on it: Magic Translate uses it as the target language, locale-scoped style guides attach to it, and the CLI's `iosLocales` and `androidLocales` map it to platform locale directories. At most one variant per workspace holds a given locale code. Variants without a locale code cover other uses, such as tone or A/B copy. The **base locale** is the language of your base text, set in a project's or the library's **Variant & locale settings**. Set it when your source copy is not English. How to add locales, get translations, and pull localized files: [Set up localization](/guides/set-up-localization). ## Variables **Variables** are reusable placeholders for values your app fills in at runtime. Write one into text as `{{variable_name}}`. Each variable has a type: `string` or `number` (with an example and optional fallback), `hyperlink` (link text and URL), `list` (a set of possible values), or `map` (key-value pairs). A variable's name is its developer ID: letters, numbers, and underscores, unique in the workspace. The API, CLI, and manual exports render variables in each string format's native placeholder syntax (for example `{username}` in ICU JSON, `%1$@` in iOS `.strings`, `` in Android XML). A scan detects the variables in your strings and creates them. More detail: [Variables](https://help.dittowords.com/en/articles/11657688-variables). ## Plurals A text item can define **plural forms** (`zero`, `one`, `two`, `few`, `many`, `other`) so your app renders different copy for different counts: "No results", "1 result", "10 results". The API returns the base form (`pluralForm: null`) and each defined plural form as separate objects. Following i18next conventions, each plural form's developer ID is the text item's developer ID with a suffix, for example `results_one`. In your code you reference the main developer ID and pass a `count`; the i18n library picks the form. Plurals can contain variables and can be translated per variant. Each string format renders plurals natively (`.stringsdict` on iOS, `` on Android, `{count, plural, …}` in ICU). More detail: [Plurals](https://help.dittowords.com/en/articles/13460485-plurals). ## Tags and statuses * **Tags** are labels on text items and library components. Style guide rules can be limited to tagged text, and the API, CLI, and `search_ditto_text` filter by tag. Only tags that already exist in the workspace can be used on a rule; `get_workspace_tags` lists them. A scan proposes a tag vocabulary for your strings. * **Statuses** track text through your workflow, from first draft to ready to ship. The default set is `NONE`, `WIP`, `REVIEW`, `FINAL`, but a workspace can rename or replace it, so call `list_statuses` (MCP) or `GET /v2/statuses` before setting or filtering by status. Filtering `pull` to `FINAL` keeps unreviewed text out of production. ## What a scan produces `npx @dittowords/cli scan ` extracts **candidate strings** from your code and uploads them for review. A candidate is one string with where it was found, how it was found (markup text, an attribute value, a value in a localization resource file, or another string position), its translation key and locale if it came from a localization file, and a few surrounding lines of source. Ditto then: 1. **Classifies** each candidate as user-facing or not. 2. **Proposes a content system** for the user-facing strings: a tag vocabulary, a naming scheme for components, a folder structure, the variants and locales found in your translation files, and a developer ID configuration derived from your existing keys. You review and adjust this before anything is created. 3. **Creates** the library components (in folders, with tags), the variables they use, and one variant per locale. 4. **Drafts a style guide** from a sample of the strings, for you to review and enable. Details and commands: [Scan your repo](/get-started/scan-your-repo). # Ditto Specs agent skills Source: https://developer.dittowords.com/ditto-specs-cli-reference/agent-skills Interactive agent workflows for scaffolding Ditto specs, auditing text, and finding style guide rule gaps ## Overview Running `ditto-spec init --agent` installs three slash commands into `.claude/commands/`. These give agents interactive, multi-step workflows for working with Ditto specs. Skills are committed to your repo alongside Ditto spec files and config — every team member gets them automatically, no separate plugin install. ## /ditto-spec-component **Analyze a component, create Ditto spec files for it and its dependencies, auto-fill surfaces and tags, and sync style guide rules from the platform.** ```text theme={null} /ditto-spec-component ``` Accepts either a component name (e.g. `Button`) or a file path (e.g. `src/components/Button/index.tsx`). ### Workflow **1. Discovery** Locates the component, walks its imports to find child components that render user-facing text, and checks which components already have Ditto specs. **2. Surface analysis** For each component needing a Ditto spec, identifies every text surface — string props, `children`, nested props, hardcoded strings — and suggests tags from the `workspace.ditto.md` tag inventory. Estimates `maxLength` where layout context provides constraints. **Pauses for your review** before creating any files. You can add, remove, or modify surfaces and tags. **3. Create specs** Scaffolds `index.ditto.md` files (or updates existing ones) with the approved surfaces, then runs `ditto-spec pull` to populate style guide rules and `ditto-spec check` to validate. ## /ditto-spec-audit **Audit copy in component instances against the style guide rules in their Ditto spec files and report violations.** ```text theme={null} /ditto-spec-audit ``` Omit the argument to audit all components that have Ditto specs. This skill is **read-only** — it never modifies code, i18n catalogs, or Ditto text items. ### Workflow **1. Load specs** Reads `workspace.ditto.md` and each relevant component's `index.ditto.md` to build the full set of style guide rules — workspace rules, component-level rules, per-surface rules, and locale-scoped rules. **2. Find instances** Searches the codebase for files that import and render each component. For each instance, resolves the actual copy bound to every text surface: * **Inline** — string literals, template strings, hardcoded text * **i18n keys** — resolves values across all locale catalogs, including plural forms * **Ditto text items** — resolves values across variant files by Developer ID * **Unresolvable** — flags dynamic or computed values for manual review **3. Evaluate** Checks each instance's copy against all applicable style guide rules — style rules, terminology entries, `maxLength` constraints, and locale-scoped rules where relevant. **4. Report** Presents violations grouped by component and file, with file location, surface, source, the violating text, the rule violated, and a suggested correction. Ends with a summary of components audited, instances checked, and violations found. ## /ditto-spec-gaps **Find style guide rule gaps — copy patterns that should be rules but aren't — then create them on the platform.** ```text theme={null} /ditto-spec-gaps ``` Omit the argument to analyze all components that have Ditto specs. ### Workflow **1. Load existing rules** Reads Ditto specs and runs `ditto-spec rules` to load the **complete** set of style guide rules on the platform — not just the rules that match local tags. This prevents proposing duplicates of rules that exist but aren't tagged for local components. **2. Analyze text** Gathers copy from all component instances (inline, i18n, Ditto text items) and looks for patterns no existing style guide rule covers: * **Terminology inconsistencies** — the same concept with different forms (e.g. "sign up" vs "signup") * **Tone mismatches** — some instances formal, others casual * **Anti-patterns** — passive voice in CTAs, redundant wording * **Conventions** that should be formalized (e.g. "all action buttons start with a verb") **3. Propose rules** Presents proposed style rules and terminology entries with names, descriptions, tags, examples, and target sections. **Pauses for your review.** You choose which rules to create, modify, or skip. **4. Create rules** Creates approved style guide rules on the platform via `ditto-spec create-rules`, then runs `ditto-spec pull` to sync them into Ditto spec files. > [!NOTE] > > This is the only skill that writes to the Ditto platform. The other two skills are read-only. ## Updating and customizing Skill files are plain markdown in `.claude/commands/`. You can edit them to add project-specific behavior — custom tags, additional audit checks, or tailored prompts. To update skills to the latest CLI version: ```bash theme={null} npx ditto-spec init --agent ``` > [!NOTE] > > Re-running `init --agent` overwrites skill files with the current templates and removes skill files from earlier versions (previously named `spec-component.md`, etc.). If you've customized skills, commit your changes first, then use `--force` to accept the update and merge in your customizations. # Ditto Specs CLI commands Source: https://developer.dittowords.com/ditto-specs-cli-reference/commands All commands available in the Ditto Specs CLI ## init First-time setup. Creates `dittospec.config.json` and `workspace.ditto.md` in the current directory, then detects your agent environment and prints setup suggestions. ```bash theme={null} npx ditto-spec init ``` | Flag | Description | | - | - | | `--agent` | Write agent configuration (Claude Code or Cursor). See [Agent Setup](/ditto-specs-cli-reference/setup#agent-setup). | | `--force` | Overwrite customized skill files in `.claude/commands/`. Only applies with `--agent`. | > [!NOTE] > > Re-running `init` is safe. Existing config files are not overwritten. With `--agent`, skill files are updated to the current CLI version and `CLAUDE.md` sections that already exist are skipped. ## scaffold Creates a new `index.ditto.md` Ditto spec file for a component with the correct YAML structure and empty managed keys. ```bash theme={null} npx ditto-spec scaffold --path ``` | Argument / Flag | Description | | - | - | | `` | Name of the component (required). | | `--path ` | Directory where the spec file is created. Defaults to the current directory. | Example: ```bash theme={null} npx ditto-spec scaffold DialogueModal --path src/components/DialogueModal ``` > [!NOTE] > > After scaffolding, add surfaces and tags to the generated file, then run `ditto-spec pull` to populate style guide rules from the platform. ## pull Syncs style guide rules from the Ditto platform into Ditto spec files by tag matching. ```bash theme={null} npx ditto-spec pull ``` | Flag | Description | | - | - | | `--dry-run` | Show what would change without writing files. | The pull process: 1. Discovers all Ditto spec files under the configured `roots` 2. Fetches style guides from the API 3. Flattens style guide rules and wordlist entries across guides (filtered by `styleguides` config if set) 4. Separates base rules from locale-scoped rules (included when `locales` is configured) 5. Matches rules to Ditto specs by tag intersection 6. Rewrites the `rules` and `locales` keys in each file's YAML frontmatter > [!NOTE] > > Requires `DITTO_TOKEN`. Set it in your environment or in a `.env` file at the repo root. ## check Validates all Ditto spec files: YAML parses correctly, required keys are present, surfaces have `tags` arrays. Exits non-zero on any failure. ```bash theme={null} npx ditto-spec check ``` > [!NOTE] > > Useful as a pre-commit hook or CI step to catch malformed Ditto spec files before they're merged. ## list Prints an inventory of all Ditto specs with their surfaces, tags, and constraints. ```bash theme={null} npx ditto-spec list ``` ## rules Prints every style guide rule on the platform, grouped by style guide and section. Shows each section's kind (`rules` or `wordlist`) and section ID. ```bash theme={null} npx ditto-spec rules ``` | Flag | Description | | - | - | | `--styleguide ""` | Limit output to one specific style guide. | > [!NOTE] > > Requires `DITTO_TOKEN`. Without `--styleguide`, output is filtered to guides in your `styleguides` config (or all guides if unset). ## create-rules Creates a batch of style rules or terminology entries on the Ditto platform. Takes a JSON array of rules on stdin or via `--file`. ```bash theme={null} npx ditto-spec create-rules ``` | Flag | Description | | - | - | | `--file ` | Read the JSON array from a file instead of stdin. | | `--styleguide ""` | Target style guide. Overrides the `defaultStyleguide` config. | | `--section ""` | Default section for rules that don't specify their own. Applies only to rules matching the section's kind. | Each rule in the array is one of two shapes: | Shape | Fields | | - | - | | Style rule | `name` (required), `description`, `examples` (array of `{from, to}`), `tags`, `section` | | Terminology entry | `term` (required), `disallowed` (array of strings), `description`, `tags`, `section` | Both shapes can be mixed in one batch. Each rule's optional `section` (name or ID, as shown by `ditto-spec rules`) maps it to an existing section of the style guide. The section's kind must match the rule's shape — `rules` sections for style rules, `wordlist` sections for terminology entries. ```bash stdin theme={null} npx ditto-spec create-rules <<'EOF' [ { "name": "Use active voice in CTAs", "description": "Lead CTAs with a verb", "tags": ["button", "call-to-action"], "examples": [{"from": "Your settings", "to": "Open settings"}], "section": "UI Patterns" }, { "term": "sign up", "disallowed": ["signup", "sign-up"], "description": "Two words as a verb", "section": "Word List" } ] EOF ``` ```bash --file theme={null} npx ditto-spec create-rules --file rules.json --styleguide "Brand Voice" ``` > [!NOTE] > > Requires `DITTO_TOKEN`. After creating rules, run `ditto-spec pull` to sync them into your Ditto spec files. # Ditto Specs overview Source: https://developer.dittowords.com/ditto-specs-cli-reference/overview Ditto spec files put your style guide rules next to your design system components, so agents get the rules that apply to each element. ## What are Ditto specs? Ditto specs integrate Ditto with your design system. A `.ditto.md` file lives next to a design system component (a `Button`, a `Dialog`, a `Card`) and declares its **text surfaces**: every piece of user-facing copy the component renders, whether passed as props, `children`, or hardcoded in the component. The Specs CLI syncs the style guide rules that apply to those surfaces from Ditto into the file, so the rules sit in the repo next to the component they govern. The file is metadata; nothing imports it at runtime. It is there so that: * **Context is scoped to the design element.** An agent editing a `Dialog` reads the `Dialog`'s surfaces and rules, not the whole style guide. * **Usage is enforced for agents.** The spec names each surface, its tags, and its `maxLength`, so copy for that component goes through the rules every time it is written or edited. * **Ambiguity and token spend go down.** The agent gets the rules already matched to the component by tag, instead of reasoning over every rule in the workspace. ## How it works 1. **Scaffold** a spec for a component with [`ditto-spec scaffold`](/ditto-specs-cli-reference/commands#scaffold). 2. **Add surfaces and tags**, one entry per piece of user-facing text, tagged by its role in the design system. 3. **Pull** style guide rules from Ditto with [`ditto-spec pull`](/ditto-specs-cli-reference/commands#pull). The CLI matches rules to surfaces by tag. 4. **Agents read the spec** when writing or editing copy for the component and follow its rules. # Ditto Specs CLI setup Source: https://developer.dittowords.com/ditto-specs-cli-reference/setup Install the Specs CLI, configure your project, and set up API keys and agent integration ## Prerequisites * **Node.js** >= 18 * A **Ditto API key** — generate one from your [developer integrations settings](https://app.dittowords.com/developers/api-keys) * A Ditto workspace with at least one style guide ## Installation **1. Install the package** ```bash npm theme={null} npm i --save-dev @dittowords/spec-cli ``` ```bash yarn theme={null} yarn add --dev @dittowords/spec-cli ``` **2. Initialize your project** Run the init command from your repo root: ```bash theme={null} npx ditto-spec init ``` This creates two files: * `dittospec.config.json` — project configuration * `workspace.ditto.md` — workspace-level Ditto spec file for universal style guide rules **3. Set your API key** Set the `DITTO_TOKEN` environment variable: ```bash theme={null} export DITTO_TOKEN=your-api-key ``` > [!NOTE] > > You can also add `DITTO_TOKEN=your-api-key` to a `.env` file at the repo root. The CLI loads it automatically. **4. Verify the connection** Run a dry-run pull to confirm everything is wired up: ```bash theme={null} npx ditto-spec pull --dry-run ``` You should see a list of discovered Ditto spec files and what style guide rules would be written. ## Configuration The `dittospec.config.json` file controls how the CLI discovers Ditto spec files and syncs style guide rules. A minimal config: ```json theme={null} { "apiBase": "https://api.dittowords.com" } ``` A full config with all options: ```json theme={null} { "apiBase": "https://api.dittowords.com", "roots": ["design-system", "src/components"], "styleguides": ["Brand Voice", "Product UI"], "locales": ["de-DE", "fr-FR"], "defaultStyleguide": "Brand Voice" } ``` ### Properties | Key | Required | Description | | - | - | - | | `apiBase` | Yes | Ditto API base URL. | | `roots` | No | Repo-relative directories to search for Ditto spec files. Defaults to `["."]`. | | `styleguides` | No | List of style guide names or IDs to pull style guide rules from. Defaults to all guides in the workspace. | | `locales` | No | Locale codes to include (e.g. `["de-DE", "fr-FR"]`). Includes locale-scoped style guides matching these codes. Base (no-variant) guides are always included. | | `defaultStyleguide` | No | Default style guide for `create-rules`. Overridable with the `--styleguide` flag. Defaults to the first guide returned by the API. | ## Agent setup Running `init` with the `--agent` flag writes configuration for your AI development tool: ```bash theme={null} npx ditto-spec init --agent ``` > [accordion] Claude Code > > Appends a **Ditto Specs** section to `CLAUDE.md` (creates the file if absent) and writes three skill files to `.claude/commands/`: > > | Skill | What it does | > | - | - | > | `/ditto-spec-component` | Analyze a component, create Ditto spec files, auto-fill surfaces and tags, sync style guide rules | > | `/ditto-spec-audit` | Audit copy in component instances against style guide rules in their Ditto specs | > | `/ditto-spec-gaps` | Find style guide rule gaps, then create new rules on the platform | > > See [Agent Skills](/ditto-specs-cli-reference/agent-skills) for detailed documentation of each skill. > [accordion] Cursor > > Appends a **Ditto Specs** section to `.cursorrules` with instructions for reading and creating Ditto specs. > [!NOTE] > > Re-running `init --agent` is safe — it updates skill files to the current CLI version and skips `CLAUDE.md` sections that already exist. Use `--force` to overwrite skill files you've customized. # Ditto Spec Files Source: https://developer.dittowords.com/ditto-specs-cli-reference/spec-files Format and structure of .ditto.md Ditto spec files for workspaces and components ## Overview Ditto spec files use YAML frontmatter in `.ditto.md` files. Everything lives between the `---` delimiters; the markdown body below the closing `---` is unused. There are two types: **workspace specs** and **component specs**. ## Workspace spec A repo has a single `workspace.ditto.md` somewhere under the CLI's configured `roots`. It holds universal style guide rules that carry no tags — these apply to every surface in every component. It also carries an inventory of all tags available on the platform. ```yaml theme={null} --- workspace: true # Managed by Ditto — do not edit below tags: [body, button, call-to-action, dialog-title, heading, nav] rules: - name: Write in active voice description: Lead with verbs, avoid passive constructions section: Voice & Tone locales: de-DE: - name: Use informal address description: Use "Du" instead of "Sie" for all user-facing copy section: Formality --- ``` | Key | Managed by | Description | | - | - | - | | `workspace` | Developer | Must be `true`. Marks this as the workspace spec. | | `tags` | CLI (`pull`) | All tags that exist on the platform. Used as a reference when tagging surfaces. | | `rules` | CLI (`pull`) | Universal style guide rules with no tag scope — apply to all surfaces everywhere. | | `locales` | CLI (`pull`) | Locale-scoped style guide rules, keyed by locale code. | ## Component spec Each component that renders user-facing text gets an `index.ditto.md` Ditto spec file in its directory. ```yaml theme={null} --- component: DialogueModal tags: [dialog, confirmation] surfaces: headline: tags: [heading, dialog-title] maxLength: 60 content: tags: [body, dialog-body] maxLength: 240 actionText: tags: [call-to-action] maxLength: 25 cancelText: tags: [button] maxLength: 25 # Managed by Ditto — do not edit below rules: - name: Confirmation dialogs should be direct description: Keep confirmation copy terse and unambiguous section: Voice & Tone - surface: actionText name: Calls to action should use active voice description: Always lead with a verb examples: - from: "Your settings" to: "Open settings" section: Voice & Tone - term: sign up disallowed: - signup - sign-up description: Always use as two words (verb form) section: Terminology locales: de-DE: - name: Use informal address description: Use "Du" instead of "Sie" for all user-facing copy section: Formality --- ``` ### component The component name (string). Set when you run `ditto-spec scaffold`. ### tags Component-level tags (array of strings) that describe what the component **is** in your design system — e.g. `[dialog, confirmation]` for a confirmation modal, `[card, product]` for a product card. Style guide rules matching any of these tags apply to **all** surfaces in the component, cascading content governance to every piece of text it renders. Edit these freely. This is the primary mechanism for integrating Ditto specs into a design system. A component's tags capture its role as a design system element, pulling in rules about how that type of component should read — tone, voice, constraints. The individual surfaces then carry their own tags for more specific rules (see below). ### surfaces Each key is a surface — a distinct piece of user-facing text the component renders. | Property | Required | Description | | - | - | - | | `tags` | Yes | Per-surface tags that describe the text's role — e.g. `[heading, dialog-title]`, `[call-to-action]`. Used for matching style guide rules to this specific surface. | | `maxLength` | No | Maximum character length. A hard layout constraint, not a stylistic preference. | ### rules Populated by `ditto-spec pull`. Style guide rules come in two shapes: | Shape | Fields | Scope | | - | - | - | | Style rule | `name`, `description`, `examples` (optional `{from, to}` pairs), `section` | Without `surface`: all surfaces. With `surface: ""`: that surface only. | | Terminology entry | `term`, `disallowed`, `description`, `section` | Without `surface`: all surfaces. With `surface: ""`: that surface only. | ### locales Populated by `ditto-spec pull`. Keyed by locale code (e.g. `de-DE`). Contains the same style guide rule shapes as `rules`, scoped to a specific locale. Locale-scoped rules apply **in addition to** base rules when writing copy for that locale. ## Style guide rule hierarchy | Scope | Where | Applies to | | - | - | - | | **Workspace** | `workspace.ditto.md` `rules[]` | Every surface in every component | | **Component-level** | Component's `rules[]`, no `surface` field | Every surface in this component | | **Per-surface** | Component's `rules[]`, with `surface: ""` | That one surface | | **Locale-scoped** | `locales.[]` (workspace or component) | Same hierarchy as above, but only when writing copy for that locale | Base style guide rules always apply. Locale-scoped rules apply only when writing copy for the matching locale — they never conflict because each locale is a separate scope. ## Developer-owned vs CLI-managed keys | Keys | Owner | Edit by hand? | | - | - | - | | `component`, `tags`, `surfaces` | Developer | Yes — add, remove, and modify freely. | | `rules`, `locales`, workspace `tags` | CLI (`pull`) | No — overwritten on every pull. | > [!NOTE] > > Never edit `rules` or `locales` by hand. Run `ditto-spec pull` to update them from the platform. ## Surface naming conventions | Scenario | Key to use | Example | | - | - | - | | String prop | The prop name | `title`, `description` | | Nested prop | Dot notation | `primaryAction.label` | | Children prop | `$children` | `$children` | | Hardcoded/internal string | Descriptive role name | `headline`, `bodyText`, `submitLabel` | Check the `tags` key in `workspace.ditto.md` for tags available on the platform. Prefer reusing an existing tag over creating a new one — only tags that exist on the platform will match style guide rules. ## Tagging in a design system Ditto specs support two levels of tagging that mirror how design systems organize components: * **Component-level tags** describe the component itself — its role in the design system. A `DialogueModal` tagged `[dialog, confirmation]` pulls in style guide rules about how confirmation dialogs should read. These rules apply to every surface in the component. * **Surface-level tags** describe each individual piece of text — its function within the component. An `actionText` surface tagged `[call-to-action]` pulls in rules specific to CTAs (e.g. "lead with a verb"). These rules apply only to that surface. Both levels work together. For a `DialogueModal`: ```yaml theme={null} tags: [dialog, confirmation] # rules about dialogs apply to ALL surfaces surfaces: headline: tags: [heading, dialog-title] # + rules about headings apply here actionText: tags: [call-to-action] # + rules about CTAs apply here cancelText: tags: [button] # + rules about buttons apply here ``` The `headline` surface inherits style guide rules matched by `dialog`, `confirmation` (from the component), plus rules matched by `heading`, `dialog-title` (from the surface). If a rule matches both levels, it appears once at component level — broader scope wins. This means you can create style guide rules on the Ditto platform scoped to design system concepts (`dialog`, `card`, `form`, `navigation`) and have them automatically cascade to every component tagged with that concept, while surface-level tags layer on more specific guidance. ## File discovery The CLI searches directories listed in the `roots` config for any `*.ditto.md` files. `workspace.ditto.md` is identified by its `workspace: true` key; all other `.ditto.md` files are treated as component Ditto specs. # Example apps Source: https://developer.dittowords.com/example-apps Reference apps that connect the DittoPay sample project to React, iOS, and Android to show Ditto's string files, developer IDs, and variants in a working app. Each Ditto workspace starts with a sample project (**DittoPay Signup Flow**), a simple sign-up flow for a banking app with sample components, variants, variables, and plurals. The example repos below connect that sample project to a small frontend so you can see the CLI's string files, developer IDs, variants, and variable interpolation in a working app. > [!WARNING] > > The example repos were built for the `4.x` CLI and use its config schema (`sources:`), and the iOS and Android repos have not been updated since 2023. They still run, but do not copy their configuration into a new project. For a new integration, [scan your own repo](/get-started/scan-your-repo) and follow the current [CLI configuration](/cli-reference/configuration). ![The DittoPay sample project in Ditto](https://mintcdn.com/ditto/WgRtoF9rLXWv6hNh/images/sample-proj.png?fit=max\&auto=format\&n=WgRtoF9rLXWv6hNh\&q=85\&s=176820751a83f310717f0887b3cb18d8) ## Repositories Follow each repo's `README` to run it. All of them expect you to log in with `npx @dittowords/cli login` before pulling text; see [Install and log in](/cli-reference/authentication). > [card] [DittoPay: React](https://github.com/dittowords/ditto-react-demo) > > `ditto-react-demo` (default branch) uses the Ditto React SDK. > [card] [DittoPay: React with i18next](https://github.com/dittowords/ditto-react-demo/tree/react-i18next) > > `ditto-react-demo`, branch `react-i18next`, uses the CLI's i18next output. > [card] [DittoPay: iOS](https://github.com/dittowords/ditto-ios-demo) > > `ditto-ios-demo` uses `.strings` and `.stringsdict` files. > [card] [DittoPay: Android](https://github.com/dittowords/ditto-android-demo) > > `ditto-android-demo` uses `strings.xml` resources. ## What they show * How the CLI keeps text in sync from Ditto to a running app * How library components are shared across projects and referenced by developer ID * How variants (localization), plurals, and variables appear in each platform's string format For the current equivalents, see [Set up localization](/guides/set-up-localization) for format examples and [Files](/cli-reference/files) for the exact output structure. # Developer changelog Source: https://developer.dittowords.com/feedback-support/changelog Release notes for the Ditto API, CLI, MCP server, GitHub Action, and React SDK, newest first. ## November 4, 2026 ### MCP Ditto is now an official connector in the [Claude connectors directory](https://claude.ai/directory/connectors/ditto). To add only the MCP server to Claude Code, connect it once on your claude.ai account and it is available in every Claude Code session. The [Ditto plugin](/guides/connect-the-mcp#install-the-ditto-plugin) remains the recommended setup for Claude Code. See [Add the MCP server directly](/guides/connect-the-mcp#add-the-mcp-server-directly). ## October 30, 2026 ### API Several updates to the API have been made to support working with Bases. This is a brand new feature that is still in development. Attempting to use any of these endpoints while the feature is in development will throw an error. A release announcement will be published here when the feature is available to all users. Brand new endpoints related to working with Bases: * [Fetch bases](https://developer.dittowords.com/api-reference/bases/get-bases) (`GET /v2/bases`) — lists all the bases in your workspace, including each base's GitHub connection. * [Fetch a base](https://developer.dittowords.com/api-reference/bases/get-base) (`GET /v2/bases/{baseId}`) — returns a single base, with its text item count, its folders and the projects connected to it. * [Create bases](https://developer.dittowords.com/api-reference/bases/create-bases) (`POST /v2/bases`) — creates one or more bases. Supports linking new bases to a GitHub repository, which also kicks off an initial scan. * [Update a base](https://developer.dittowords.com/api-reference/bases/patch-bases) (`PATCH /v2/bases/{baseId}`) — edit an individual base. Currently supports name, developer ID and connecting to a GitHub repository. * [Create text items from a base](https://developer.dittowords.com/api-reference/text-items/create-text-items-from-base) (`POST /v2/textItems/fromBase`) — copies text items from a base into a project connected to that base, optionally placing them in a new or existing block. * [Publish to base](https://developer.dittowords.com/api-reference/projects/publish-project) (`POST /v2/projects/{projectId}/publish`) — publishes a project's Modified and New text items to its base, either all of them or the ones you name. Text items in Conflict are only acted on when you choose which version to keep. * [Update text items from base](https://developer.dittowords.com/api-reference/projects/update-text-items-from-base) (`POST /v2/projects/{projectId}/updateFromBase`) — updates Outdated or Conflict text items in a project to their base's version. The following existing endpoints now include support for Bases: * [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) - Optional `bases` filter to retrieve Base Text Items. Project Text Items now include nullable `baseId` and `baseTextItemId` fields. * [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) - Optional `bases` filter to retrieve Base Text Items. If the same text item appears in a fetched project and base, the project's value overrides the base's. * [Update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items) (`PUT /v2/textItems`) - Takes an optional `projectId` parameter on each text item to update, to disambiguate between the same base text item appearing in multiple projects. * [Delete text items](https://developer.dittowords.com/api-reference/text-items/delete-text-items) (`DELETE /v2/textItems`) - Takes an optional `projectId` parameter to disambiguate between text items that are connected to the same library component or base text item. * [Fetch projects](https://developer.dittowords.com/api-reference/projects/get-projects) (`GET /v2/projects`) - Added `baseId` to each project in the response. * [Create project](https://developer.dittowords.com/api-reference/projects/create-projects) (`POST /v2/projects`) - Added optional `baseId` field to support creating a project from a base. ## October 29, 2026 ### Ditto React — 1.6.4 Maintenance release to resolve security vulnerabilities in DOMPurify. [Release Notes](https://github.com/dittowords/ditto-react/releases/tag/v1.6.4). Legacy customers using the React SDK should upgrade to the latest version. If you cannot upgrade `ditto-react`, you may also pin the DOMPurify version to `^3.4.17` to resolve the security vulnerabilities without upgrading the Ditto SDK directly. ## October 21, 2026 ### API [Delete Text Items](https://developer.dittowords.com/api-reference/text-items/delete-text-items) (`DELETE /v2/textItems`) now optionally accepts a `projectId` to disambiguate Developer IDs that match multiple project text items. A Developer ID could match multiple project text items if it is the ID of a library component or base text item. Provide a `projectId` to specify which individual text item to delete, while leaving all other instances unchanged. The `projectId` should sufficiently disambiguate as long as there are not multiple instances of the same library component in that project. In that case, you must delete the target instance(s) through Ditto app. Prior to this change, deletion of library component instances was not supported at all through this endpoint. ### MCP [`delete_text_items`](/mcp-reference/tools#delete_text_items) now optionally accepts a `projectId` to disambiguate Developer Ids that match multiple project text items. Follows the same patterns as the API endpoint. ## October 20, 2026 #### Library component versioning For workspaces that have opted into library component versioning, adds endpoints for saving component changes without publishing them, publishing or discarding those changes, and bringing instances up to date. In workspaces that haven't opted in, these endpoints return a `404`. * [Fetch unpublished component changes](https://developer.dittowords.com/api-reference/components/get-unpublished-component-changes) (`GET /v2/components/staged`): returns the components that have unpublished changes, with those changes applied. Takes the same `filter` and `richText` parameters as Fetch components. * [Save unpublished component changes](https://developer.dittowords.com/api-reference/components/save-unpublished-component-changes) (`PATCH /v2/components/staged`): saves changes to `text`, `plurals`, `tags`, `notes`, or `characterLimit` without making them available to instances. * [Publish component changes](https://developer.dittowords.com/api-reference/components/publish-component-changes) (`POST /v2/components/staged/publish`): publishes the unpublished changes, allowing component instances to adopt those changes. * [Discard unpublished component changes](https://developer.dittowords.com/api-reference/components/discard-unpublished-component-changes) (`DELETE /v2/components/staged`): discards the unpublished changes, leaving the published component and its instances unchanged. * [Upgrade component instances](https://developer.dittowords.com/api-reference/components/upgrade-component-instances) (`PATCH /v2/components/upgrade`): brings text items that are instances of a component up to date with its latest published version. Instances with local edits return a `409` unless `discardLocalEdits` is set. In these workspaces, existing endpoints also include versioning details: * [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`): each component includes `hasUnpublishedChanges`. * [Update components](https://developer.dittowords.com/api-reference/components/patch-components) (`PATCH /v2/components`): can't update `text`, `plurals`, `tags`, `notes`, or `characterLimit` on a component with unpublished changes, since publishing those changes would overwrite the updates. * [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`): each text item includes `component`, with the component's `id` and the instance's `state`: `current`, `outdated`, `modified`, or `conflict`. It's `null` for text items that aren't instances. ## October 19, 2026 ### API [Link Figma nodes](https://developer.dittowords.com/api-reference/text-items/link-figma-nodes) (`POST /v2/textItems/linkFigmaNodes`) now supports linking Figma text nodes to many text items in one request. Request body now takes `links`, a mapping of text item developer IDs to Figma node IDs. Up to 500 distinct node IDs are allowed per request across all links. Reports whether linking was successful for each node in request. > [!WARNING] > > The previous request body (`textItemId` with `figma.nodeIds`) is no longer accepted. Send one `links` entry to link > nodes to a single text item. Omitting `branchId` always targets linking nodes in the main Figma file. To link nodes in a branch, pass its ID explicitly in the top-level `branchId` field. ### MCP Add the [`update_project`](/mcp-reference/tools#update_project) tool, which supports updating an individual project's name, Developer ID, and/or connected base. More details on bases and connecting projects to bases coming soon! Add the [`link_figma_nodes_to_text_items`](/mcp-reference/tools#link_figma_nodes_to_text_items) tool for linking Figma text nodes to many text items in one call. It takes a mapping of text item developer IDs to Figma node IDs, up to 500 node IDs per call, and returns a count per outcome plus the nodes that did not link. > [!WARNING] > > The `link_figma_nodes_to_text_item` tool has been removed. Use `link_figma_nodes_to_text_items` with a single entry in > `links` to link nodes to one text item. Omitting `branchId` always targets linking nodes in the main Figma file. To link nodes in a branch, pass its ID explicitly in the top-level `branchId` field. ### Ditto CLI - 5.12.0 v5.12.0 released. Bumps `@dittowords/text-extract` to 0.4.1. The `scan` command now reports `files.skipped_too_large` in the extract summary. This count covers files larger than 1 MB, which are not scanned. ## October 16, 2026 ### API Adds a new endpoint: [Update Project](https://developer.dittowords.com/api-reference/projects/patch-projects) (`PATCH /v2/projects/{projectId}`) with support for changing the `name` or `developerId` of a project, as well as connecting to or disconnecting from a `base` (new feature coming soon!). ## October 15, 2026 ### API #### Filter text items by component Adds a `components` filter to [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) that returns only the text items that are instances of the given library components. #### Readable validation errors When a request doesn't match an endpoint's request schema, the `400` response now has a readable `message` that names each invalid field and why it's invalid. The invalid fields are also listed in `details.errors`, each with its `location` in the request (`body`, `query`, or `params`), its `path`, and a `message`. Previously, these responses returned the raw output of our validation library as an array. **Affected endpoints:** all `/v2` endpoints. Legacy endpoints are unchanged. ## October 12, 2026 ### API Released several bugfixes and standardization measures across the Text Item and Component fetch and export endpoints. #### New JSON metadata parameters Adds two new optional parameters for JSON exports: * `excludeVariantMetadata?: boolean`. By default, all three JSON exports include the following fields at the bottom of each variant file: `__variant-name` and `__variant-description`. You can now set this query param to `true` to omit these two fields. Applies to the following formats: `json_icu`, `json_i18next`, `json_vue_i18n`. Ignored for base text (non-variant) exports or any other formats. * `includeVariableSummary?: boolean`. If `true`, will add a `__variables_used` field to the bottom of the exported file, which will contain an alphabetical list (array) of the names of all the variables that are present in the export. If omitted or set to false, this field will not be added to the file. Applies to the following formats: `json_i18next`, `json_vue_i18n`. Ignored for any other formats. **Affected endpoints:** * [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) * [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) #### Empty text is now included in exports Text items and components with empty text are now included in every export format, instead of being left out. This applies to empty base text, empty variant text, and empty plural forms; the behavior change primarily affects empty variants. Items that don't have the requested variant at all are still left out of that variant's export. Because these keys used to be missing, most localization libraries fell back to either the base text or to the key itself. Now that they're included with an empty value, your app will render them as empty. > [!WARNING] > > This is a behavior change for plaintext exports that contain empty text. If a variant's text is empty by mistake, your > app will now show it as empty instead of falling back to the base text. Fill in the variant's text, or remove the > variant from that text item or component to continue rendering base text. **Format-specific details:** * `json_vue_i18n`: An empty plural form is written as the literal `{''}`, and a plural form that contains only whitespace is written as a literal that keeps it, such as `{' '}`. vue-i18n can't compile a plural message with an empty choice, so plural messages with an empty form previously caused an error when they were rendered. * `json_icu` and `arb`: Plural messages are now included when their first form is empty. Previously, the whole message was left out, even if its other forms had text. FormatJS (`react-intl`) treats an empty message as missing by default; set `fallbackOnEmptyString: false` to render it as empty. * `android`: Empty strings and plural forms are written as self-closing elements, such as ``. This also fixes malformed XML in exports that contained an empty plural form. **Affected endpoints:** * [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) * [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) #### Consistent `html` and `html_paragraphs` rich text output Standardizes the html rich text outputs across all four related endpoints. `richText="html_paragraphs"` wraps all text in `

` tags, while `richText="html"` does not wrap its content in outer tags, and uses `
` for line breaks. All other html generation is identical between the two formats. **Affected endpoints:** * [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) * [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) * [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) * [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) Prior to this change, the `html` parameter returned the unwrapped text from the Fetch endpoints, and the paragraph-wrapped text from the Export endpoints. Now, all four endpoints return the same output format for each richText option. > [!WARNING] > > This is a behavior change for the Export endpoints. To continue receiving identical output, you must switch from > `html` to `html_paragraphs`. The Create and Update endpoints are unaffected by this change. They still maintain a single `html` rich text option, which can convert either paragraph format into Ditto rich text. **Example:** For a text item with two paragraphs: > **Welcome back!**\ > > Your order has shipped. `richText=html` returns: ```html theme={null} Welcome back!
Your order has shipped. ``` `richText=html_paragraphs` returns: ```html theme={null}

Welcome back!

Your order has shipped.

``` #### Variant notes in `arb` and `ios-strings` exports Variant exports now use the variant's own notes as each string's `description` in `arb`, and as the comment above each string in `ios-strings`. If a variant has no notes, its base notes are used instead. Base exports are unchanged. Previously, both formats always used the base notes, even in variant exports. **Affected endpoints:** * [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) * [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) ### Ditto CLI - 5.11.0 Updates the CLI to support the new `html_paragraphs` rich text parameter. Prior to the API changes, the CLI used unwrapped `html` for the plain JSON export and the JSON export with `i18next` and `vue-i18n` frameworks. All other formats (including `json_icu`) returned the `html_paragraphs` style. The API changes mentioned above affect all CLI versions immediately. If you were using one of the other formats with rich text, and want to continue exporting with the legacy format, you will need to upgrade to this CLI version and change your config file from `richText: html` to `richText: html_paragraphs`. Additionally, this version includes the following bug fixes: * Generates plurals correctly for `format: json, framework: vue-i18n`, as returned from the Export endpoints. * Respects the `integrated` filter for `components` fetches. Previously, it was only applied to text items. ## October 7, 2026 ### API Add `newDeveloperId` to [Update a style guide](https://developer.dittowords.com/api-reference/styleguides/patch-styleguides) (`PATCH /v2/styleguides`) for renaming a style guide's developer ID. It can be set alongside other field changes in the same request, and must be unique among the workspace's style guides. ### MCP Add `newDeveloperId` to the [`update_styleguide`](/mcp-reference/tools#update_styleguide) tool for renaming a style guide's developer ID. It can be combined with other field changes in the same call. ## October 5, 2026 ### API Improvements to plural exports for json formats. * `json_i18next` Now returns the unsuffixed key alongside all plural keys. This brings it in line with what the CLI exports for `json` and `json + i18next`. * `json_vue_i18n` Resolves a minor bug in how plurals were generated if you had a `zero` and `other` form defined but not a `one` form. This format still drops `two`, `few`, and `many` forms, which are not supported by Vue without a custom rule definition. This change is not yet reflected in the CLI. This affects the following endpoints: * [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) * [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) ## October 1, 2026 ### MCP Add the [`translate_text_items`](/mcp-reference/tools#translate_text_items) tool for translating project text items into a variant's locale, applying any style guide scoped to that locale. Add the [`translate_library_components`](/mcp-reference/tools#translate_library_components) tool for translating library components into a variant's locale, applying any style guide scoped to that locale. ## September 17, 2026 ### Ditto CLI - 5.10.0 [`scan`](/cli-reference/commands#scan) now requires a clean checkout of your repository's default branch. Scanning from a tree with uncommitted changes, from a non-default branch, or from a detached `HEAD` stops with a message explaining what to change, before anything is uploaded. Scan records the commit and branch it read, and the code links Ditto builds point at them. A scan taken from anywhere else describes lines your default branch never had, so a later scan reports those links as changed or removed when nothing actually changed. The default branch comes from `origin/HEAD`, falling back to a local `main` or `master`. Because that reference is set when you clone and doesn't update on its own, a mismatch is confirmed against your remote first, so a branch renamed after you cloned won't block you. Scanning outside a Git repository is unaffected. ## September 3, 2026 ### API Add [Publish component](https://developer.dittowords.com/api-reference/components/publish-component) (`POST /v2/components/publish`) endpoint for publishing an existing text item to the component library. Add [Link components](https://developer.dittowords.com/api-reference/components/link-components) (`PATCH /v2/components/link`) endpoint for linking text items to a library component by developer ID. Add [Unlink components](https://developer.dittowords.com/api-reference/components/unlink-components) endpoint, which unlinks every text item that currently shares a given developer ID with its sibling instances. Accepts an optional `projectId` to scope the unlink to a single project. ### MCP Add the [`publish_library_component`](/mcp-reference/tools#publish_library_component) for publishing an existing text item to the component library. Add the [`link_text_items_to_component`](/mcp-reference/tools#link_text_items_to_component) for linking text items to a library component by developer ID. Add the [`unlink_text_items_from_component`](/mcp-reference/tools#unlink_text_items_from_component) tool, which unlinks every text item that currently shares a given developer ID with its sibling instances. Accepts an optional `projectId` to scope the unlink to a single project. ## September 2, 2026 ### API Add [Link Figma nodes](https://developer.dittowords.com/api-reference/text-items/link-figma-nodes) (`POST /v2/textItems/linkFigmaNodes`) endpoint for linking already-synced Figma text nodes to an existing text item, up to 50 node IDs per request. ### MCP Add the `link_figma_nodes_to_text_item` tool for linking already-synced Figma text nodes to an existing text item. Replaced by [`link_figma_nodes_to_text_items`](/mcp-reference/tools#link_figma_nodes_to_text_items). Add `figma` to the [`search_ditto_text`](/mcp-reference/tools#search_ditto_text) `fields` parameter, returning the Figma text node IDs linked to each text item. ## August 27, 2026 ### API Add [ARB](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification) as an export format for [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) and [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) — provide `?format=arb`. See [ARB JSON](/string-formats/json#arb-json) for the content shape. ### Ditto CLI - 5.9.0 Add `framework: arb` and `framework: icu` for `json`-format outputs, generating [ARB](/string-formats/json#arb-json) and [ICU](/string-formats/json#icu-json) JSON respectively. See [Frameworks](/cli-reference/files#framework-specific-files). Add [`androidLocales`](/cli-reference/configuration#androidlocales), which maps Ditto variant IDs to Android locale-qualified resource directories (`values-/`) — the Android counterpart to `iosLocales`. Unlike `iosLocales`, there's no need to map `base`, since Android already has a default resource directory (`values/`) that the platform falls back to automatically. Add [`androidLocalesOutDir`](/cli-reference/configuration#androidlocalesoutdir) and [`iosLocalesOutDir`](/cli-reference/configuration#ioslocalesoutdir), which let Android and iOS locale-organized output be written to separate root directories — useful since they typically need to live in different parts of a codebase. ## August 24, 2026 ### Ditto CLI - 5.8.1 [`scan`](/cli-reference/commands#scan) now records which part of your repository it looked at, alongside the Git context added in 5.8.0. Scanning a repository root is recorded as covering the whole repository; scanning a single directory is recorded as covering only that directory. This lets Ditto tell the difference between a string removed from your code and a string that simply was not part of the scan, so re-scanning one directory implies nothing about the rest of the repository. ## August 20, 2026 ### Ditto CLI - 5.8.0 [`scan`](/cli-reference/commands#scan) now records the Git repository, commit, and branch of the code it scans. Ditto uses these to recognize a codebase it has already seen, so a later scan of the same repository can be matched against an earlier one instead of importing every string again. This is read from your local Git checkout. Scan makes no additional network requests, does not send Git credentials, and records nothing about your remote beyond its host and path. Scanning outside a Git repository still works — the CLI notes that the scan can be imported but not re-synced, and continues. ## August 18, 2026 ### Ditto CLI - 5.7.2 Move [`scan`](/cli-reference/commands#scan) text extraction into the shared `@dittowords/text-extract` library. Extraction behavior is unchanged. ## August 17, 2026 ### API Add `richText` parameter to [Create components](https://developer.dittowords.com/api-reference/components/create-components) (`POST /v2/components`) and [Patch components](https://developer.dittowords.com/api-reference/components/patch-components) (`PATCH /v2/components`). When `richText` param is set to `html`, all text values in the update request — including plural and variant text — will be parsed as an HTML string and saved as rich text. ## August 13, 2026 ### Ditto CLI - 5.7.1 Improve how [`scan`](/cli-reference/commands#scan) decodes the text it extracts. Escape sequences, XML entities, and inline markup are now handled per file format, so candidates arrive in Ditto as the text a user sees instead of as raw source. ### MCP Add the [`list_statuses`](/mcp-reference/tools#list_statuses) tool, which lists the statuses text items and library components can be set to, in workflow order. The MCP server now supports logging in with your Ditto account via OAuth, in addition to a static API token. See [Installation](/guides/connect-the-mcp) for setup instructions per client. ### API Add [Delete components](https://developer.dittowords.com/api-reference/components/delete-components) (`DELETE /v2/components`) endpoint for deleting library components by developer ID. Add [Fetch statuses](https://developer.dittowords.com/api-reference/statuses/get-statuses) (`GET /v2/statuses`) endpoint for listing the statuses available in a workspace, in workflow order. ## August 11, 2026 ### API Add `richText` parameter to [Update text items](https://developer.dittowords.com/api-reference/text-items/patch-text-items) (`PATCH /v2/textItems`) and [Create text items](https://developer.dittowords.com/api-reference/text-items/create-text-items) (`POST /v2/textItems`). When `richText` param is set to `html`, all `text` values in the update request — including plural and variant text — will be parsed as an HTML string and saved as rich text. For example, the following request would store formatted string "**Inspiration** for your next trip" as the Korean variant: ```json theme={null} { "richText": "html", "variantId": "korean", "updates": [ { "developerId": "homepage.section.inspiration", "text": "Inspiration for your next trip" } ] } ``` * Supported formatting: ``/``, ``/``, ``, ``, and `
` for line breaks. * `` and `` are only supported for workspaces with superscript and subscript enabled. * We drop unsupported markup such as links, lists, or headings but keep its text. * `{{variable_name}}` placeholders still resolve, and keep any formatting applied around them. * Without the parameter, text is saved as plaintext and any markup is stored literally, unchanged from previous behavior. ## August 10, 2026 ### MCP Add [`create_variables`](/mcp-reference/tools#create_variables), [`update_variables`](/mcp-reference/tools#update_variables), and [`delete_variables`](/mcp-reference/tools#delete_variables) tools for managing variables from your editor. ### API Add [Create variables](https://developer.dittowords.com/api-reference/variables/create-variables) (`POST /v2/variables`), [Update variables](https://developer.dittowords.com/api-reference/variables/patch-variables) (`PATCH /v2/variables`), and [Delete variables](https://developer.dittowords.com/api-reference/variables/delete-variables) (`DELETE /v2/variables`) endpoints. * Renaming a variable with `newName` rewrites every `{{variable_name}}` placeholder referencing it — across text items, library components, variants, and plurals — so existing text keeps resolving. * A variable can't be deleted while any text item or library component still references it. ## August 6, 2026 ### MCP Add [`create_variants`](/mcp-reference/tools#create_variants), [`update_variants`](/mcp-reference/tools#update_variants), and [`delete_variants`](/mcp-reference/tools#delete_variants) tools for managing variants from your editor. ### API Add [Create variants](https://developer.dittowords.com/api-reference/variants/create-variants) (`POST /v2/variants`), [Update variants](https://developer.dittowords.com/api-reference/variants/patch-variants) (`PATCH /v2/variants`), and [Delete variants](https://developer.dittowords.com/api-reference/variants/delete-variants) (`DELETE /v2/variants`) endpoints. * At most one variant per workspace can hold a given locale code. * A variant can't be deleted while any text item or library component still has text for it. ## August 5, 2026 ### Ditto CLI - 5.7.0 Add the [`login`](/cli-reference/commands#login) and [`logout`](/cli-reference/commands#logout) commands. `login` signs you in through your browser, so the CLI no longer needs an API key. ```bash theme={null} npx @dittowords/cli login ``` * The session is saved to `$HOME/.config/ditto` and renews itself as you keep using the CLI. Commands act as you — your Ditto account and its permissions — instead of as whoever generated an API key. * `logout` forgets the session on this machine and revokes it with Ditto. * API keys still work. `DITTO_TOKEN` takes precedence over a saved session, so CI and other headless environments are unaffected. See [credential precedence](/cli-reference/authentication#credential-precedence). ## August 3, 2026 ### MCP Add [`create_styleguide`](/mcp-reference/tools#create_styleguide), [`list_styleguides`](/mcp-reference/tools#list_styleguides), and [`update_styleguide`](/mcp-reference/tools#update_styleguide) tools for managing style guides themselves — sections, the default-enabled setting, and metadata — as opposed to the rules inside them. Add [`create_styleguide_rules`](/mcp-reference/tools#create_styleguide_rules), [`update_styleguide_rules`](/mcp-reference/tools#update_styleguide_rules), and [`delete_styleguide_rules`](/mcp-reference/tools#delete_styleguide_rules) tools for managing the rules inside a style guide section. Add [`list_component_folders`](/mcp-reference/tools#list_component_folders), [`create_component_folders`](/mcp-reference/tools#create_component_folders), [`update_component_folders`](/mcp-reference/tools#update_component_folders), and [`delete_component_folders`](/mcp-reference/tools#delete_component_folders) tools for managing component library folders from your editor. ### API Add [Create a style guide](https://developer.dittowords.com/api-reference/styleguides/create-styleguides) (`POST /v2/styleguides`) and [Update a style guide](https://developer.dittowords.com/api-reference/styleguides/patch-styleguides) (`PATCH /v2/styleguides`) endpoints, along with [Create style guide rules](https://developer.dittowords.com/api-reference/styleguides/create-styleguide-rules) (`POST /v2/styleguides/rules`), [Update style guide rules](https://developer.dittowords.com/api-reference/styleguides/patch-styleguide-rules) (`PATCH /v2/styleguides/rules`), and [Delete style guide rules](https://developer.dittowords.com/api-reference/styleguides/delete-styleguide-rules) (`DELETE /v2/styleguides/rules`) endpoints for managing the rules inside a section. Add [Fetch component folders](https://developer.dittowords.com/api-reference/component-folders/get-component-folders) (`GET /v2/componentFolders`), [Create component folders](https://developer.dittowords.com/api-reference/component-folders/create-component-folders) (`POST /v2/componentFolders`), [Update component folders](https://developer.dittowords.com/api-reference/component-folders/patch-component-folders) (`PATCH /v2/componentFolders`), and [Delete component folders](https://developer.dittowords.com/api-reference/component-folders/delete-component-folders) (`DELETE /v2/componentFolders`) endpoints. ## July 31, 2026 ### MCP Add the [`suggest_edit`](/mcp-reference/tools#suggest_edit) tool, which checks text against your workspace's style guide rules and returns suggested edits — the same rule-based suggestions shown in the Ditto app. Check an existing text item by developer ID, or freeform text that isn't saved as a text item yet. ## July 29, 2026 ### MCP Add [`list_project_blocks`](/mcp-reference/tools#list_project_blocks), [`create_project_blocks`](/mcp-reference/tools#create_project_blocks), [`update_project_blocks`](/mcp-reference/tools#update_project_blocks), and [`delete_project_blocks`](/mcp-reference/tools#delete_project_blocks) tools for managing the blocks that group text items within a project. ### API Add [Fetch project blocks](https://developer.dittowords.com/api-reference/project-blocks/get-project-blocks) (`GET /v2/projectBlocks`), [Create project blocks](https://developer.dittowords.com/api-reference/project-blocks/create-project-blocks) (`POST /v2/projectBlocks`), [Update project blocks](https://developer.dittowords.com/api-reference/project-blocks/patch-project-blocks) (`PATCH /v2/projectBlocks`), and [Delete project blocks](https://developer.dittowords.com/api-reference/project-blocks/delete-project-blocks) (`DELETE /v2/projectBlocks`) endpoints. ## July 27, 2026 ### Ditto CLI - 5.6.3 Improve how [`scan`](/cli-reference/commands#scan) handles codebases that are larger than a workspace's plan's scan limit. Add the `--list-directories` flag. `scan --list-directories` prints the number of candidate strings found in each directory and exits without uploading anything, so you can choose a scope before running a real scan. ```bash theme={null} npx @dittowords/cli scan . --list-directories ``` * Counts are rolled up: each directory includes the strings found in its subdirectories. * Directories are printed in tree order, indented by depth, with the whole scan total at the top. When a scan exceeds your plan's limit, the error now suggests narrower paths to scan instead of just reporting the limit. * The message lists the largest subdirectories that each fit within your remaining limit, with their string counts and a ready-to-run command for each. * The limit is checked before candidates are uploaded, so an oversized scan fails right away instead of after the upload finishes. ### MCP Add the [`create_ditto_project`](/mcp-reference/tools#create_ditto_project) and [`list_project_folders`](/mcp-reference/tools#list_project_folders) tools for creating projects and looking up project folders from your editor. ### API Add [Create a project](https://developer.dittowords.com/api-reference/projects/create-projects) (`POST /v2/projects`) endpoint. Optionally provide a folder's developer ID to create the project inside that folder. ## July 24, 2026 ### MCP Add [`create_library_components`](/mcp-reference/tools#create_library_components) and [`update_library_components`](/mcp-reference/tools#update_library_components) tools for managing library components from your editor, and the [`list_component_folders`](/mcp-reference/tools#list_component_folders) tool for looking up their folders. ## July 22, 2026 ### Documentation Document the [agent setup package](https://github.com/dittowords/ditto-agent-setup) as the default way to integrate Ditto with Claude Code. See [Agent setup package](/guides/connect-the-mcp). Standardize the API token environment variable to `DITTO_TOKEN` across the CLI, Ditto Specs, and MCP setup docs (previously `DITTO_API_KEY` and `DITTO_API_TOKEN`). ### MCP Add [`list_variants`](/mcp-reference/tools#list_variants) and [`list_variables`](/mcp-reference/tools#list_variables) tools, and expose variants and variables on the text item and library component tools — reference a variable with a `{{variable_name}}` placeholder, or set variant-specific text via a variant's developer ID. ## July 10, 2026 ### MCP Add [`create_text_items`](/mcp-reference/tools#create_text_items), [`update_text_items`](/mcp-reference/tools#update_text_items), and [`delete_text_items`](/mcp-reference/tools#delete_text_items) tools for managing text items in bulk from your editor, and the [`list_projects`](/mcp-reference/tools#list_projects) tool for looking up a project's developer ID. ## July 9, 2026 ### API Add [Fetch activity](https://developer.dittowords.com/api-reference/activity/get-activity) (`GET /v2/activity`) endpoint for retrieving a paginated list of change events in your workspace, optionally filtered by project, text item, library component, or entity type. ## July 1, 2026 ### API Add `blockName` field to [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) response. * Each text item now includes a `blockName` field: the name of the block the text item belongs to, or `null` if it is not in a block. ## June 25, 2026 ### API Add `sort` parameter to [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`). * `id` (default) sorts the response alphabetically by developer ID (matching previous behavior) * `project_order` groups items by project, returned alphabetically by project developer ID, then sorts within projects based on the custom sort order set up in the web app. ## June 16, 2026 ### Ditto CLI - 5.6.0 Add the `scan` command. `scan ` analyzes a local directory for user-facing text and sends the candidates to Ditto, where you can review them and turn them into a content system tailored to your product. See the [Scan documentation](/cli-reference/commands#scan). * This release includes packages that require **Node.js 20 or higher**. ## June 10, 2026 ### API Add new variant fields `variants[n].status`, `variants[n].assignee`, `variants[n].notes`, and `variants[n].characterLimit` to the [Create text items](https://developer.dittowords.com/api-reference/text-items/create-text-items) resource. * `assignee` must be a valid email matching a user in your workspace * variant `status` must be a valid status in your workspace ## June 4, 2026 ### API Add `folderId` to the [Update components](https://developer.dittowords.com/api-reference/components/patch-components) resource, allowing users to organize their components in bulk. * If `folderId` of `null` or `"root"` is provided, will move component into "All Components". * If `folderId` omitted, component folder will remain unchanged Add [Get style guides](https://developer.dittowords.com/api-reference/styleguides/get-styleguides) (`GET /v2/styleguides`) endpoint for fetching the style guides created in your workspace, along with their sections and rules. ## May 28, 2026 ### API Add `assignee`, along with new variant fields `variants[n].status`, `variants[n].assignee`, `variants[n].notes`, and `variants[n].characterLimit` to the [Create components](https://developer.dittowords.com/api-reference/components/create-components) resource. * `assignee` must be a valid email matching a user in your workspace * variant `status` must be a valid status in your workspace ## May 18, 2026 ### API Add `characterLimit` field to [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) and [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) responses. * Each text item and component in the response now includes a `characterLimit` field: a positive integer when a limit is set, or `null` when no limit is configured. * For variant responses, `characterLimit` reflects the variant's own character limit rather than the base value. ## May 12, 2026 ### API Update Text Item and Component endpoints to support variant assignee, notes and character limit. * [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) and [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) now return variant assignee and notes in variant response objects, not the base properties. * Requests that include an assignee filter and a variantId return only items whose base and variant assignees both match the filter. This applies to [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`), [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`), [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`), and [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`). * Requests that include assignee, notes and/or character limit along with a variantId in the payload of [Update text items](https://developer.dittowords.com/api-reference/text-items/patch-text-items) (`PATCH /v2/textItems`) or [Update components](https://developer.dittowords.com/api-reference/components/patch-components) (`PATCH /v2/components`) will update that variant's properties, not the base values. ## April 30, 2026 ### API Add component name to the response of [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) endpoint. Each component in the response will now include a `name` field matching the component name. The same component name will be returned across base text, variants and plural forms. ## April 6, 2026 ### API Add [Update components](https://developer.dittowords.com/api-reference/components/patch-components) (`PATCH /v2/components`) endpoint. Supports updating text, tags, status, and assignee on components identified by developer ID. Includes optional variant targeting with `variantId` and `forceVariantCreation` parameters. Add `PATCH /v2/textItems` as the preferred method for updating text items. `PUT /v2/textItems` continues to work but is now deprecated. ## April 1, 2026 ### Ditto CLI - 5.5.1 Dependency updates and internal improvements. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.5.1). ## March 31, 2026 ### Ditto CLI - 5.5.0 Add CLI support for filtering by tags. Add a `tags` configuration to your config file to scope text items and components by one or more tags, with support for `AND`/`OR` operators. ```yaml theme={null} tags: values: - "tag-1" - "tag-2" operator: "AND" ``` The `tags` filter can be specified at the top level of your config or within individual outputs (output-level overrides top-level). When `operator` is `"AND"`, only items with all specified tags are returned. When `operator` is `"OR"` (or omitted), items with any of the specified tags are returned. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.5.0). ## March 27, 2026 ### MCP Add the [`get_workspace_tags`](/mcp-reference/tools#get_workspace_tags) tool, which lists the tags currently in use across the workspace — useful before adding tags to a style guide rule, since only existing tags are allowed. Add the first tools for creating, updating, and deleting style guide rules from your editor. ## March 26, 2026 ### API Add `assignee` and `tags` filters to [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`), [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`), and [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`). The `assignee` filter accepts an email address or `null` to match unassigned items. The `tags` filter accepts an array of tag values with an optional `operator` field (`AND` or `OR`, defaulting to `OR`). ## March 24, 2026 ### Ditto CLI - 5.4.0 Add CLI support for filtering by integration status. Add `integrated: true` to your config file to fetch only text items and components that have been marked as integrated into development. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.4.0). ### API Add the ability to update the `integrated` flag of a text item via [Update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items) (`PUT /v2/textItems`). ## March 23, 2026 ### API Add `integrated` field to the response data of: * [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) * [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`). Add optional `integrated` boolean filter to: * [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) * [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) * [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) * [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) ## March 20, 2026 ### MCP The Ditto MCP server can now run remotely over Streamable HTTP, instead of only as a local stdio server. See [Installation](/guides/connect-the-mcp) for how to connect Claude Code, Cursor, VS Code, Windsurf, Codex, and other MCP clients. ## March 19, 2026 ### API Add `plurals` field to [Update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items) (`PUT /v2/textItems`) API endpoint. The `plurals` field supports `upsert` and `remove` operations for managing plural forms (zero, one, two, few, many, other) on text items. Existing plural forms not mentioned in either operation are preserved. Supports variant plurals via the `variantId` parameter. ## March 18, 2026 ### API Add `assignee` and `tags` filters to [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`). The `assignee` filter accepts an email address or `null` to match unassigned text items. The `tags` filter accepts an array of tag values with an optional `operator` field (`AND` or `OR`, defaulting to `OR`). [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) and [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) now include the `assignee` field in the response payload, returned as the assignee's email address or `null` if unassigned. ## March 16, 2026 ### API Add `assignee` field to [Update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items) (`PUT /v2/textItems`) API endpoint. The `assignee` field accepts an optional email string of a user in the workspace to assign to the text item. Setting `assignee` to `null` will remove the current assignee. ## March 12, 2026 ### MCP Add the [`search_ditto_text`](/mcp-reference/tools#search_ditto_text) tool, which searches your workspace for existing text items and library components by content — so agents can find and reuse strings instead of generating new ones. ## March 5, 2026 ### MCP Ditto's MCP server is here! It connects AI coding agents to your team's content in Ditto, starting with the [`get_styleguide_rules`](/mcp-reference/tools#get_styleguide_rules) tool for fetching workspace and project-level style guide rules. See the [MCP overview](/mcp-reference/overview) to get started. ## February 10, 2026 ### API Add [Create components](https://developer.dittowords.com/api-reference/components/create-components) (`POST /v2/components`) endpoint for creating new library components. ## February 2, 2026 ### API [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`), [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`), [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`), and [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) now return a `400` error response when statuses that don't exist in your workspace are provided in the status filter. ## January 14, 2026 ### API [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) and [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) now return separate records for each plural form. Plural forms follow the standard i18n plural forms outlined [here](https://www.i18next.com/translation-function/plurals). Each record in the response includes a `pluralForm` value, and for each `pluralForm` on a text item or component, a separate record is returned with that `pluralForm` and its respective value. If a record has a `pluralForm`, it will be appended to the end of the developer ID. Example response: ```json theme={null} [ { "id": "some-items", "text": "Some items", "pluralForm": null, ... }, { "id": "some-items_other", "text": "Some items", "pluralForm": "other", ...}, { "id": "some-items_one", "text": "An item", "pluralForm": "one", ... } ] ``` ### Ditto CLI - 5.3.1 Default [JSON file formats](https://developer.dittowords.com/cli-reference/files#json) now include plurals, where the standard unicode plural rule (zero, one, two, few, many, other) is appended to the end of the developer ID key. Example output: ```json theme={null} { "some-items": "Some items", "some-items_one": "An item", "some-items_other": "{{count}} items" } ``` See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.3.1). ## January 7, 2026 ### API Add [Export text items](https://developer.dittowords.com/api-reference/text-items/export-text-items) (`GET /v2/textItems/export`) and [Export components](https://developer.dittowords.com/api-reference/components/export-components) (`GET /v2/components/export`) endpoints for fetching text items and components in `.json` ICU, `.json` i18next, `.json` Vue i18n, `.strings`, `.stringsdict`, or `.xml` formats. ### Ditto CLI - 5.3.0 Add CLI support for Android `.xml`, iOS `.strings` and `.stringsdict`, and ICU `.json` formats. In conjunction with iOS format additions, add iOS Locale directory generation for configured variant -> locale mappings in config. If iOS locales are configured, CLI will also auto-generate a Ditto.swift file for reading locale values. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.3.0). ## December 11, 2025 ### Ditto CLI - 5.2.0 Add CLI support for filtering [text items/components by status](/cli-reference/configuration#statuses) when pulling changes. The filtering logic used for the cli is the same as the [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) and the [Fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) API endpoints. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.2.0). ## December 10, 2025 ### API Relax requirement in [Update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items) (`PUT /v2/textItems`) API endpoint that requires text to be included in the request. Add `tags` field which is an optional `string[]` that if set in the request will replace the existing tags with the specified tags on the textItem. ## December 9, 2025 ### API Clarified expected behavior with variant and status filtering for [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) API endpoint. Variants that match the variant filter should only be returned if they match the status filter and their base text also matches the status filter. Added query param in [Fetch components](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/components`) API endpoint status filter that allows for filtering components by status. Variants that match the variant filter should only be returned if they match the status filter and their base text also matches the status filter. ## December 5, 2025 ### API Added [Fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) API endpoint status filter that allows for fetching textItems by status. Relax requirement in [Update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items) (`PUT /v2/textItems`) API endpoints that requires all textItems to be in the same project. Now updates can include textItems that span multiple projects and all will be updated. ### Documentation Fix env var naming used in the cli for setting the ditto api token to the correct value `DITTO_TOKEN` ## November 5, 2025 ### API Added [delete text items](https://developer.dittowords.com/api-reference/text-items/delete-text-items) (`DELETE /v2/textItems`) API endpoint for deleting text items. ## October 6, 2025 ### Ditto Github Action - 1.0.2 Updates the Github Action to work with CLI `v5.x`, compatible with both new and legacy Ditto. After upgrading, the Github Action will fetch from new Ditto projects/components by default. Legacy users will need to add `pull-from-legacy: "true"` to their workflow file to continue fetching legacy text. If you wish to use this action for both new and legacy Ditto, simply create separate steps with different inputs. See the [README](https://github.com/marketplace/actions/ditto-github-action) for more details and examples. Release notes: * [1.0.2](https://github.com/dittowords/ditto-github-action/releases/tag/v1.0.2) * [1.0.1](https://github.com/dittowords/ditto-github-action/releases/tag/v1.0.1) * [1.0.0](https://github.com/dittowords/ditto-github-action/releases/tag/v1.0.0) ### Ditto CLI - 5.1.0 A small update to support the `-m` and `--meta` flags in commands. This is used internally for analytics and should not affect customer workflows. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.1.0). ## October 1, 2025 ### Ditto CLI - 5.0.0 First official stable release that provides support for fetching project text and components from the new Ditto experience, including all features from the v5 Beta release (through 5.0.0-beta.8). This version maintains full support for fetching from legacy projects and components. Simply include the `--legacy` flag along with any command from `v4.x`. Users with both new and legacy projects may pull data from both places into the same codebase by maintaining two separate config files and output directories, and including the path to the right config file in your pull command. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.0.0). ## September 29, 2025 ### API Added [fetch components](https://developer.dittowords.com/api-reference/components/get-components) (`GET /v2/components`) API endpoint for fetching components in the user's workspace. ### Ditto CLI - 5.0.0-beta.8 Added support for fetching components in addition to project text via the CLI. Include `components` in your config file to fetch components from the specified folders. Include `projects` in your config file to fetch text from the specified projects. **Breaking change:** If `projects` or `components` are omitted from the config file, will not fetch text from that area. Previously, if `projects` was omitted, it would fetch text from all projects. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.8). ## September 11, 2025 ### API Added the ability to create variants from [update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items#body-force-variant-creation) (`PUT /v2/textItems`) API endpoint with the `forceVariantCreation` parameter. ## August 18, 2025 ### Ditto CLI - 5.0.0-beta.7 Added support for fetching and outputting rich text (HTML) content for Ditto text, by adding `richText: html` to the config file. At this time, this version is still in Beta, accessible via `@dittowords/cli@beta`. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.7). ## July 24, 2025 ### API Added [create text items](https://developer.dittowords.com/api-reference/text-items/create-text-items) (`POST /v2/textItems`) API endpoint for creating new text items. ## July 8, 2025 ### API Added [update text items](https://developer.dittowords.com/api-reference/text-items/update-text-items) (`PUT /v2/textItems`) API endpoint for editing existing text items. ## April 30, 2025 ### Ditto CLI - 5.0.0-beta.6 Beta release of CLI for Ditto 2.0. This version supports fetching text from new, non-legacy Ditto projects. It requires a new format for the config file and supports returning json files formatted for i18next. This version is still in beta, and can be accessed via `@dittowords/cli@beta`. Supports all legacy features via `--legacy` flag. Release notes: * [5.0.0-beta.6](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.6) * [5.0.0-beta.5](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.5) * [5.0.0-beta.4](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.4) * [5.0.0-beta.3](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.3) * [5.0.0-beta.2](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.2) * [5.0.0-beta.1](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.1) * [5.0.0-beta.0](https://github.com/dittowords/cli/releases/tag/v5.0.0-beta.0) ## March 28, 2025 ### API * Added [fetch project folders](https://developer.dittowords.com/legacy/api-reference-legacy/project-folders/fetch-project-folders) (`GET /v1/project-folders`) API endpoint for fetching project folders in the user's workspace. * Added [fetch projects by project folder id](https://developer.dittowords.com/legacy/api-reference-legacy/project-folders/fetch-projects-by-project-folder-id) (`GET /v1/project-folders/:folder_id/projects`) API endpoint for fetching projects in the specified folder. ## March 24, 2025 ### API Added the first set of v2 endpoints to the API: * Added [fetch projects](https://developer.dittowords.com/api-reference/projects/get-projects) (`GET /v2/projects`) API endpoint for fetching projects in the user's workspace. * Added [fetch text items](https://developer.dittowords.com/api-reference/text-items/get-text-items) (`GET /v2/textItems`) API endpoint for fetching text items in the user's workspace. * Added [fetch variables](https://developer.dittowords.com/api-reference/variables/get-variables) (`GET /v2/variables`) API endpoint for fetching variables in the user's workspace. * Added [fetch variants](https://developer.dittowords.com/api-reference/variants/get-variants) (`GET /v2/variants`) API endpoint for fetching variants in the user's workspace. ## February 3, 2025 ### API The [fetch component text](https://developer.dittowords.com/legacy/api-reference-legacy/components/fetch-component-text) (`GET /v1/components`) API endpoint now supports the `includeTimeData` query parameter. When `includeTimeData` is set to `true`, the response will include the `createdAt` and `updatedAt` fields for each component. This feature is only available when the `structured` format is used. ## November 11, 2024 ### Ditto React - 1.6.3 * Dependency upgrades See full release notes [here](https://github.com/dittowords/ditto-react/releases/tag/v1.6.3) ## May 21, 2024 ### Ditto CLI - 4.5.2 * Fix to include header distinguishing Ditto CLI requests from direct API requests. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v4.5.2). ## May 7, 2024 ### Ditto CLI - 4.5.1 * Fix issue with error logs not being flushed before exiting the CLI See full release notes [here](https://github.com/dittowords/cli/releases/tag/v4.5.1). ## May 3, 2024 ### Ditto Github Action - 0.3.0 * add optional name input parameter pr-title-prefix to distinguish prs with multi config setups See full release notes [here](https://github.com/dittowords/cli/releases/tag/v0.3.0). ## May 2, 2024 ### API / Web App Pluralization is now supported for all JSON formats when importing components via the Ditto web app or the [Fetch component text](https://developer.dittowords.com/legacy/api-reference-legacy/components/fetch-component-text) API endpoint. Example import data: ```json theme={null} { "fruit": "You have fruit in your cart.", "fruit_one": "You have one fruit in your cart.", "fruit_other": "You have some fruit in your cart." } ``` ## April 16, 2024 ### CLI v4.5.0 released to add support for a new option which disables the generation of `index.js` and `index.d.ts` files when working with JSON formats. See the release notes [here](https://github.com/dittowords/cli/releases/tag/v4.5.0). ## March 22, 2024 ### CLI v4.4.1 released to fix periodic hanging from attempting to exit the CLI process using Ctrl + C. See full release notes [here](https://github.com/dittowords/cli/releases/tag/v4.4.1). ## March 15, 2024 ### Documentation * Small fixes and wording clarifications on the [SDKs](/legacy/legacy-tools) page ## March 13, 2024 ### Ditto React - 1.6.1 * Fix crash when rich text is enabled See release notes: [https://github.com/dittowords/ditto-react/releases/tag/v1.6.1](https://github.com/dittowords/ditto-react/releases/tag/v1.6.1) ## February 29, 2024 ### Ditto CLI - 4.4.0 * Add `pull` --sample-data flag for variants See release notes: [https://github.com/dittowords/cli/releases/tag/v4.4.0](https://github.com/dittowords/cli/releases/tag/v4.4.0) ## February 27, 2024 ### Ditto API * `/v1/variants` now supports the `showSampleData` query parameter * Fix `/v1/variants` for `projectIds` query parameter ## February 19, 2024 ### Ditto CLI - 4.3.0 * `nested` format is now supported See release notes: [https://github.com/dittowords/cli/releases/tag/v4.3.0](https://github.com/dittowords/cli/releases/tag/v4.3.0) ### Ditto CLI - 4.2.3 * Upgrades to several dependencies See release notes: [https://github.com/dittowords/cli/releases/tag/v4.2.3](https://github.com/dittowords/cli/releases/tag/v4.2.3) ## January 22, 2024 ### Ditto CLI - 4.2.2 * Stop component folders with emojis from creating a leading separator See release notes: [https://github.com/dittowords/cli/releases/tag/v4.2.2](https://github.com/dittowords/cli/releases/tag/v4.2.2) ## December 12, 2023 ### Ditto API The `/components/file` [endpoint](https://developer.dittowords.com/legacy/api-reference-legacy/components/import-components-via-a-file) now supports a `folderId` query parameter. You can supply the Developer ID of one of your component folders, and all the components will be imported to that folder. ## December 7, 2023 ### Ditto React v1.3.2 released to fix a bug that incorrectly tracked the "Searching" location. ## November 28, 2023 ### Ditto CLI - 4.2.1 * Only run init() on commands that need it in [https://github.com/dittowords/cli/pull/89](https://github.com/dittowords/cli/pull/89) This change prevents the `ditto` directory from being created on commands that don't need it (e.g. `generate-suggestions`) See release notes: [https://github.com/dittowords/cli/releases/tag/v4.2.1](https://github.com/dittowords/cli/releases/tag/v4.2.1) ## November 16, 2023 ### Ditto CLI - 4.2.0 Add `--sample-data` flag to the `components-folders` command in [https://github.com/dittowords/cli/pull/88](https://github.com/dittowords/cli/pull/88) See release notes: [https://github.com/dittowords/cli/releases/tag/v4.2.0](https://github.com/dittowords/cli/releases/tag/v4.2.0) ## November 2, 2023 ### Ditto React Ditto text items with rich text can now be rendered natively by passing the `richText` property on either `Ditto` or `DittoText`. This expands rich text support from v1.5.0 which introduced support for rich text on components. Example: ```jsx theme={null} ``` ## October 30, 2023 ### Ditto CLI — 4.1.0 This release includes features designed to tighten integration for a variety of environments, including iOS, TypeScript, and more. See the release notes [here](https://github.com/dittowords/cli/releases/tag/v4.1.0). ### New example apps We've released new versions of our example applications, which have been updated to include the latest in recommended practices for integrating Ditto into development. See the full list [here](/example-apps#repositories). ## September 29, 2023 [Ditto Webhooks](/additional-tools/webhooks) have been released! ## August 29, 2023 ### Ditto React * Ditto components with rich text can now be rendered natively by passing the richText property on either Ditto or DittoComponent. * Example: `` ## August 18, 2023 ### Ditto API * Endpoints for fetching project text now support an `exclude_components` query parameter. * When `exclude_components=true`, only text items not associated with Ditto components will be returned from a given endpoint. ### Ditto CLI * We've released a new major version (v4) of the CLI. See the release notes [here](https://github.com/dittowords/cli/releases/tag/v4.0.0). ## August 15, 2023 ### Ditto API The behavior of variables when exporting to mobile formats (`.strings`, `.stringsdict`, and `.xml`) has changed. Variables will now maintain a consistent ordering across base text, variant text, and plural forms; they're assigned an index according to the order of usage (starting with base text, and then variants), and that index is maintained through variant exports. For example, consider the following text: ```json theme={null} // Base Text {{username}}, your role is {{role}}. // Variant Text Tú papel es {{role}}, {{username}}. Previously, exports for the base text and variant text would cause format specifier indices to be reversed for the variables: /* base-example.strings */ "text" = "%1$@, your role is %2$@."; /* spanish-example.strings */ "text" = "Tú papel es %1$@, %2$@."; To facilitate ease of integration into development, exports for variant text will now ensure that the format specifier indices match those in the base text: /* base-example.strings */ "text" = "%1$@, your role is %2$@."; /* spanish-example.strings */ "text" = "Tú papel es %2$@, %1$@."; ``` ## August 7, 2023 ### Ditto API * Support has been added for an `exclude_components` query parameter to the endpoint. * When set to `true`, only text items not associated with components will be returned from the endpoint. ### Ditto CLI * Small bug fix to ensure files aren't written to disk for empty API responses. * For more information, please refer to the release [here](https://github.com/dittowords/cli/releases/tag/v3.10.1). ## July 26, 2023 ### Ditto API * When exporting data for variants, variant metadata (name and description) is now included. * In the JSON formats, two new meta keys are added at the top level: `variant_name` and `variant-description` * In the Android `.xml`, iOS `.strings`, and iOS `.stringsdict` formats, a comment is added for each field at the top of the file ## July 17, 2023 ### Ditto API * The `flat` format now supports rich text. ## June 27, 2023 ### Ditto CLI * Added support for specifying a list of formats in the format configuration property. * For more information, please refer to the release [here](https://github.com/dittowords/cli/releases). ## June 26, 2023 ### Ditto React * `v1.4.1` fixes a bug introduced in `v1.4.0` which caused variables to ignore passed values. ## June 14, 2023 ### Ditto CLI * Added the `import-components` command which enables component imports via a file through the CLI. For information about the command please refer to the [documentation](https://github.com/dittowords/cli#import-components) in the README. ## June 12, 2023 ### Ditto API * We've added the new `POST /components/file` endpoint to enable uploading components through the API. ## June 8, 2023 ### VSCode Extension * Add the ability to only match on components from a component folder. Must be used with `v3.8.0` of the Ditto CLI. ### Ditto CLI * Add `component-folders` command which lists component folders in your workspace. See the release [here](https://github.com/dittowords/cli/releases/tag/v3.8.0). ## June 5, 2023 ### Ditto CLI * Add the `-cf` (`--component-folder`) flag for `generate-suggestions`. See the release [here](https://github.com/dittowords/cli/releases/tag/v3.7.0). ## June 2, 2023 ### Ditto CLI * Performance improvements. See the release [here](https://github.com/dittowords/cli/releases/tag/v3.6.1). ## May 30, 2023 ### Ditto CLI * We've added support for specifying the new `ios-stringsdict` format in the CLI configuration. See the release [here](https://github.com/dittowords/cli/releases/tag/v3.6.0). ### Ditto API We clarified a small point of confusion with the documentation around the request payload expected for the endpoint. The JSON payload should include a top-level data property: ```json theme={null} { "data": { "[API ID]": { "text": "[TEXT OF TEXT ITEM (CAN INCLUDE VARIABLES)]", "notes": "[NOTES OF TEXT ITEM]", // optional "tags": [ ... ], // optional: array of strings "plurals": [ // optional: array of plural objects { "text": "[TEXT OF PLURAL FORM]", "form": "[one|two|zero|few|many|other]" } ] } } } ``` ## May 12, 2023 ### Ditto API We've added support for the iOS `.stringsdict` format to our API. Read more [here](https://developer.dittowords.com/legacy/api-reference-legacy/text-formats#ios-.stringsdict-format-ios-stringsdict). ## May 3, 2023 ### Ditto CLI * Adds ICU support * Fixes a bug where pulling would fail on new initializations * [Full Changelog](https://github.com/dittowords/cli/compare/v3.3.0...v3.5.0) ## May 1, 2023 ### VS Code Extension We've create a VS Code extension! Check it out [here](https://marketplace.visualstudio.com/items?itemName=dittotech.vscode-ditto). ## April 28, 2023 ### Ditto API We've added support for an ICU JSON export format to our API. Read more [here](https://developer.dittowords.com/api-reference/text-formats#icu-json-format-icu). ## April 26, 2023 ### Ditto CLI `v3.4.0` of the CLI has been published, which adds a `-v`, `--version` flag to output what version of the CLI is being run. ## March 23, 2023 ### Ditto React We've published `v1.4.0`, which includes explicit support for map and list variables, in addition to miscellaneous cleanup. Read about the release [here](https://github.com/dittowords/ditto-react/releases/tag/v1.4.0). ## March 22, 2023 ### Ditto API, Ditto CLI, Ditto React We've added a `__type` property to variable output in our structured export format; this enables simple discrimination between different types of variables: ```json theme={null} { "some-variable-1": { "example": "Hello world!", "__type": "string" }, "some-variable-2": { "example": 100, "__type": "number" }, "some-variable-3": { "text:" "Foo", "url": "https://bar.com", "__type": "hyperlink" } } ``` ## March 21, 2023 ### Ditto API We've added support for a `nested` JSON export format to our API. Read more [here](https://developer.dittowords.com/api-reference/text-formats#nested-json-format-nested). ## March 9, 2023 ### Ditto CLI We've published `v3.0.0` of the CLI, which focuses around a number breaking changes intended to better align default CLI behavior with Ditto best practices. Read the release notes [here](https://github.com/dittowords/cli/releases/tag/v3.0.0). ## February 15, 2023 ### Ditto API We've released two bug fixes specific to the Android XML export format: * index values for format specifiers will now be relative to a text item's base value AND its plurals, instead of resetting for each plural form ([issue](https://github.com/dittowords/cli/issues/48)) * extra whitespace is no longer added between text and variables in the final output ([issue](https://github.com/dittowords/cli/issues/49)) ## February 14, 2023 ### Ditto API We've released a new version of our API that includes some breaking changes. The old API endpoints are deprecated, and existing users should migrate to the new endpoints at their earliest convenience. ## February 6, 2023 ### Ditto Web App * Removed the deprecated the full format from the export modals on the project page and in the component library * Set the flat format to be the default export format in the web app ## February 3, 2023 ### Ditto API Minor documentation update to add and emphasize a deprecation notice for the full format. ## February 1, 2023 ### Ditto React — 1.3.0 Added support for using variables in conjunction with the flat format. See the release [here](https://github.com/dittowords/ditto-react/releases/tag/v1.3.0). ## January 25, 2023 We added support to the web app for importing components from two new file formats: * Android localization files (.xml) * iOS localization files (.strings) Read more [here](https://dittov3.notion.site/Importing-JSON-iOS-Android-Files-256cc8865c7a80fe94cdf135631bf8c6). ## January 19, 2023 ### Ditto React — 1.2.1 * Updated README to clarify that the flat format does not currently support variables. * Fixed a bug preventing falsy values (namely 0 and "") from being properly interpolated. See the release [here](https://github.com/dittowords/ditto-react/releases/tag/v1.2.1). ## December 15, 2022 ### Ditto CLI Added rich text support. See more info [here](https://github.com/dittowords/cli#richtext) ## December 13, 2022 ### Ditto API Added the `includeRichText` flag to the following endpoints (format must be full or structured): * `/projects` * `/projects/:id` * `/projects/:id/components` * `/components` * `/component-folders/:api*id/components` ## November 30, 2022 ### Ditto CLI Added the `status` filter as a configuration variable. To filter the fetched text by its status, you can now use the `status` parameter in your `config.yml`. ## November 29, 2022 ### Ditto CLI Added `android` and `ios-strings` as available formats. To use these formats, specify them under the format paramter in your `config.yml`. ## November 10, 2022 ### Ditto API Fixed a typo in an example in the documentation containing an incorrect usage of the curl command. ### Ditto CLI Added information to the README about two supported environment variables: * `DITTO_CONFIG_FILE:` used to specify an alternate file location for the CLI to read authentication information from. The default path is `$HOME/.config/ditto`. * `DITTO_TEXT_DIR`: used to specify an alternate folder location for the CLI to write its output files to. The default path is `./ditto`. ## Oct 11, 2022 ### Ditto API * Added new endpoint: Fetch component folders * Added new endpoint: Fetch components by component folder * Added sub-headings to divide the endpoints by the entity they're associated with, and implemented minor miscellaneous cleanup across the endpoint documentation ## Sep 23, 2022 ### Ditto API Text items with plural values will have those plural values included in flat exports as individual key/value pairs; pluralized keys are structured like `[TEXT ID]_[PLURAL FORM]`. See the [flat format documentation](/string-formats/json#flat-json) for more details. Fixed a bug where the status field failed to properly filter returned data when specified in conjunction with the `android` or `ios-strings` formats. ## Sep 19, 2022 ### Ditto React — 1.2.0 * We exported two hooks that can be used for accessing synced data: `useDittoSingleText` and `useDittoComponent`. These hooks are experimental and will likely undergo API changes in the future. * See the release [here](https://github.com/dittowords/ditto-react/releases/tag/v1.2.0). ### Ditto API * Added the `status` field to data returned from the `/components` and `/projects/:id/components` endpoints. ## Jun 30, 2022 ### Ditto API * Added support for including variables in payloads sent to the `PUT /components` endpoint. * Fixed an issue with Android & iOS export formats not escaping special characters: `\n`, `\t`, `@`, `?`, `'`, and `"` characters are now properly escaped. * Added missing `xliff` namespace to exported Android `.xml` files. \= Fixed a bug with Android `.xml` exports that occurred with text items with API IDs that include the `-` character. Now, all `-` characters are replaced with `\_` characters in the name property of `` and `` tags, and a `ditto_api_id` property has been added to string and plural tags to serve as a reference to the original API ID. ### Ditto CLI — 2.5.1 * Fixed a bug when using the `project add` / `project remove` commands and selecting the component library caused incorrect modifications to be made to the CLI's configuration file. * See the release [here](https://github.com/dittowords/cli/releases/tag/v2.5.1). ### Ditto Example Projects * Added new iOS Example Project showcasing how to use exported .strings files. * Added new Android Example Project showcasing how to use exported .xml string files. See [Example Projects](/example-apps) for more details. ## Jun 13, 2022 ### Ditto GitHub Action — 0.2.0 Added an optional `ditto-dir` input parameter for projects that do not have the ditto directory in the root of the repository ## June 7, 2022 ### Ditto API Added support for upserting plurals via the `PUT /components`. See our [API documentation](/api-reference/authentication) for more details. ## June 1, 2022 ### Ditto API * Added early support for exporting text data to Android and iOS Strings formats. All endpoints that support a `format` query parameter can now accept values of `android` or `ios-strings`. * Added `GET /variables` endpoint that returns the variables in the workspace of the requesting user. * Added the ability to filter by text item status by passing a status query parameter in API requests. Supported by the following endpoints: * `GET /projects` * `GET /projects/:id` * `GET /projects/:id/components` * `GET /components` See our [API documentation](/api-reference/authentication) for more details. ## May 23, 2022 ### Ditto CLI — 2.5.0 * Added meta option flag in support of the Ditto GitHub Action release. See the release [here](https://github.com/dittowords/cli/releases/tag/v2.5.0). ### Ditto GitHub Action — 0.1.0 * v0.1.0 released! Check it out [here](https://github.com/dittowords/ditto-github-action). ## May 16, 2022 ### Ditto CLI — 2.4.0 * Update some user-facing CLI messages to accurately reflect our new subdomain: app.dittowords.com (instead of the old beta.dittowords.com). See the release [here](https://github.com/dittowords/cli/releases/tag/v2.4.0). ## May 10, 2022 ### Ditto CLI — 2.3.0 * Allows users to override the output directory via a new environment variable `DITTO_TEXT_DIR` * When no projects are found in the `config.yml` file an additional help message displays about verifying that the projects have developer mode enabled * See the release [here](https://github.com/dittowords/cli/releases/tag/v2.3.0). ## February 28, 2022 ### Ditto API Added better error handling for the niche edge case where an API request is made using the token of a user that has been removed from their workspace. ## January 20, 2022 ### Ditto React — 1.1.1 * Our React SDK now supports variable interpolation by passing in values through the variables prop. * Our React SDK now supports displaying plural forms through the count prop. For more information about using variable interpolations and pluralization through our React SDK, check out our [SDK documentation](/legacy/legacy-tools). ### Ditto CLI — 2.2.0 * The CLI now supports reading API keys from an environment variable (`DITTO_API_KEY`). * See the release [here](https://github.com/dittowords/cli/releases/tag/v2.2.0). ## January 11, 2022 ### Ditto CLI We deployed a bugfix release to fix issues introduced in the latest minor version of the package: * the pull command no longer crashes when projects other than the component library are specified in `config.yml` * resolves a casing issue in the project add command that was causing it to crash See the release [here](https://github.com/dittowords/cli/releases/v2.1.2). ## January 6, 2022 ### Ditto CLI We've added a new supported property to `ditto/config.yml` to allow explicitly specifying that the component library should be fetched and included in the CLI's output: `components: true` This is now the preferred alternative to the old way of specifying the component library: ```yml theme={null} projects: name: Ditto Component Library id: ditto_component_library ``` Our goal with this change is to make the CLI easier to use and understand by aligning it with the way things work in the Ditto web app, where the component library is fundamentally different from the projects in a workspace. See the release [here](https://github.com/dittowords/cli/releases/tag/v2.1.1). ## December 8, 2021 ### Ditto API * `GET /projects/:id/components` can now be used to fetch the components used in a project * Return format matches the `GET /components` endpoint * Supports `format` and `variant` parameters (the same as the `/projects`, `/project/:id`, and `/components` endpoints) ## November 29, 2021 ### Ditto API * `GET /components` can now be used to fetch the components in a workspace * Supports format and variant parameters (the same as the `/projects` and `/project/:id` endpoints) * `GET /projects/ditto_component_library` has been deprecated; the endpoint is still available, but references to it have been removed from the documentation. * `PUT /components` can now be used to update the text of components in a workspace * Currently only supports updating variant text via the variant query parameter (an error will be thrown if not specified) * Currently only available to teams on Team or Enterprise plans * Data is specified on data key in request body and must be in one of our [three supported import formats](https://dittov3.notion.site/Importing-JSON-iOS-Android-Files-256cc8865c7a80fe94cdf135631bf8c6) ## November 11, 2021 ### Ditto CLI — 2.0.0 * Output files are now written to disk in several different ways according to how the CLI is configured; most notably, if the variants option is set and a format of structured or flat is specified, output files will be written on a per-project per-variant basis * Data for `structured` and `flat` formats is saved without arbitrary top-level nesting * Files are only written for variants that contain values for the configured projects (no more empty files) For a more in-depth description (with examples) of these changes and others in the release, see the full release notes [here](https://github.com/dittowords/cli/releases/tag/v2.0.0). ### Ditto React — 1.0.0 * `ditto-react` now only works with CLI version `2.x.x` — leaning into new CLI improvements allowed us to drastically reduce many areas of complex logical overhead * We made some stabilization improvements to the way that missing/invalid `frameId`, `blockId`, `textId`, or `componentId` values are handled * We implemented significant refactoring in several areas of the project For a more in-depth description of these changes, see the full release notes [here](https://github.com/dittowords/ditto-react/releases/tag/v1.0.0) ### Ditto Demo * We added a [new branch](https://github.com/dittowords/ditto-demo/tree/react-i18next) that showcases how Ditto can be integrated into a project with react-i18next * We updated dependencies to the latest versions of the Ditto CLI (v2.0.0) and Ditto React (v1.0.0) ## October 22, 2021 ### Ditto API — 2.4.1 * We fixed a bug with the API (and in-app exports) that included hidden text in `flat` and `structured` JSON exports. Now, hidden text will correctly not be included in these export formats! ### Ditto Demo * We included the files to import into the component library separately from where they'd be synced via the CLI * We included more instructions on linking the demo app with its corresponding design file ## October 7, 2021 ### General * Refactored API documentation to be easier to navigate and communicate critical concepts more clearly ### Ditto CLI — 1.1.1 * When the `pull` command is executed, all `.json` and `.js` files are now removed from the `ditto` folder prior to new data being written \= Support added for new options in `ditto/config.yml`: * `variants`: `true` or `false` * If set to true, data will be fetched for both base and variant text and stored in separate JSON files * `format`: `flat` or `structured` - If specified, data will be fetched and stored in the specified format \= Refactored `README` * `GET /variants` will now return names and API IDs for variants in the workspace associated with the requesting user. ### Ditto React — 0.0.5 * Support added for `flat` and structured formats * Support added for passing a `variant` value to `DittoProvider` * Bug fixes around support for integrating with workspace component libraries (as opposed to individual projects) * Refactored `README` ### Ditto Demo * Refactored `ditto-sdk` usage to integrate with a component library instead of an individual project * Added a language picker to showcase `ditto-react` and `ditto-cli` support for variants * Refactored `README`, including adding instructions for connecting the repo to a component library ## September 27, 2021 ### General Fixed incorrect example of `/projects` usage in API Reference. The `projectIds` query parameter should be specified as follows: * Correct: `?projectIds[]=&projectIds[]=` * Incorrect: `?projectIds[]=,` ### API — 2.4.0 `GET /variants` will now return names and API IDs for variants in the workspace associated with the requesting user. ## September 8, 2021 ### API — 2.3.0 * `GET /projects` and `GET /projects/:id` now accept an optional query parameter of `variant`; when provided, the data exported will be specific to the variant with an API ID that matches the parameter's value. For `default` and `structured` formats, the variants key will not be included when the variant parameter is provided. Example: `/projects/?variant=xxx-xxx-xxx` * `GET /projects/:id` will not include top-level projects and project\_xyz keys Example request: `GET /projects/123` Example response before change: ```json theme={null} { projects: { project_123: { text_item: "Hello World", ... }}} ``` Example response after change: ```json theme={null} { text_item: "Hello World", ... } ``` ## August 11, 2021 ### API — 2.2.0 `GET /projects` and `GET /projects/:id` now accepts an optional query parameter of `format`, which can be either `flat` or `structured`. If no format is provided, the `full` JSON of the project will be returned with blocks and frames. Example: `/projects/?format=flat` ## July 28, 2021 With the recent release of [Variants for Components](https://dittov3.notion.site/Variants-for-Components-256cc8865c7a80bb8c44da73209fb0cc), we've included them in both exports from our web-app and the JSON response from the API. ### API — 2.1.0 * `GET /projects/:id` will now return component variants. This applies both for when fetching projects or when fetching the component library (i.e. id = ditto\_component\_library) * `GET /projects/:id` will now match the project JSON export from the web-app to include component information (`is_comp` and `component_api_id`) in the text object ## June 21, 2021 We've updated our React library and our demo project to work with the multi-project JSON structure that the CLI now saves in `text.json`. ### Ditto React — 0.0.4 TypeScript bindings are now exported 🎉 A new `projectId` prop can be passed to `DittoProvider` or `Ditto` * If passed to `DittoProvider`, all descendant `Ditto` components will default to pulling text from the associated project * If passed to `Ditto`, the component will pull text from the associated project * If passed to both `DittoProvider` and `Ditto`, the value passed to `Ditto` will take precedence * Breaking change: If a `Ditto` component is rendered that is not provided a `projectId` via direct prop or `DittoContext` ancestor, an error will be thrown See more details in the [updated documentation](https://www.npmjs.com/package/ditto-react). ### Ditto Demo * Upgraded `ditto-react` to `v0.0.4` * Updated source to work with new `ditto-react` version by passing `projectId` props to all instances of `DittoContext` ## June 7, 2021 We've updated both our CLI and API to handle multiple projects. Heads up! This results in some breaking changes to the CLI in order for it to list projects, and will change the format of the `text.json` fetched by the CLI. Please upgrade your Ditto CLI via either `npm` or `yarn`. ### API — 2.0.0 * `GET /project-names` is the new name of the previous `GET /projects` endpoint. Just as before, this will return the names and IDs of projects in your Ditto workspace with Developer Mode turned on * `GET /projects/:id` will now return the structured JSON in a format compatible with multiple projects * `GET /projects` is a new endpoint that will return the content of multiple projects (specified via the query parameter projectIds) as a single JSON object ### CLI — 1.0.0 * We've updated the CLI to handle syncing multiple projects in a single directory! This means several things: `/ditto/config.yml` now supports multiple projects. `ditto-cli` pull will pull all projects in the current directory's /`ditto/config.yml` * We've added two new commands to add and remove projects from `/ditto/config.yml` * `ditto-cli project add` * `ditto-cli project remove` * The regular `ditto-cli project` command will now function as `ditto-cli project add` ## April 1, 2021 ### API — 1.1.0 * `GET /projects` now includes the Component Library if Developer Mode is enabled for the Component Library * `GET /projects/:id` will return the Component Library as a structured JSON if the ID provided is `ditto_component_library` ## February 25, 2021 ### API — 1.0.0 * `GET /projects` endpoint for fetching Ditto projects from workspace * `GET /projects/:id` endpoint for fetching content in Ditto project as structured JSON # Feedback & Support Source: https://developer.dittowords.com/feedback-support/feedback-support Where to report issues and ask questions about the Ditto CLI, API, MCP server, React SDK, and GitHub Action. Have questions, feedback, or requests for our developer tools? Please **open an issue** in the respective repo on Github and an engineer from our team will get back to you with a resolution as quickly as possible. ### API or CLI Support * Open an issue at [dittowords/cli](https://github.com/dittowords/cli/issues) ### React SDK Support * Open an issue at [dittowords/ditto-react](https://github.com/dittowords/ditto-react/issues) ### Github Action Support * Open an issue at [dittowords/ditto-github-action](https://github.com/dittowords/ditto-github-action/issues) ### Contact Support For any other questions or urgent issues, please contact our customer success team via [support@dittowords.com](mailto:support@dittowords.com). # Scan your repo Source: https://developer.dittowords.com/get-started/scan-your-repo Bring in your product's existing strings with one command. Once they're in, you can search, audit, and reuse the text from your live product. Let's bring in your product copy. One command scans your codebase and identifies your current product strings. Run it in your product's repo to import them; once they're in, you'll be able to search, audit, and reuse the text from your live product. ## Before you start > [!NOTE] > > You need Node.js 20 or newer (`node --version`) and a Ditto account ([app.dittowords.com](https://app.dittowords.com)). Scan the folder that holds your product's user-facing code, usually `./src`, `./app`, or the repo root on a small project. ## Run the scan > [tab] Claude Code > > Paste this prompt into Claude Code from the root of your repo. > > ```text theme={null} > You're helping me set up Ditto on this codebase for the first time. Ditto manages the product copy, the words people read in this app. > > How to work with me: > - Before you ask me to run, paste, click, or install anything, say in one plain sentence what it does and why, especially anything that opens a window or changes something on my machine. > - One thing at a time. Ask, stop, wait for me. > - Do the mechanical work yourself. Don't make me find links, pick folders, or assemble commands. > > SETUP, quietly, without stopping to ask me: > 1. Check what OS I'm on and use it for every command you give me later. > 2. The Ditto CLI needs Node 20 or newer. If mine is missing or too old, say what Node is and why the CLI needs it, walk me through installing it for my OS, then re-check. > 3. Work out what we'd scan: run `pwd`, list the directory, run `git remote -v`, and read package.json. Pick the folder holding the app's user-facing code, usually ./src, ./app, or the repo root on a small project. Skip vendored and generated code. > > CHECK WITH ME, in a message with nothing else in it and no command yet: > - Say what this app looks like ("this looks like your marketing site") and which part you'd scan, in plain words, with the folder name as supporting detail. > - Ask whether that's the right product and the right part of it. If not, ask where the right thing lives and redo step 3. > > RUN THE SCAN. Run both commands yourself; my parts are confirming the folder and approving the sign-in. > 1. Check whether DITTO_TOKEN is set in my environment. It's an API key and it outranks a browser sign-in, so leaving it set would scan into whichever workspace that key belongs to. If it's set, tell me and ask which to use. If I pick my own account, unset it for these commands only. > 2. Tell me a browser tab is about to open for me to sign in to Ditto, then run: > npx -y @dittowords/cli@latest login > 3. Once I've approved it, scan the folder I confirmed: > npx -y @dittowords/cli@latest scan > 4. The scan is done as soon as it prints "Scan initiated!" and an app.dittowords.com/scan/... link. Stop there, give me the link, and tell me the rest happens on that page. > > If the scan exceeds my plan's limit, that's not a crash. Pick a smaller subfolder and try again, and tell me what you chose. > ``` > [tab] Cursor > > Paste this prompt into Cursor from the root of your repo. > > ```text theme={null} > You're helping me set up Ditto on this codebase for the first time. Ditto manages the product copy, the words people read in this app. > > How to work with me: > - Before you ask me to run, paste, click, or install anything, say in one plain sentence what it does and why, especially anything that opens a window or changes something on my machine. > - One thing at a time. Ask, stop, wait for me. > - Do the mechanical work yourself. Don't make me find links, pick folders, or assemble commands. > > SETUP, quietly, without stopping to ask me: > 1. Check what OS I'm on and use it for every command you give me later. > 2. The Ditto CLI needs Node 20 or newer. If mine is missing or too old, say what Node is and why the CLI needs it, walk me through installing it for my OS, then re-check. > 3. Work out what we'd scan: run `pwd`, list the directory, run `git remote -v`, and read package.json. Pick the folder holding the app's user-facing code, usually ./src, ./app, or the repo root on a small project. Skip vendored and generated code. > > CHECK WITH ME, in a message with nothing else in it and no command yet: > - Say what this app looks like ("this looks like your marketing site") and which part you'd scan, in plain words, with the folder name as supporting detail. > - Ask whether that's the right product and the right part of it. If not, ask where the right thing lives and redo step 3. > > RUN THE SCAN. Run both commands yourself; my parts are confirming the folder and approving the sign-in. > 1. Check whether DITTO_TOKEN is set in my environment. It's an API key and it outranks a browser sign-in, so leaving it set would scan into whichever workspace that key belongs to. If it's set, tell me and ask which to use. If I pick my own account, unset it for these commands only. > 2. Tell me a browser tab is about to open for me to sign in to Ditto, then run: > npx -y @dittowords/cli@latest login > 3. Once I've approved it, scan the folder I confirmed: > npx -y @dittowords/cli@latest scan > 4. The scan is done as soon as it prints "Scan initiated!" and an app.dittowords.com/scan/... link. Stop there, give me the link, and tell me the rest happens on that page. > > If the scan exceeds my plan's limit, that's not a crash. Pick a smaller subfolder and try again, and tell me what you chose. > ``` > [tab] Codex > > Paste this prompt into Codex from the root of your repo. > > ```text theme={null} > You're helping me set up Ditto on this codebase for the first time. Ditto manages the product copy, the words people read in this app. > > How to work with me: > - Before you ask me to run, paste, click, or install anything, say in one plain sentence what it does and why, especially anything that opens a window or changes something on my machine. > - One thing at a time. Ask, stop, wait for me. > - Do the mechanical work yourself. Don't make me find links, pick folders, or assemble commands. > > SETUP, quietly, without stopping to ask me: > 1. Check what OS I'm on and use it for every command you give me later. > 2. The Ditto CLI needs Node 20 or newer. If mine is missing or too old, say what Node is and why the CLI needs it, walk me through installing it for my OS, then re-check. > 3. Work out what we'd scan: run `pwd`, list the directory, run `git remote -v`, and read package.json. Pick the folder holding the app's user-facing code, usually ./src, ./app, or the repo root on a small project. Skip vendored and generated code. > > CHECK WITH ME, in a message with nothing else in it and no command yet: > - Say what this app looks like ("this looks like your marketing site") and which part you'd scan, in plain words, with the folder name as supporting detail. > - Ask whether that's the right product and the right part of it. If not, ask where the right thing lives and redo step 3. > > RUN THE SCAN. Run both commands yourself; my parts are confirming the folder and approving the sign-in. > 1. Check whether DITTO_TOKEN is set in my environment. It's an API key and it outranks a browser sign-in, so leaving it set would scan into whichever workspace that key belongs to. If it's set, tell me and ask which to use. If I pick my own account, unset it for these commands only. > 2. Tell me a browser tab is about to open for me to sign in to Ditto, then run: > npx -y @dittowords/cli@latest login > 3. Once I've approved it, scan the folder I confirmed: > npx -y @dittowords/cli@latest scan > 4. The scan is done as soon as it prints "Scan initiated!" and an app.dittowords.com/scan/... link. Stop there, give me the link, and tell me the rest happens on that page. > > If the scan exceeds my plan's limit, that's not a crash. Pick a smaller subfolder and try again, and tell me what you chose. > ``` > [tab] Terminal > > Run this in your terminal from your product's repo. > > ```bash theme={null} > npx -y @dittowords/cli@latest login > npx -y @dittowords/cli@latest scan ./src > ``` > > `login` opens a browser tab to sign in to Ditto. `scan` prints its progress and ends with a link: > > ```text theme={null} > Scan initiated! Visit https://app.dittowords.com/scan/ to view progress and see results. > ``` > [!NOTE] > > This only needs read access to your codebase. It won't edit or write to your repo. You'll be able to review what's scanned before bringing it into Ditto. Open the link. ## Review and refine Ditto classifies the strings and shows **Your codebase scan has finished!** with how many strings and locales it found. **Review scan** takes you through setting up your content system: 1. **Set up your content system in Ditto.** Ditto shows the unique and repeated strings it kept, the developer IDs it found, and your product name and blurb. Mark any strings to leave out or handle differently. 2. **Review proposed content system.** The tags Ditto will create, how it will give components names, how it will organize them, the variants and locales it found, and how it will name developer IDs, each with examples. Describe what you'd like to adjust, in plain language, until it looks right. 3. **Create.** Ditto creates the library components, variables, and one variant per locale. Home shows **Your product copy is now in Ditto!** with what was imported, and you can search and reuse what's live today across your product. ## Get a first style guide We can infer a starter style guide from your strings, or you can import your own. After a scan finishes, Ditto drafts a style guide with three sections: **Voice & Tone** and **Style & Mechanics** (rules) and a **Word List** (terminology). Each rule has a name, a description, and up to three from → to examples taken from your copy. Definitions: [Style guides and rules](/concepts#style-guides-and-rules). 1. In Ditto, open **Style guides** in the left sidebar. The inferred guide appears as a draft named after your product (or "Content Style Guide" if the name could not be inferred). 2. Open it. Edit, delete, or add rules, and move rules between sections. 3. Enable it. Turn on **Enabled by default** so it applies to every project, or enable it per project from the project's **…** menu under **Style guides**. Agents receive the rules through `get_styleguide_rules` as soon as the guide is enabled; the [PR review bot](/additional-tools/github-pr-review-bot) checks pull requests against them. To add, refine, or retire rules from your editor, see [Agent instructions](/guides/agent-instructions#turn-a-code-review-comment-into-a-rule). ## Frequently asked questions **What if my repo already has translation files?** Scan reads `strings.xml`, `.strings`, `.stringsdict`, `.xcstrings`, and JSON catalogs, and each locale becomes a variant in Ditto with that locale's text. **Is there a limit?** Trial workspaces can classify up to 100,000 strings in total across all of their scans. Paid plans have no cap. If a scan would exceed the remaining budget, the CLI stops before uploading and suggests subdirectories that fit. # Agent instructions Source: https://developer.dittowords.com/guides/agent-instructions The CLAUDE.md / AGENTS.md block that makes a coding agent check Ditto before writing copy, plus example prompts and the MCP tools each one triggers. The Ditto MCP server exposes tools; your agent still needs to know when to use them. Give it standing instructions in a project-level file such as `CLAUDE.md` (Claude Code), `AGENTS.md` (Codex and others), or `.cursor/rules` (Cursor). Everything on this page assumes the server is connected; see [Connect the Ditto MCP](/guides/connect-the-mcp). If you installed the Ditto plugin, the block below is already loaded at the start of every session. Committing it to the repo as well means agents without the plugin behave the same way. ## The block to commit Paste this into `CLAUDE.md` or `AGENTS.md` at the repo root: ```text theme={null} DITTO ACTIVE — user-facing copy goes through Ditto. The Ditto MCP server is available (server name `ditto`; tools such as `get_styleguide_rules`, `search_ditto_text`, and `suggest_edit`). Whenever you write or edit user-facing text — UI labels, buttons, headings, error messages, empty states, notifications, tooltips, emails — do this without being asked: 1. Fetch the team's rules with `get_styleguide_rules` and follow them. 2. Before writing new copy, search existing text with `search_ditto_text` and reuse a match instead of inventing a new string. Consistent > novel. 3. Prefer the reused string verbatim; only write new copy when no match exists, and make it rule-compliant. Check it with `suggest_edit`. Context that changes behavior: - If the repo has `ditto/config.yml` (or `.yaml`) with project ids, Ditto results are scoped to those projects — trust that scoping. - If the repo contains Ditto spec files (`*.ditto.md`), treat them as the source of truth for that surface's copy and keep them in sync. If the Ditto MCP tools are unavailable, say so once, suggest logging in to the `ditto` server (Claude Code: `/mcp` → ditto → Authenticate), and continue without them. Do not repeat the warning every turn. ``` For Cursor, put the same text in a rule file with `alwaysApply: true`. ## Prompts by task Each prompt below works as a one-off message or as a line in your instructions file. The "what happens" notes describe the tool calls a correctly instructed agent makes. ### Generate UI with compliant text ```text theme={null} Use the style guides in Ditto to generate UI with compliant text. ``` What happens: the agent calls `get_styleguide_rules` once before writing any user-facing string, then applies the rules (for example, sentence case for buttons, no exclamation marks in errors) to the text it writes. ### Audit existing strings ```text theme={null} Check my existing user-facing strings and update them so they comply with the style guide rules in Ditto. ``` What happens: the agent fetches your rules, finds user-facing strings in the code, and proposes edits where text does not match. With the plugin, `/ditto-audit src/` does the same and returns a fix-list in the form `file:line: rule "current" → "suggested" (why)`. ### Reuse text when generating UI ```text theme={null} Try to reuse text in Ditto when generating UI. ``` What happens: when the agent needs a button label, heading, or message, it calls `search_ditto_text` first and references the existing text (by developer ID) when a match is found. ### Replace hardcoded strings with Ditto text ```text theme={null} Look at the user-facing strings in this codebase and replace any that already exist in Ditto with references to the Ditto text. ``` What happens: the agent searches Ditto for each string (it can batch several searches in one `search_ditto_text` call) and suggests replacements where equivalent text already exists. ### Review a diff before committing ```text theme={null} Review the user-facing strings in my current diff against Ditto. ``` What happens: with the plugin, `/ditto-review` collects the diff, extracts added or changed user-facing strings, fetches rules once, checks each string for rule violations and reuse opportunities, and ends with `N strings checked, M findings.` ### Turn a code review comment into a rule ```text theme={null} We keep shipping error messages that blame the user. Add a Ditto style guide rule that error messages should be neutral and solution-oriented, with an example like "You entered an invalid email" → "Enter a valid email address." ``` What happens: the agent calls `list_styleguides` to find the target style guide and its section IDs, optionally `get_workspace_tags` to tag the rule, then `create_styleguide_rules`. ### Refine or remove a rule ```text theme={null} Our "date formatting" rule in Ditto only covers full dates. Update it to also specify that relative times like "2 hours ago" are lowercase. ``` ```text theme={null} We no longer use title case anywhere. Delete the "title case headings" rule from our Ditto style guide. ``` What happens: the agent calls `get_styleguide_rules` to find the rule ID, then `update_styleguide_rules` or `delete_styleguide_rules`. ### Add translations for a locale ```text theme={null} Add German text for the strings in the checkout flow. Use the existing "German" variant. ``` What happens: the agent calls `list_variants` to find the variant's developer ID and locale code, `search_ditto_text` to find the checkout flow's text items, then `translate_text_items` (or `translate_library_components` for library components) with the variant developer ID. Translation targets the variant's locale code and applies any German-scoped style guide. See [Set up localization](/guides/set-up-localization). ### Link a Figma file's text to Ditto The prompt for this task is on [Connect Figma files](/guides/connect-figma-files#step-3-instruct-your-agent). It takes your Ditto project URL and Figma file URL and walks the agent through four steps, each confirmed with you before it acts, and the guide shows how to store it as a skill your team runs by name. What happens: the agent reads the file's text nodes through the Figma MCP, matches each one to a text item in the project with `search_ditto_text` (exact matches only) and links them with `link_figma_nodes_to_text_items`, creating text items with `create_text_items` where none exist. It then attaches exact library matches with `link_text_items_to_component` and reports the counts. ### Spin up a project from a prototype ```text theme={null} Create a Ditto project called "Checkout redesign" from the copy in this prototype. One text item per string, developer IDs in the checkout.. scheme, status WIP, and reuse any string that already exists in our library instead of creating a duplicate. ``` What happens: the agent calls `create_ditto_project`, `search_ditto_text` for each string, `create_text_items` for the new ones (with `developerId` and a status from `list_statuses`), and `link_text_items_to_component` for strings that already exist as library components. ### Consolidate near-duplicates into components ```text theme={null} Find every button label in Ditto that means "continue" ("Continue", "Next", "Next step", "Continue →") and consolidate them into one library component using our style guide's preferred wording. ``` What happens: the agent calls `search_ditto_text` (several queries in one call), `get_styleguide_rules` to pick the compliant wording, `publish_library_component` to turn the winning text item into a component (or `create_library_components` if none fits), and `link_text_items_to_component` to attach the others. ### Clean up after a style guide change ```text theme={null} We just added a rule that error messages never start with "Oops". Find every text item that breaks it and fix them. ``` What happens: the agent calls `get_styleguide_rules`, `search_ditto_text` with a status or tag filter to narrow the set, `suggest_edit` on each candidate, and `update_text_items` with the accepted edits. ### Reorganize the component library ```text theme={null} Our library folders are a mess. Propose a folder structure by product area, show it to me, and then move the components. ``` What happens: the agent calls `list_component_folders`, proposes a tree, waits for your approval, then uses `create_component_folders`, `update_component_folders` (to move or rename folders), and `update_library_components` (to change each component's `folderId`). ## Combine instructions For most repos, three lines are enough: ```text theme={null} When working on UI: - Use the style guides in Ditto to ensure all user-facing text follows our content guidelines. - Try to reuse existing text from Ditto instead of writing new copy. - Check any new copy with suggest_edit before finishing. ``` ## Skills from the plugin If you installed the [Ditto plugin](/guides/connect-the-mcp#install-the-ditto-plugin), these slash commands are available in Claude Code and Cursor: | Skill | What it does | Writes to Ditto? | | - | - | - | | `/ditto-review` | Check the current diff's user-facing strings against rules and existing text; returns a fix-list | No | | `/ditto-audit [path]` | Same check across a directory or the whole repo; also flags near-duplicates within the codebase | No | | `/ditto-spec-setup` | One-time setup of Ditto Specs in the repo (installs the Specs CLI, creates config and workspace spec) | No | | `/ditto-spec-component ` | Create or update a component's `*.ditto.md` spec and sync rules from Ditto | No | | `/ditto-spec-audit [component]` | Audit every instance of a specced component against its rules | No | | `/ditto-spec-gaps [component]` | Find copy patterns that should be rules but are not, and create approved ones in Ditto | Yes | The Specs skills need [Ditto Specs](/ditto-specs-cli-reference/overview) set up in the repo. # Connect Figma files Source: https://developer.dittowords.com/guides/connect-figma-files Link your Figma file to a Ditto project from your editor: your agent matches text layers to the strings from your codebase, links them, and organizes them. Use your AI agent to link your Figma file to your Ditto project in minutes. Instead of connecting every text layer by hand, your agent matches, links, and organizes your product copy for you. You can also link layers one at a time in the Ditto plugin or the web app ([Linking](https://help.dittowords.com/en/articles/11502480-linking) in the help center). From your editor, an agent links a whole file in one pass, in the tools developers already use. > [!NOTE] > > If you ran a scan, your product strings are already in your Ditto component library. The agent matches Figma text layers against them and links the matches, so the layer, the component, and the key in your string files share one developer ID. ## Step 1: Connect the Ditto MCP and the Figma MCP Your agent talks to both Ditto and Figma to accomplish tasks. Ditto is used to search, create, and link text, and Figma is used to read the text layers in your file. * Ditto MCP server: [Setup](/guides/connect-the-mcp). * Figma MCP server: [Figma's MCP catalog](https://www.figma.com/mcp-catalog/). ## Step 2: Create the Ditto project from your Figma file A Ditto project is where your team works on the copy for a feature, product area, or Figma file. Creating it from the file connects the two. This step happens in Figma, once, in the Ditto plugin: 1. In Figma, open the Ditto plugin (**Actions › Plugins & widgets › Ditto**) and log in. 2. Paste your Figma file's URL into the plugin. 3. Select **Start a new project**, click **Continue**, and add the frames you want in Ditto. For more detail on this step, see [How to create a Ditto project from Figma](https://help.dittowords.com/en/articles/11557887-how-to-create-a-ditto-project-from-figma) in the help center. This step must be done via the Ditto Figma plugin as the API and MCP cannot connect a file. ## Step 3: Instruct your agent Paste this prompt into your agent with your Ditto project URL and your Figma file URL. The agent tells you what it is about to do at each step and waits for your confirmation before it changes anything. ```text theme={null} You're helping me link Figma text nodes to Ditto text items, then link matched Ditto text items to library components. Follow these steps in order. At each step, tell me what you're about to do and wait for my explicit confirmation before taking the action — never proceed automatically. 1. Identify the project and file, then confirm Look up the Ditto project from the URL below and tell me its name. Wait for my confirmation before doing anything else. Then pull all text nodes from the Figma file. 2. Link Figma text nodes → Ditto project text items For every text node in the Figma file: - Search only within this Ditto project for a text item whose text is an exact match (case-sensitive, trimmed) to the node's text. Search with matchType "anyWord", not "containsPhrase" — containsPhrase has produced false negatives on real matches (especially contractions like "Don't" or "We'll"). Treat anyWord results as candidates only, then verify each one with a strict trimmed, case-sensitive comparison before calling it a match — never conclude "no match" from an empty containsPhrase result. - If a match exists, link the node to that existing item. - If no match exists, create a new text item in the project with the node's text, then link the node to it. - If multiple Figma nodes have identical text, link each node to the same Ditto text item — don't create duplicate items for repeated text. - Collect the matches first, then link them in batches covering many text items per call, not one call per text item. 3. Link Ditto project text items → library components Once step 2 is fully done, go through every text item in the project (existing and newly created) and check it against text items in my Ditto component library: - Search the same way as step 2 — matchType "anyWord", never "containsPhrase" — then verify each candidate with a strict trimmed, case-sensitive comparison. - If a project text item's text is an exact match to a library component's text, link them. - If there's no exact match, leave it alone — don't create new components and don't link on partial/fuzzy matches. 4. Report back Summarize: how many nodes linked to pre-existing items, how many new items were created, and how many items got linked to a library component. Flag anything ambiguous (e.g. near-duplicate text, empty text nodes) instead of guessing. Ditto project: [Insert Ditto Project URL from the Ditto web app] Figma file: [Insert Figma File URL] ``` ### Save the prompt as a skill Store the prompt as a skill so anyone on the team runs it by name instead of pasting it. Create a `SKILL.md` with this frontmatter and the prompt above as the body, at the path for your agent, and commit it. Leave the two placeholder lines in place; the agent fills them from the URLs you pass when you run it. ```markdown theme={null} --- name: ditto-figma-link description: Link the text layers of a Figma file to a Ditto project and attach exact matches to library components. Use when asked to link, connect, or sync a Figma file with Ditto. --- ``` > [tab] Claude Code > > Path: `.claude/skills/ditto-figma-link/SKILL.md`. Run it with `/ditto-figma-link`, followed by the project URL and the file URL. > [tab] Cursor > > Path: `.cursor/skills/ditto-figma-link/SKILL.md`. Run it with `/ditto-figma-link`, followed by the project URL and the file URL. > [tab] Codex > > Path: `.agents/skills/ditto-figma-link/SKILL.md`. Run it with `$ditto-figma-link`, followed by the project URL and the file URL. ### What the agent does At each step of the prompt, and the tools it calls: 1. **Identify the project and file.** `list_projects` resolves the project from its URL, and the Figma MCP returns the file's text nodes. The agent works from each node's rendered text, not its layer name. 2. **Link Figma text nodes to project text items.** For each node, `search_ditto_text` with `projectIds` set to the project and `matchType: "anyWord"`, then a strict trimmed, case-sensitive comparison of the candidates. Matches are linked with `link_figma_nodes_to_text_items`, which takes a mapping of text items to node IDs, up to 500 node IDs per call (`branchId` for a Figma branch). No match: `create_text_items`, then link. Identical text on several nodes links to one text item. 3. **Link project text items to library components.** `search_ditto_text` with `entityTypes: ["libraryComponent"]`, exact matches only. `link_text_items_to_component` attaches each match, and the text item takes on the component's text, properties, variants, and developer ID. No new components, no fuzzy links. 4. **Report back.** Counts of nodes linked to existing items, items created, and items attached to components, plus anything ambiguous (near-duplicate text, empty nodes) for you to decide. `link_figma_nodes_to_text_items` returns a count per outcome, and lists each node that did not end up linked to its requested text item: | Outcome | Meaning | What to do | | - | - | - | | `linked` | The node now points at the text item | Nothing | | `already_linked` | The node was already linked to this item | Nothing | | `linked_to_other_text_item` | The node is already linked to a different item, named in `linkedTextItemId` | Keep the existing link, or unlink the node in the plugin and retry | | `not_found` | Ditto has not synced this node | Open the plugin in the file, sync the page, and retry | | `not_text_node` | The node is not a text layer | Check the node ID | | `wrong_file_or_branch` | The node belongs to a different file or branch than the project | Check `projectId` and `branchId` | | `main_component_text_node` | The node is text inside a Figma main component, which Ditto links through its instances | Link the instances instead | ## Step 4: What you have now * **Your Figma text layers are linked to text items.** A text item holds the copy and its status, tags, notes, and comments. Linking does not change the Figma file; from now on, edits to the text item in Ditto and edits to the layer in Figma sync to each other, and frame previews in the web app refresh on sync. * **Repeated copy points at one text item.** A string that appears on several screens is one text item, so an edit to any instance applies to all of them. * **Reused text is attached to your components.** Each matched text item carries the component's text, properties, variants, and developer ID, so the Figma layer, the component, and the key in your string files resolve to one ID. Edit the component and every instance updates. ## Link a single layer For one layer, give the agent the layer's link instead of the file's. In Figma, select the text layer, choose **Copy link**, and tell the agent which string it should show: ```text theme={null} The "Create account" button at should show our production string. Find that string in Ditto and link the layer to it. ``` The agent reads the node through the Figma MCP, finds the project connected to the file with `list_projects`, searches for "Create account" with `search_ditto_text`, and links the node with `link_figma_nodes_to_text_items`. ## After linking * **Namespaced developer IDs.** Project-only text items keep auto-generated IDs such as `welcome-to-dittopay-7`. Ask the agent to rename them into a scheme (`onboarding.welcome.title`) with `update_text_items` and `newDeveloperId`. Leave component-linked items alone; that ID is the component's, shared across every project that uses it. * **Undo a wrong link.** `unlink_text_items_from_component` detaches a text item (every instance carrying that developer ID, or only those in one `projectId`) and gives it a fresh developer ID. * **Keep in sync.** `npx -y @dittowords/cli@latest pull` brings design-side edits into code; re-running `scan` on the same repository lets Ditto match code-side changes against the earlier scan; the `TextItem_Base_Text_Changed` [webhook](/additional-tools/webhooks#event-reference) fires when linked text changes. Filter `pull` by status or the integrated flag so in-progress design copy never reaches production ([CLI configuration](/cli-reference/configuration#statuses)). # Connect the Ditto MCP Source: https://developer.dittowords.com/guides/connect-the-mcp Connect Claude Code, Cursor, Codex, VS Code, Windsurf, or Figma Make to Ditto with your Ditto login so agents follow your style guide and reuse text. Use Ditto with your agents, whether in drafting new copy, reusing existing strings, or integrating with code. The Ditto MCP (Model Context Protocol) server connects AI coding agents to your team's text in Ditto. Once connected, an agent can fetch your style guide rules before it writes UI copy, search for existing text to reuse instead of inventing new strings, check text against your rules, and create or edit text, projects, variants, and style guides directly from your editor. The server is remote: ```text theme={null} https://api.dittowords.com/v2/mcp ``` It uses Streamable HTTP and OAuth: the first time a client connects, it asks you to log in to Ditto in a browser, and from then on the agent acts as you. There is no token to create and nothing secret to store in your configuration. A static API key is accepted for CI and headless clients that cannot open a browser; see [API key for CI and headless clients](#api-key-for-ci-and-headless-clients). ## Choose your setup | Client | Recommended path | What you get | | - | - | - | | Claude Code, Claude Desktop, Cursor | [Install the Ditto plugin](#install-the-ditto-plugin) | The MCP server, always-on instructions that make the agent check Ditto before writing copy, and skills such as `/ditto-review` and `/ditto-audit` | | Codex, VS Code, Windsurf, Figma Make, other clients | [Add the MCP server directly](#add-the-mcp-server-directly) | The MCP server only. Add your own instructions from [Agent instructions](/guides/agent-instructions) | Either path ends with the same tools available to the agent. ## Install the Ditto plugin The plugin is the [agent setup package](https://github.com/dittowords/ditto-agent-setup). It bundles the MCP server configuration, session instructions that make the agent check Ditto before writing copy, and skills such as `/ditto-review` and `/ditto-audit`; the full list is on [Agent instructions](/guides/agent-instructions#skills-from-the-plugin). ### Claude Code Run these in a terminal (an agent can run them too; they change settings rather than starting a session): ```bash theme={null} claude plugin marketplace add dittowords/ditto-agent-setup claude plugin install ditto@ditto claude plugin list ``` Or, inside a Claude Code session, send each command as its own prompt: ```text theme={null} /plugin marketplace add dittowords/ditto-agent-setup ``` ```text theme={null} /plugin install ditto@ditto ``` Then log in: 1. Restart any running Claude Code sessions and approve the `ditto` MCP server when prompted. 2. Run `/mcp`, select the Ditto server (listed as **ditto**, or **plugin:ditto:ditto** when it comes from the plugin), and choose **Authenticate**. A browser tab opens for you to log in to Ditto and approve access. 3. Back in Claude Code, the Ditto tools appear in your tool list. ### Claude Desktop Claude Desktop has a plugin manager instead of a `/plugin` command. 1. Click **Customize**. 2. Open the **Plugins** tab, click **Add**, then **Add marketplace**. 3. Paste `https://github.com/dittowords/ditto-agent-setup` and confirm. 4. Find **Ditto** in the marketplace and click **Install**. 5. Go to **Connectors**, find **Ditto**, and click **Connect** (or **Reconnect**). A browser tab opens for you to log in to Ditto and approve access. > [accordion] Screenshots of the Claude Desktop steps > > ![Claude Desktop: Customize](https://mintcdn.com/ditto/5zth-7kfxkxk-RFM/images/claude-step-1.png?fit=max\&auto=format\&n=5zth-7kfxkxk-RFM\&q=85\&s=2cb1f2b8fbc682c8683dc482d2ae53f2) > > ![Claude Desktop: Plugins tab, Add, Add marketplace](https://mintcdn.com/ditto/5zth-7kfxkxk-RFM/images/claude-step-2.png?fit=max\&auto=format\&n=5zth-7kfxkxk-RFM\&q=85\&s=a0878ca96fc62338e887313030c084ef) > > ![Claude Desktop: paste the marketplace URL](https://mintcdn.com/ditto/5zth-7kfxkxk-RFM/images/claude-step-3.png?fit=max\&auto=format\&n=5zth-7kfxkxk-RFM\&q=85\&s=96768a9092c1cd5620124167ca9741bd) > > ![Claude Desktop: install Ditto](https://mintcdn.com/ditto/5zth-7kfxkxk-RFM/images/claude-step-4.png?fit=max\&auto=format\&n=5zth-7kfxkxk-RFM\&q=85\&s=4453779bb097c02dea935eb076bc251d) > > ![Claude Desktop: Connectors, Ditto, Connect](https://mintcdn.com/ditto/5zth-7kfxkxk-RFM/images/claude-step-5.png?fit=max\&auto=format\&n=5zth-7kfxkxk-RFM\&q=85\&s=aefe5c1ff767fb970d00303bc0983e16) ### Cursor 1. Open **Cursor Settings** and go to **Plugins**. 2. Paste `https://github.com/dittowords/ditto-agent-setup` into the **Search or Paste Link** field. 3. Click **Ditto**, then **Add to Cursor**. 4. Go to **Tools & MCPs** and log in to the **ditto** server. A browser tab opens for you to log in to Ditto and approve access. A green status indicator means you are connected. 5. Start a new Cursor session. Tools from a newly added server only load in sessions started after the install, so a "tools unavailable" message in the current session is expected. > [accordion] Screenshots of the Cursor steps > > ![Cursor: Plugins, paste the marketplace link](https://mintcdn.com/ditto/laCjxQwcBt02wjt8/images/cursor-step-1.png?fit=max\&auto=format\&n=laCjxQwcBt02wjt8\&q=85\&s=a5c92b09f89f4517d97277c405c37a91) > > ![Cursor: Tools & MCPs, log in to ditto](https://mintcdn.com/ditto/laCjxQwcBt02wjt8/images/cursor-step-2.png?fit=max\&auto=format\&n=laCjxQwcBt02wjt8\&q=85\&s=b86e5ac8cba71e54553dc7a3695ee1f7) ## Add the MCP server directly Use this when you only want the MCP server, or for a client the plugin does not support. Afterwards, add the instruction block from [Agent instructions](/guides/agent-instructions) so the agent knows when to use the tools. > [tab] Claude Code > > Install the official [Ditto connector](https://claude.ai/directory/connectors/ditto) from the Claude connectors directory. Claude Code loads the connectors on your claude.ai account, so after you connect Ditto there it is available in every Claude Code session. > > 1. Open the [Ditto connector page](https://claude.ai/directory/connectors/ditto) and click **Connect**. > 2. Log in to Ditto in the browser tab that opens and approve access. > 3. Restart any running Claude Code sessions, then run `/mcp`. Ditto is listed as **claude.ai Ditto**. > > Claude Code loads claude.ai connectors only when you are signed in with the claude.ai account you connected Ditto on. If Ditto is missing from `/mcp`, run `/status` to check how you are signed in. On Team and Enterprise plans, an admin adds the connector for the organization first; each member then connects with their own Ditto account. > > To add the server from the command line instead, for example to commit it to the repo for your team, pick a scope: > > ```bash theme={null} > # Local scope (default): this project, only you > claude mcp add --transport http ditto https://api.dittowords.com/v2/mcp > > # Project scope: writes .mcp.json at the repo root so the whole team gets it > claude mcp add --scope project --transport http ditto https://api.dittowords.com/v2/mcp > > # User scope: all of your projects, only you > claude mcp add --scope user --transport http ditto https://api.dittowords.com/v2/mcp > ``` > > Project scope creates this `.mcp.json`, which holds no credentials and is safe to commit: > > ```json theme={null} > { > "mcpServers": { > "ditto": { > "type": "http", > "url": "https://api.dittowords.com/v2/mcp" > } > } > } > ``` > > Then restart Claude Code, run `/mcp`, select **ditto**, and choose **Authenticate**. Each team member logs in with their own Ditto account the first time. Claude Code also asks for approval the first time a project-scoped server is used. > [tab] Cursor > > Add to `~/.cursor/mcp.json` (all projects) or `.cursor/mcp.json` in the repo (this project): > > ```json theme={null} > { > "mcpServers": { > "ditto": { > "url": "https://api.dittowords.com/v2/mcp" > } > } > } > ``` > > Or go to **Cursor Settings › Tools & MCP**, click **+ Add new MCP server**, and enter the URL. After saving, Cursor prompts you to log in to Ditto; a green status indicator means you are connected. > > > [!NOTE] > > > > Cursor 2.6.x has a [known bug](https://forum.cursor.com/t/cursor-fails-to-fall-back-from-streamable-http-to-sse-transport-for-remote-mcp-servers/154390) where remote servers retry indefinitely instead of connecting. Use the `mcp-remote` configuration under **Other clients** as a workaround until it is fixed. > [tab] VS Code > > Add to `~/.vscode/mcp.json` (all workspaces) or `.vscode/mcp.json` in the repo: > > ```json theme={null} > { > "servers": { > "ditto": { > "type": "http", > "url": "https://api.dittowords.com/v2/mcp" > } > } > } > ``` > > After saving, VS Code prompts you to log in to Ditto and approve access. > [tab] Windsurf > > Add to your Windsurf MCP configuration file: > > ```json theme={null} > { > "mcpServers": { > "ditto": { > "serverUrl": "https://api.dittowords.com/v2/mcp" > } > } > } > ``` > > After saving, Windsurf prompts you to log in to Ditto and approve access. > [tab] Codex > > ```bash theme={null} > codex mcp add ditto --url https://api.dittowords.com/v2/mcp > codex mcp login ditto > codex mcp list > ``` > > Or add it to `~/.codex/config.toml` yourself (use `.codex/config.toml` in the repo to scope it to one project; Codex reads project-level configuration for trusted projects only): > > ```toml theme={null} > [mcp_servers.ditto] > url = "https://api.dittowords.com/v2/mcp" > ``` > > `codex mcp login ditto` opens a browser tab for you to log in to Ditto. If the login never completes, that is a [known Codex bug](https://github.com/openai/codex/issues/31573); use an API key instead (below). Tools appear in Codex sessions started after the server was added. > [tab] Figma Make > > 1. From the chat box, select **Add context**, hover over **Connectors**, then choose **Manage**. > 2. Open the **Created by you** tab and click **Create**. Name the connector "Ditto". > 3. Enter `https://api.dittowords.com/v2/mcp` and click **Create**. > 4. Click **Connect**. Figma Make opens a browser tab where you log in to Ditto and approve access. > 5. Review the available tools and enable the ones you want. > > Figma Make reaches MCP servers over HTTPS only; it does not support localhost or stdio servers. > [tab] Other clients > > If your client does not support Streamable HTTP or SSE, bridge it with [`mcp-remote`](https://github.com/geelen/mcp-remote). It runs the OAuth flow for you and opens a browser tab on first use. > > ```json theme={null} > { > "mcpServers": { > "ditto": { > "command": "npx", > "args": ["-y", "mcp-remote", "https://api.dittowords.com/v2/mcp"] > } > } > } > ``` ## Verify the connection Ask the agent: ```text theme={null} List the style guide rules in my Ditto workspace. ``` A connected agent calls `get_styleguide_rules` and returns your rules, or says the workspace has none yet. From a terminal, `claude mcp list` (Claude Code) or `codex mcp list` (Codex) shows the server's status; `Needs authentication` means the login step has not been completed. ## API key for CI and headless clients Logging in with your Ditto account is the recommended way to connect. An API key is for the cases where no browser login is possible: a CI pipeline, an agent running unattended on a server, machine-to-machine access, or a client whose OAuth is broken (Codex, while [its bug](https://github.com/openai/codex/issues/31573) is open). With a key, the agent acts as the user who created it rather than as the person running it, and Ditto's activity history attributes its actions accordingly. > [accordion] Configure an API key > > **1. Create an API key (a workspace admin does this once)** > > The steps are in [API authentication](/api-reference/authentication#create-an-api-key). > > **2. Pass it as an Authorization header** > > The header value is `token `. If the same pipeline also runs the CLI, keep the key in the `DITTO_TOKEN` environment variable, which the [CLI](/cli-reference/authentication#in-ci-or-anywhere-without-a-browser) and the [Ditto Specs CLI](/ditto-specs-cli-reference/setup) read. > > > [tab] Claude Code > > > > ```bash theme={null} > > claude mcp add --transport http ditto https://api.dittowords.com/v2/mcp --header "Authorization: token " > > ``` > > > > For project scope, reference the environment variable so the committed file stays free of secrets: > > > > ```bash theme={null} > > claude mcp add --scope project --transport http ditto https://api.dittowords.com/v2/mcp --header 'Authorization: token ${DITTO_TOKEN}' > > ``` > > > > Equivalent `.mcp.json`: > > > > ```json theme={null} > > { > > "mcpServers": { > > "ditto": { > > "type": "http", > > "url": "https://api.dittowords.com/v2/mcp", > > "headers": { > > "Authorization": "token ${DITTO_TOKEN}" > > } > > } > > } > > } > > ``` > > > > Each team member then sets `DITTO_TOKEN` to their own API key. > > > [tab] Cursor, VS Code, Windsurf > > > > Add a `headers` object to the server entry. > > > > Cursor (`~/.cursor/mcp.json` or `.cursor/mcp.json`): > > > > ```json theme={null} > > { > > "mcpServers": { > > "ditto": { > > "url": "https://api.dittowords.com/v2/mcp", > > "headers": { "Authorization": "token " } > > } > > } > > } > > ``` > > > > VS Code (`~/.vscode/mcp.json` or `.vscode/mcp.json`): > > > > ```json theme={null} > > { > > "servers": { > > "ditto": { > > "type": "http", > > "url": "https://api.dittowords.com/v2/mcp", > > "headers": { "Authorization": "token " } > > } > > } > > } > > ``` > > > > Windsurf: > > > > ```json theme={null} > > { > > "mcpServers": { > > "ditto": { > > "serverUrl": "https://api.dittowords.com/v2/mcp", > > "headers": { "Authorization": "token " } > > } > > } > > } > > ``` > > > > Do not commit a file that contains a literal key. > > > [tab] Codex > > > > In `~/.codex/config.toml`: > > > > ```toml theme={null} > > [mcp_servers.ditto] > > url = "https://api.dittowords.com/v2/mcp" > > http_headers = { Authorization = "token " } > > ``` > > > > To keep the key out of the file, point Codex at an environment variable instead. Codex sends the variable's value as the header verbatim, so the value must include the `token ` prefix: > > > > ```toml theme={null} > > [mcp_servers.ditto] > > url = "https://api.dittowords.com/v2/mcp" > > env_http_headers = { Authorization = "DITTO_MCP_AUTH" } > > ``` > > > > ```bash theme={null} > > export DITTO_MCP_AUTH="token $DITTO_TOKEN" > > ``` > > > > Do not use `bearer_token_env_var`: it sends `Authorization: Bearer `, which Ditto reads as an OAuth access token and rejects. > > > [tab] Figma Make > > > > Follow the Figma Make steps above, but before clicking **Connect**, open **Advanced settings** and add a custom request header with name `Authorization` and value `token `. > > > [tab] mcp-remote > > > > ```json theme={null} > > { > > "mcpServers": { > > "ditto": { > > "command": "npx", > > "args": [ > > "-y", > > "mcp-remote", > > "https://api.dittowords.com/v2/mcp", > > "--header", > > "Authorization: token " > > ] > > } > > } > > } > > ``` ## Scope results to your codebase Add a `ditto/config.yml` (or `ditto/config.yaml`) file at the repo root listing the Ditto projects this codebase owns: ```yaml theme={null} projects: - id: your-project-developer-id ``` The MCP tools read it and scope results to those projects; it is the same file the CLI uses for `pull` (see [Scope results to your codebase](/mcp-reference/overview#scope-results-to-your-codebase)). Find a project's developer ID under the project's **Development integration** menu in the web app, or ask the agent to call `list_projects`. ## Make it stick Commit: * `.mcp.json` (Claude Code project scope) or `.cursor/mcp.json` / `.vscode/mcp.json`, with no literal keys in them. * `ditto/config.yml` with your project IDs. * The instruction block from [Agent instructions](/guides/agent-instructions) in `CLAUDE.md` or `AGENTS.md`. If you installed the plugin, the same instructions load automatically at session start; committing the block as well means every agent, plugin or not, behaves the same way. * `DITTO_TOKEN` as a secret in CI, only if pipelines run agents or the CLI without a browser. ## Optional: Ditto Specs [Ditto Specs](/ditto-specs-cli-reference/overview) are `*.ditto.md` files that sit next to your design system components and declare their text surfaces, with matching style guide rules synced from Ditto. With the plugin installed, run `/ditto-spec-setup`; the agent installs the Specs CLI if it is missing, creates `dittospec.config.json` and `workspace.ditto.md`, and scaffolds component spec files. ## Frequently asked questions **Is this the same as the documentation MCP server?** No. `https://api.dittowords.com/v2/mcp` is the Ditto MCP server described on this page; it connects an agent to your workspace. `https://developer.dittowords.com/mcp` is this documentation site's own server for searching the docs. **Which tools will the agent use?** It depends on the task. Writing UI copy uses `get_styleguide_rules`, `search_ditto_text`, and `suggest_edit`; importing a prototype or a codebase uses `create_ditto_project`, `create_text_items`, and the library tools; localization uses the variant and variable tools. Every tool is defined on the [Tools](/mcp-reference/tools) page. ## Troubleshooting * **`Needs authentication` in `claude mcp list`**: run `/mcp`, select the server, choose **Authenticate**, and complete the browser login. * **The tools are not in the tool list**: restart the session. Newly added servers only load in sessions started after the change. * **`Unauthorized` with an API key** (CI only): the header must read `Authorization: token `, and the key must come from a workspace with developer integrations enabled. * **Results include the wrong projects**: check `ditto/config.yml` at the repo root. Full list of tools and what each does: [MCP tools](/mcp-reference/tools). # Set up localization Source: https://developer.dittowords.com/guides/set-up-localization Manage and automate localization in Ditto: add locales as variants, translate with AI, preview in design, and bring localized string files to code. Manage and automate localization at any scale: automatically translate text, preview it in design, and bring it to code. Ditto keeps every language of a string on the same text item, so one developer ID resolves to the right text for each locale. ## How localization works in Ditto * A **locale** is a variant with a locale code (`de-DE`, `fr`, `es-419`); every text item and library component carries base text plus its variants. Definitions: [Variants and locales](/concepts#variants-and-locales). * The **base locale** is the language of your base text. Set it in a project's or the library's **Variant & locale settings** if your source copy is not English. * Locale-scoped [style guides](/concepts#style-guides-and-rules) apply when text is written or translated for that locale; variables and plurals carry into every variant. If your codebase already had translation files when you [scanned](/get-started/scan-your-repo) it, Ditto found the locales, proposed one variant per locale during review, and imported each locale's text as variant text. Check **Variants** in the left sidebar to see them. ## Step 1: add the locales you need Pick one: * **In the Ditto app**: open **Variants** in the left sidebar, click **+ New variant**, enter a name, choose the locale, and click **Create Variant**. * **From your agent**: "Create variants for German (de-DE) and Brazilian Portuguese (pt-BR)." The agent calls `create_variants` with a `localeCode` for each. Read the developer IDs off the response; they are generated from the name unless you pass one. * **From the API**: `POST /v2/variants` with `name` and `localeCode`. See [Create variants](/api-reference/variants/create-variants). Add the variant to the text that needs it: in a project, open the **…** menu, choose **Variant configuration**, and add the variant to all project text (optionally with auto-translate on). For a single text item, open its variant tab and click **Add variant**. ## Step 2: get the translations Choose the workflow that fits your team; they can be combined. > [tab] Magic Translate (in Ditto) > > Ditto's AI translation uses the variant's locale code as the target and applies any style guide scoped to that locale. > > * **One text item**: select it, open its variant tab, click **Add variant**, choose the locale variant, and click **Translate** under the text. > * **A whole project**: open the project's **…** menu, choose **Variant configuration**, add the locale variants, turn on auto-translate for the ones you want translated automatically, and click **Save**. > * **The component library**: the same **Variant configuration** flow from the library. > * **From your AI agent**: the MCP tools [`translate_text_items`](/mcp-reference/tools#translate_text_items) and [`translate_library_components`](/mcp-reference/tools#translate_library_components) run the same translation on the text items or components you name, up to 100 per call. > > Translations can be reviewed, edited, and scored in Ditto before developers pull them. > [tab] Crowdin or Lokalise > > Ditto connects to **Crowdin** and **Lokalise**. Open **Settings › Connections** in Ditto, choose the platform, and follow the connection steps. Translated text returns to the variants on your text items, so the developer handoff below is unchanged. > [tab] Your own translators or pipeline > > Write variant text with the API or the MCP: > > * API: `PATCH /v2/textItems` or `PATCH /v2/components` with `variantId` (a variant developer ID) in the payload updates that variant's text instead of the base. See [Update text items](/api-reference/text-items/patch-text-items). > * MCP: `update_text_items` or `update_library_components` with the variant developer ID from `list_variants`. > > Export the base text for translators with `GET /v2/textItems/export` in any format below, translate it, and write it back the same way. ## Step 3: pull localized files into your app The CLI writes one file per variant per source into `./ditto` by default: `{project_id}___{variant_id}.{ext}` for each project and `components__{variant_id}.{ext}` for the component library, where `variant_id` is the variant's developer ID or `base`. Configure what to pull in `ditto/config.yml` at the repo root and run: ```bash theme={null} npx -y @dittowords/cli@latest pull ``` ### Choose a format | Your platform | `outputs` entry | Files you get | Format details | | - | - | - | - | | Web, i18next | `format: json` + `framework: i18next` | Flat JSON per variant plus an i18next driver file | [JSON](/string-formats/json), [i18next files](/cli-reference/files#i18next) | | Web, Vue I18n | `format: json` + `framework: vue-i18n` | Flat JSON per variant plus a vue-i18n driver | [vue-i18n files](/cli-reference/files#vue-i18n) | | Any ICU-based library | `format: json` + `framework: icu` | ICU MessageFormat JSON | [ICU JSON](/string-formats/json#icu-json) | | Flutter | `format: json` + `framework: arb` | ARB files with `@@locale` from each variant's locale code | [ARB JSON](/string-formats/json#arb-json) | | iOS | `format: ios-strings` and `format: ios-stringsdict` | `.strings` and `.stringsdict`, grouped into `.lproj` bundles when `iosLocales` is set, plus a `Ditto.swift` driver | [iOS strings](/string-formats/ios), [iOS locale directories](/cli-reference/files#ditto-generated-files) | | Android | `format: android` | `strings.xml` per variant, in `values-/` directories when `androidLocales` is set | [Android XML](/string-formats/android), [Android locale directories](/cli-reference/files#android-locale-directories) | The same formats are available from the API without the CLI: `GET /v2/textItems/export?format=&variantId=` and the equivalent `/v2/components/export`. See [Export text items](/api-reference/text-items/export-text-items). ### Example: a web app with three locales ```yaml theme={null} # ditto/config.yml projects: - id: checkout variants: - id: base - id: german - id: french outputs: - format: json framework: i18next outDir: ./src/locales ``` Result: ```text theme={null} src/locales/ ├── checkout___base.json ├── checkout___german.json ├── checkout___french.json └── (i18next driver file that imports the three JSON files, grouped by variant) ``` The driver file is described under [i18next files](/cli-reference/files#i18next); set `type: module` in the output to get ES module syntax instead of CommonJS. ### iOS and Android `iosLocales` and `androidLocales` map variants to platform locale directories: `.lproj` bundles with a `Ditto.swift` helper on iOS, `values-/` resources on Android. Complete configs for both are under [Examples](/cli-reference/configuration#examples). Full option reference: [CLI configuration](/cli-reference/configuration) (`variants`, `iosLocales`, `iosLocalesOutDir`, `androidLocales`, `androidLocalesOutDir`, `outputs`) and [Files](/cli-reference/files). ## Keep it in sync * Run `pull` in CI or a pre-build step so every build ships the latest approved text. Set `DITTO_TOKEN` as a secret; see [CLI authentication](/cli-reference/authentication#in-ci-or-anywhere-without-a-browser). * Use the [GitHub Action](/additional-tools/gh-action) to open a pull request automatically when text changes. * Filter by status (`statuses: [FINAL]`) or by the **integrated** flag so unreviewed translations never reach production. See [`statuses`](/cli-reference/configuration#statuses) and [`integrated`](/cli-reference/configuration#integrated). * Subscribe to `TextItem_Variant_Text_Changed` and `TextItem_Variants_Changed` [webhooks](/additional-tools/webhooks#text-item-variant-text-change) to react when translations change. ## Ask an agent to do it With the [MCP connected](/guides/connect-the-mcp), all of the above is available as a conversation: ```text theme={null} Add a Spanish (es) variant to our workspace, translate the checkout project's text into Spanish following our Spanish style guide, and update ditto/config.yml so `pull` fetches base, German, and Spanish. ``` The agent calls `create_variants`, `list_variants`, `update_text_items` (with the variant developer ID) or leaves translation to Magic Translate, `suggest_edit` against the locale-scoped guide, and then edits the config file in your repo. # Ditto developer documentation Source: https://developer.dittowords.com/introduction Ditto is a single source of truth for product copy across design, review, localization, and code. Start by bringing in the strings from your codebase. **Ship the right copy, faster than ever.** Ditto gives you a single source of truth for product copy across design, review, localization, and code. For developers, that means the strings already in your codebase become searchable, reusable text that designers, writers, and coding agents all work from. The same text flows back into code as string files, API responses, and context for AI agents. ## Start here: bring in the strings from your codebase After running the single command to scan, you'll be able to search and reuse what's live today. Scan only needs read access to your codebase; it won't edit or write to your repo, and you review what's scanned before bringing it into Ditto. ```bash theme={null} npx -y @dittowords/cli@latest login npx -y @dittowords/cli@latest scan ./src ``` > [card] [Scan your repo](/get-started/scan-your-repo) > > Full instructions on scanning your repo: Set up in about 10 minutes with a prompt for your agent and information on what happens next. If you set up Ditto from the onboarding flow in the web app, you have already done this step. ## Start using Ditto in one of these workflows > [card] [Connect your strings to your Figma files](/guides/connect-figma-files) > > Reuse and link the strings you imported from your codebase in your Figma files. > [card] [Manage and automate localization](/guides/set-up-localization) > > Automatically translate text, preview it in design, and bring it to code. > [card] [Set up your style guides](/get-started/scan-your-repo#get-a-first-style-guide) > > We can infer a starter style guide from your strings, or you can import your own. > [card] [Connect the Ditto MCP](/guides/connect-the-mcp) > > Use Ditto with your agents, whether in drafting new copy, reusing existing strings, or integrating with code. ## Which developer surface do you need? Ditto has several developer surfaces. Use this table to pick the right one. | You want to | Use | | - | - | | Import the strings already in a codebase | [CLI `scan`](/get-started/scan-your-repo) | | Pull string files (JSON, iOS, Android, ARB) into a build or CI | [CLI `pull`](/cli-reference/commands#pull) | | Let a coding agent create, find, and edit text in Ditto, follow your style guide, and localize | [Ditto MCP server](/guides/connect-the-mcp) | | Read or write text, projects, variants, and style guides programmatically | [REST API](/api-reference/authentication) | | React when text changes in Ditto | [Webhooks](/additional-tools/webhooks) | | Review pull requests for style guide violations and reusable text | [GitHub PR review bot](/additional-tools/github-pr-review-bot) | | Open pull requests with the latest text automatically | [GitHub Action](/additional-tools/gh-action) | | Co-locate text rules with your design system components in the repo | [Ditto Specs CLI](/ditto-specs-cli-reference/overview) | ## How the pieces fit Text items live in projects; library components are reusable across projects, and a scan creates them from your strings. Developer IDs are the stable keys that code, design, and translations share. Style guides hold the rules for how your text reads, variants hold locales and other alternatives, and variables and plurals are formatted per string file. Definitions: [Key concepts](/concepts). ## If you are a coding agent * The index of this site is at `https://developer.dittowords.com/llms.txt`, and every page is available as Markdown by appending `.md` to its URL. * To search these docs from your tool, connect the documentation MCP server at `https://developer.dittowords.com/mcp`. It is separate from the **Ditto MCP server** at `https://api.dittowords.com/v2/mcp`, which connects you to a workspace's text and rules; see [Connect the Ditto MCP](/guides/connect-the-mcp). * Start with [Scan your repo](/get-started/scan-your-repo). Prefer logging in with a Ditto account over API keys. ## After setup, you'll be able to use Ditto to * **Keep copy in sync across design and code.** Keep your strings connected and in sync, from Figma design files to your codebase. * **Manage and automate localization.** Manage localization at any scale. Draft and score translations with AI, review translated text in designs, and hand off with streamlined string files. * **Draft on-brand copy with AI, in any tool.** Get every teammate and agent writing from the same set of rules, with Ditto automatically flagging any text that needs improvement. * **Fetch the latest strings in production.** Keep what's in the code up to date with the API and CLI, without having to ask an engineer to edit strings. # Legacy API authentication Source: https://developer.dittowords.com/legacy/api-reference-legacy/authentication Authenticate requests to the legacy (v1) Ditto API with an API key in the Authorization header. Ditto's developer integrations require an API key to authenticate requests. You can manage your API keys from the [Developer Integrations](https://app.dittowords.com/developers) page. API keys enable programmatic access to all data in your workspace, so be sure to keep them secure. Don't share your API keys in publicly accessible places such as GitHub or client-side code. ## Generate an API key 1. Navigate to the [Developer Integrations](https://app.dittowords.com/developers) page. 2. Enable developer integrations for your workspace if they haven't been enabled already (see [this help guide](https://dittov3.notion.site/Developer-Mode-256cc8865c7a801ea9bdca5aa4303fca) for more information). 3. Under the **API Keys** section, click **+ Create API key**. 4. Enter a name for the API key and click the **Create** button. 5. Copy the value of the API key to your clipboard and store it somewhere safe; you won't be able to access it again after closing the modal. ## Authenticate API requests Authorize requests to the Ditto API by passing an API key in an `Authorization` header: ```text theme={null} "Authorization": "" ``` Here is an example request to the `/components` endpoint: ```sh theme={null} curl -X GET \ --header "Authorization: " \ "https://api.dittowords.com/v1/components" ``` ## Authenticate the Ditto CLI The first time you run the CLI, you’ll be asked to provide an API key. See the [CLI documentation](/cli-reference/authentication) for more information. # Fetch component folders Source: https://developer.dittowords.com/legacy/api-reference-legacy/components-folders/fetch-component-folders openapi-legacy.json get /component-folders Returns an object of component folders in the requesting user's workspace. # Fetch component text by component folder Source: https://developer.dittowords.com/legacy/api-reference-legacy/components-folders/fetch-component-text-by-component-folder openapi-legacy.json get /component-folders/{api_id}/components Returns the components in the specified folder. Format is identical to the format used by the Fetch components endpoint. # Fetch component text Source: https://developer.dittowords.com/legacy/api-reference-legacy/components/fetch-component-text openapi-legacy.json get /components Returns the components in the workspace of the requesting user. Only component libraries with Developer Mode enabled can be fetched. # Import components via a file Source: https://developer.dittowords.com/legacy/api-reference-legacy/components/import-components-via-a-file openapi-legacy.json post /components/file For more information about importing components via a file please see: https://dittov3.notion.site/Importing-JSON-iOS-Android-Files-256cc8865c7a80fe94cdf135631bf8c6. The request body must be of type multipart/form-data. # Update component text variant Source: https://developer.dittowords.com/legacy/api-reference-legacy/components/update-component-text-variant openapi-legacy.json put /components Used for updating the text and status fields for components in the workspace of the requesting user. -> Currently only supports updating variant text or status on components via the variant query parameter (an error will be thrown if the variant param is not specified) -> Currently only available for teams on team or enterprise plans # Fetch project folders Source: https://developer.dittowords.com/legacy/api-reference-legacy/project-folders/fetch-project-folders openapi-legacy.json get /project-folders Returns a list of project folders in the workspace of the requesting user. # Fetch projects by project folder id Source: https://developer.dittowords.com/legacy/api-reference-legacy/project-folders/fetch-projects-by-project-folder-id openapi-legacy.json get /project-folders/{folder_id}/projects Returns a list of projects in the specified project folder. # Fetch component text by project id Source: https://developer.dittowords.com/legacy/api-reference-legacy/projects/fetch-component-text-by-project-id openapi-legacy.json get /projects/{id}/components Returns the components used in the specified project. Format is identical to the format used by the Fetch components endpoint. # Fetch projects Source: https://developer.dittowords.com/legacy/api-reference-legacy/projects/fetch-projects openapi-legacy.json get /projects Returns a list of Ditto projects. Only projects with Developer Mode enabled can be fetched. # Fetch text by project id Source: https://developer.dittowords.com/legacy/api-reference-legacy/projects/fetch-text-by-project-id openapi-legacy.json get /projects/{id} Returns the contents of the Ditto project referred to by :id. You can find the id of a Ditto project from its URL: https://app.dittowords.com/doc/:id. Only projects with Developer Mode enabled can be fetched. # Fetch variables Source: https://developer.dittowords.com/legacy/api-reference-legacy/variables/fetch-variables openapi-legacy.json get /variables Returns the variables in the workspace of the requesting user. # Fetch variants Source: https://developer.dittowords.com/legacy/api-reference-legacy/variants/fetch-variants openapi-legacy.json get /variants Returns a list of variants in the workspace of the requesting user. # Legacy CLI installation Source: https://developer.dittowords.com/legacy/cli-reference-legacy/authentication Installation and authentication of the Ditto CLI ## Overview > [!WARNING] > > The following documentation relates to `v4.x` of the Ditto CLI. To use legacy features with `v5.x`, add the `--legacy` > flag to your commands. The Ditto CLI allows developers to access the [Ditto API](/api-reference/authentication) directly from the command line. The Ditto CLI updates text directly in local development. Teams can also build workflows with the CLI, including utilizing it in CI/CD. ## Installation To install the CLI as a development dependency: ```bash npm theme={null} npm i --save-dev @dittowords/cli ``` ```bash yarn theme={null} yarn add --dev @dittowords/cli ``` After installation, you can get a list of the available commands by running the help command: ```bash theme={null} npx @dittowords/cli --help ``` > [!NOTE] > > **Note:** [npx](https://medium.com/@maybekatz/introducing-npx-an-npm-package-runner-55f7d4bd282b) comes with `npm` 5.2 > or higher ## Authentication The first time you run the CLI, you'll be asked to provide an API key. You can generate an API key in your [developer integrations settings](https://app.dittowords.com/developers/api-keys). > [accordion] Using an env variable for your API key > > By default, your API key is saved to `$HOME/.config/ditto`; the default path can be overwritten by specifying a custom one via the environment variable `DITTO_CONFIG_FILE`. > > The Ditto API key is read from the file at the path stored at `DITTO_CONFIG_FILE` unless it's explicitly provided by the env var `DITTO_API_KEY`. > [accordion] Updating your API key > > We don't recommend editing the authentication file by hand; if you need to remove a saved API key or swap one key with another, it's better to fully delete the file and then re-run the CLI. > > To change your API key, delete the `$HOME/.config/ditto` file and you'll be prompted for a new key the next time that the CLI executes. ## Fetching text Once you've installed, authenticated, and [configured](/cli-reference/configuration) the CLI, you'll be able to pull down up-to-date text by running the CLI: ```bash theme={null} npx @dittowords/cli pull ``` This will update the string files you have locally in the `/ditto` directory from where you're running the CLI. (For more information on the structure of files pulled by Ditto, see [files](/cli-reference/files)) Because the Ditto CLI updates your string files locally, you can continue to manage it as you would with any other product update (push it to staging, commit changes to a branch, etc.). # Legacy CLI configuration Source: https://developer.dittowords.com/legacy/cli-reference-legacy/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} sources: components: enabled: true format: flat variants: true ``` ## Supported Properties Listed below are each of the properties that you can specify in your CLI config. ### `sources` A list of places the CLI should pull text data from. For the CLI to function, at least one source type (`components` or `projects`) is required. #### `sources.components` A boolean or configuration object indicating how component data should be pulled from your component library. The simplest configuration is a boolean, which indicates that all components in your component library should be fetched: ```yml theme={null} sources: components: true ``` You can also pass an object that supports two complementary properties: `root` and `folders`. * `root` controls behavior for components that are NOT in folders (i.e. that are at the *root* of the component library) * `folders` controls behavior for components that are ARE in folders Further examples on how to use the two properties to specify the components fetched: > [accordion] Only components not in folders > > If `root` is `true` and `folders` is unspecified, **only components not in folders** will be fetched: > > ```yml theme={null} > sources: > components: > root: true > ``` > [accordion] Only components in folders > > If `root` is `false` and `folders` is unspecified, **only components in any folder** will be fetched: > > ```yml theme={null} > sources: > components: > root: false > ``` > [accordion] Only components in specified folders > > If `root` is unspecified and `folders` contains a list of component folders, **only components in the specified folders** will be fetched: > > ```yml theme={null} > sources: > components: > folders: > - id: folder-api-id-1 > name: Folder 1 > - id: folder-api-id-2 > name: Folder 2 > ``` > [accordion] Only components in specified folders and not in a folder > > If `root` is `true` and `folders` contains a list of component folders, **non-folder components and components in the specified folders** will be fetched: > > ```yml theme={null} > sources: > components: > root: true > folders: > - id: folder-api-id-1 > name: Folder 1 > - id: folder-api-id-2 > name: Folder 2 > ``` > [accordion] Only components not in folders with a given status > > `root` can also be specified as a configuration object with a `status` property to indicate that non-folder components should be fetched that have the indicated status: > > ```yml theme={null} > sources: > components: > root: > status: WIP > ``` #### `sources.projects` A list of projects to pull text from. ```yml theme={null} sources: projects: - id: 61b8d26105f8f400e97fdd14 name: Landing Page Copy - id: 606cb89ac55041013d552f8b name: User Settings ``` * The `name` property is used for display purposes when referencing a project in the CLI, but does not have to be an exact match with the project name in Ditto. An `exclude_components` property can be added on a per-project basis to indicate that text items should only be pulled which are not associated with a component: ```yml theme={null} sources: projects: - id: 61b8d26105f8f400e97fdd14 name: Landing Page Copy exclude_components: true ``` ### `variants` Enables including variant information when pulling text data for configured sources. Defaults to `false` if not specified. ```yml theme={null} variants: true ``` ### `format` The format that text data should be generated in. For examples of each format, see [JSON](/string-formats/json), [iOS Strings](/string-formats/ios), and [Android XML](/string-formats/android) in the String Formats documentation. Accepted values: * `structured` * `flat` * `nested` * `android` * `ios-strings` * `ios-stringsdict` Defaults to `flat` if not specified. ```yml theme={null} format: flat ``` Also accepts a list of values: ```yml theme={null} format: - ios-strings - ios-stringsdict ``` You should not specify multiple JSON formats in the same configuration, as they will overwrite one another when writing to disk. ### `status` If specified, only source data with the indicated status will be fetched. Accepted values: * `NONE` * `WIP` * `REVIEW` * `FINAL` If `status` is specified at the top level of the configuration, it will apply to all sources: ```yml theme={null} status: FINAL sources: components: true projects: - id: project-1 name: Project 1 ``` `status` can also be specified at the level of individual sources: ```yml theme={null} sources: components: root: status: REVIEW folders: - id: folder-api-id-1 name: Folder 1 status: NONE projects: - id: project-1 name: Project 1 status: WIP ``` If both are specified, a source-level `status` overrides a top-level `status`. More information about Ditto statuses can be found [here](https://dittov3.notion.site/Tips-for-cross-team-collaboration-256cc8865c7a8041a922f2d3838c61ec). ### `richText` If defined with the `flat` format, output values will be HTML strings (rich text) for each piece of source data. If defined with the `structured` format, output data will include a `rich_text` property that is an HTML string (rich text) for each piece of source data. You can read more about Ditto's rich text feature [here](https://dittov3.notion.site/Rich-Text-Formatting-in-Ditto-256cc8865c7a808b9837c1bfb7728dba). ```yml theme={null} richText: true ``` ### `iosLocales` A property that maps [variant](https://dittov3.notion.site/Variants-256cc8865c7a80e38671e6f9206001af) IDs in Ditto to [iOS locale IDs](https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPInternational/LanguageandLocaleIDs/LanguageandLocaleIDs.html), intended to simplify the process of integrating Ditto with an iOS project. This property should only be defined in conjunction with one or both of the `ios-strings` or `ios-stringsdict` formats. When this property is defined, iOS string files written to disk by the CLI are grouped into `lproj` localization bundles, and a `Ditto.swift` driver file is generated. ```yml theme={null} iosLocales: - base: en - spanish: es ``` The `base` property is required, and should map to the locale ID corresponding to the default language of your iOS project. See [iOS format-specific files](/cli-reference/files#ios-strings) for more information. ### `disableJsDriver` A boolean property that disables the generation of the `index.js` and `index.d.ts` driver files when working with JSON formats. ```yml theme={null} disableJsDriver: true ``` ## Full Configuration Example ```yml theme={null} sources: components: root: true folders: - id: onboarding name: Onboarding - id: onboarding-2 name: Onboarding 2 status: WIP projects: - name: Landing Page Copy id: 61b8d26105f8f400e97fdd14 - name: User Settings id: 606cb89ac55041013d552f8b status: WIP variants: true format: structured status: FINAL richText: false disableJsDriver: false ``` # Legacy CLI output files Source: https://developer.dittowords.com/legacy/cli-reference-legacy/files The file structure of text pulled down via the CLI ## Output Directory The CLI outputs data from Ditto by writing files to disk. By default, these files are written to a `./ditto` folder relative to the current working directory, but the location of the output files can be customized by setting the environment variable `DITTO_TEXT_DIR`. If a `DITTO_TEXT_DIR` environment variable is specified, the config will still be read from a `./ditto` folder relative to the current working directory that the CLI is executing in. ## `./ditto` Directory This directory houses the configuration file (`./ditto/config.yml`) used by the CLI and is also the default write destination for any output files the CLI writes to disk. If you run the CLI in a directory that does not contain a `ditto/` folder, the folder and a default `config.yml` file will be automatically created. ## String File Naming Conventions All string files created will adhere to the following template: ```text theme={null} {source_name}__{variant_name}.{extension} ``` * `source_name`: the name of the source a given file holds data for * for components, this is `components__{folder_name}` * for projects, this is `{project-name}` * `variant_name`: the API ID of a variant or `base` for files generated for non-variant text * `extension`: the file extension that corresponds to the configured format(s) To keep cross-platform behavior consistent, file names will be fully lowercase, have whitespace replaced with hyphens, and have non-word characters (with the exception of `-` and `_`) removed. See the section on [iOS format-specific files](#ios) for the only exception to these conventions. ## Format-specific Files ### JSON If one or more JSON string formats are configured, an `index.js` driver file is generated to simplify the process of integrating Ditto into JavaScript apps. If a `package.json` file is detected with `"type": "module"`, the driver file will be generated as an ESM module. Otherwise, it will be generated as a CommonJS module. ```js theme={null} // CommonJS const componentsRootBase = require("./components__root__base.json"); const componentsRootSpanish = require("./components__root__spanish.json"); const exampleProjectBase = require("./example-project__base.json"); const exampleProjectSpanish = require("./example-project__spanish.json"); module.exports = { ditto_component_library: { base: { ...componentsRootBase }, spanish: { ...componentsRootSpanish }, }, project_xxx: { base: { ...exampleProjectBase }, spanish: { ...exampleProjectSpanish }, }, }; // ESM import componentsRootBase from "./components__root__base.json"; import componentsRootSpanish from "./components__root__spanish.json"; import exampleProjectBase from "./example-project__base.json"; import exampleProjectSpanish from "./example-project__spanish.json"; export default { ditto_component_library: { base: { ...componentsRootBase }, spanish: { ...componentsRootSpanish }, }, project_xxx: { base: { ...exampleProjectBase }, spanish: { ...exampleProjectSpanish }, }, }; ``` To enable easy Typescript compatibility, an `index.d.ts` file is also generated with type definitions for the driver file. The generation of both the `index.js` and `index.d.ts` files can be disabled by specifying `disableJsDriver: true` in your `config.yml` file. ### iOS If one or more iOS string formats (`ios-strings`, `ios-stringsdict`) are configured alongside the [iosLocales](/cli-reference/configuration#ioslocales) property, iOS string files are grouped into localization bundles and a `Ditto.swift` driver file is generated. The string files will be structured as follows: ```sh theme={null} ditto/ ├── en.lproj/ │ ├── components.strings │ ├── project1.strings ├── es.lproj/ │ ├── components.strings │ ├── project1.strings ``` The `Ditto.swift` driver file will look like this: ```swift theme={null} // // Ditto.swift // // Created by the Ditto CLI on 10/6/2023, 1:16:52 PM // import SwiftUI struct Ditto { public static func component1(_ localeOverride: String? = nil) -> String { String.localizedStringWithFormat(NSLocalizedString("component-1", tableName: "components", /* ... */)) } public struct project1 { public static func textItem1(_ localeOverride: String? = nil, variable1: String) -> String { String.localizedStringWithFormat(NSLocalizedString("text-item-1", tableName: "project1", /* ... */), variable1) } } ``` These files are designed to be imported into Xcode **by reference**. This enables using the CLI to pull the latest strings from Ditto and have the text in your project automatically updated. To add these files to your Xcode project: 1. Open Finder and select all `.strings` and `.stringsdict` files inside of the respective `lproj` folders. 2. Drag the selected files into your Xcode project. 3. When prompted, select **Create folder references** and deselect **Copy if needed**. 4. Click **Finish**. Once all your localization bundles and `Ditto.swift` driver are imported into Xcode, you can call methods from the `Ditto` struct directly to get localized strings: ```swift theme={null} struct ContentView: View { var body: some View { Text(Ditto.component1()) Text(Ditto.textItem1(variable1: "Hello world!")) } } ``` # Legacy tools Source: https://developer.dittowords.com/legacy/legacy-tools The legacy CLI mode, the Ditto React SDK, the VS Code extension, and legacy webhook events, with the current tools that replace each one. These tools predate the current Ditto CLI (`5.x`), the MCP server, and `scan`. They keep working for teams that adopted them, but new integrations should start with [Scan your repo](/get-started/scan-your-repo). ## Legacy CLI mode Version `5.x` of the Ditto CLI pulls text from the current version of Ditto by default and still supports every `4.x` feature behind the `--legacy` flag. Add the flag to any command that `v4.5.2` supported: ```bash theme={null} npx @dittowords/cli --legacy pull [options] npx @dittowords/cli --legacy project npx @dittowords/cli --legacy component-folders [options] npx @dittowords/cli --legacy generate-suggestions [options] npx @dittowords/cli --legacy replace [options] npx @dittowords/cli --legacy import-components [options] npx @dittowords/cli --legacy help [command] ``` To use legacy and current features in the same repo, keep two config files and point at the legacy one with `-c`: ```bash theme={null} npx @dittowords/cli --legacy pull -c ./ditto/legacy-config.yml ``` Full reference: [Legacy CLI installation](/legacy/cli-reference-legacy/authentication), [configuration](/legacy/cli-reference-legacy/configuration), and [files](/legacy/cli-reference-legacy/files). Legacy API endpoints: [Legacy API reference](/legacy/api-reference-legacy/authentication). ## Ditto React SDK [`ditto-react`](https://www.npmjs.com/package/ditto-react) is a React and React Native library that reads the JSON string files written by the CLI and exposes them through a provider and hooks, with first-class support for variants. It works with all JSON output formats. Installation and usage are in the package README on npm. Issues go to [dittowords/ditto-react](https://github.com/dittowords/ditto-react/issues). The library's last release was 1.6.3; new React projects can use the CLI's i18next output with `react-i18next` instead, which needs no Ditto-specific runtime dependency. ## Legacy webhook events These events are *only* available on legacy webhooks, which are connected directly to the legacy component library; if you joined Ditto after March 2025, you probably aren't looking for these. ### Component Creation Emitted any time a component is created in Ditto. ```tsx theme={null} { "event": "Component_Creation", "data": { "componentId": "test_component", "folderId": "test_folder", // null if not in a folder "name": "Test Component", "text": "This is a test component.", "status": "NONE", "notes": "", "tags": ["test_tag"] } } ``` ### Component Deletion Emitted any time a component is deleted in Ditto. ```tsx theme={null} { "event": "Component_Deletion", "data": { "componentId": "test_component", "folderId": "test_folder", // null if not in a folder "name": "Test Component" } } ``` ### Component Text Change Emitted any time a component’s base text is changed in Ditto. *Does not emit when variant text, plural text, or rich text styling is changed.* ```tsx theme={null} { "event": "Component_TextChange", "data": { "componentId": "test_component", "folderId": "test_folder", // null if not in a folder "textBefore": "This is a test component.", "textAfter": "This is a test component!" } } ``` ### Component Status Change Emitted any time a component’s status is changed in Ditto. ```tsx theme={null} { "event": "Component_StatusChange", "data": { "componentId": "test_component", "folderId": "test_folder", // null if not in a folder "statusBefore": "NONE", "statusAfter": "WIP" } } ``` ### Component ID Change Emitted any time a component’s developer ID is changed in Ditto. ```tsx theme={null} { "event": "Component_IdChange", "data": { "componentIdBefore": "test_component", "componentIdAfter": "test_component_1", "folderId": "test_folder", // null if not in a folder } } ``` ### Test Event Emitted when sending a test request while creating a webhook. ```tsx theme={null} { "event": "TestEvent", "data": { "message": "Hello, Ditto!" } } ``` # Ditto MCP server overview Source: https://developer.dittowords.com/mcp-reference/overview The Ditto MCP (Model Context Protocol) server connects AI coding agents to your team's product text in Ditto. The server runs remotely at `https://api.dittowords.com/v2/mcp`, authenticates with your Ditto account, and gives an agent the operations a person has in the Ditto app. **The goal is parity with the Ditto app.** Anything you can do in the UI, you can do through the MCP server, and new capabilities ship to both. To connect a client, see [Setup](/guides/connect-the-mcp). Every tool, with its parameters and constraints, is defined on the [Tools](/mcp-reference/tools) page. Prompts for the tasks below are on [Agent instructions](/guides/agent-instructions). ## What teams use it for ### Bring product text into Ditto * **Spin up a new Ditto project from an AI prototype.** The agent reads the copy in a prototype, creates a project, and adds each string as a text item with a developer ID and a status. Designers and writers refine the text in Ditto while the developer keeps building against the IDs. * **Bulk-create text items.** From a design, a spreadsheet, or a list in chat, the agent checks each string against what already exists, reuses the matches, and creates the rest with developer IDs, tags, and statuses in one call. * **Replace hardcoded strings.** After a scan, the agent swaps string literals in code for the developer IDs of the library components Ditto created from them. ### Write on-brand copy * **Generate UI with compliant text.** Before writing copy for a new screen, the agent fetches your style guide rules and searches for text that already says it, then checks what it wrote against the rules. * **Brainstorm with full context.** Asked for options, the agent proposes copy informed by the words your library already uses and the rules your team has agreed on, rather than starting from a blank page. * **Review a diff before committing.** The agent audits the user-facing strings in a change for rule violations and near-duplicates of existing text, and reports them before they ship. ### Keep the workspace consistent * **Run a global search for near-duplicates.** The agent finds the variations of a phrase across the workspace ("Log in", "Log In", "Sign in"), consolidates them onto one approved version, and adds it to the library. * **Clean up after a style guide change.** When a rule changes, the agent finds every text item that now breaks it and updates them in bulk. When a review comment reveals a pattern, it adds the rule to the style guide. * **Reorganize your component library.** The agent creates folders and moves components into them so the library follows your product's structure. ### Localize * **Draft a new variant for translated copy.** The agent adds the locale, translates the text following that locale's style guide, and writes the translations to each text item for a localization manager to review. ### Connect designs * **Link a Figma file's text to Ditto.** The agent reads the text layers of a connected Figma file, matches them to existing text, shows you anything ambiguous, and links the layers so design and code share developer IDs. Walkthrough: [Connect Figma files](/guides/connect-figma-files). ## Scope results to your codebase Add `ditto/config.yml` (or `ditto/config.yaml`) at the repo root listing the Ditto projects this codebase owns: ```yaml theme={null} projects: - id: your-project-developer-id ``` When present, `get_styleguide_rules`, `search_ditto_text`, and `list_projects` read it and scope their results to those projects. This is the same file the CLI's `pull` command uses, so one committed file serves both; everything else it can contain is in [CLI configuration](/cli-reference/configuration). ## Permissions and attribution With your Ditto login, tools run with your Ditto permissions and actions are attributed to you in Ditto's activity history. With an API key, tools run as the user who created the key. # Ditto MCP tools Source: https://developer.dittowords.com/mcp-reference/tools Available tools in the Ditto MCP server The Ditto MCP server provides the following tools that your AI coding agent can call, grouped by the entity each one acts on; the [Overview](/mcp-reference/overview#what-teams-use-it-for) describes what teams use them for. To connect the server, see [Setup](/guides/connect-the-mcp); for the instructions that make an agent call these tools at the right time, see [Agent instructions](/guides/agent-instructions). Tool names below are exact. Several tools accept a batch of items; batch calls are all-or-nothing unless the description says otherwise. Delete tools cannot be undone. ## Style guides and rules ### get\_styleguide\_rules Returns the workspace's enabled style guide rules. Use them as the reference when writing or reviewing UI copy so it complies with the team's guidelines. Rules from a locale-scoped style guide are labeled with their locale; apply those only when writing text for that locale. Pass the project developer IDs from the repo's `ditto/config.yml` as `projectIds` to scope results to this codebase; see [Scope results to your codebase](/mcp-reference/overview#scope-results-to-your-codebase). What a style guide and a rule are: [Style guides and rules](/concepts#style-guides-and-rules). Ditto drafts a first guide from your codebase; see [Get a first style guide from your scan](/get-started/scan-your-repo#get-a-first-style-guide). Every rule comes back with an ID. When you write text that a rule shaped, cite that ID in `ruleIds` on the tool doing the write — `create_text_items`, `update_text_items`, `create_library_components`, or `update_library_components` — and Ditto records the rule against the text. `ruleIds` is set per item, so each item in a batch cites its own rules. Only writes that change the text itself are attributed: a call that updates just a status, tags, or an assignee records nothing. Rule IDs your workspace doesn't recognize are named back in the tool's response rather than dropped silently, so call `get_styleguide_rules` again for the current IDs if that happens. ### suggest\_edit Checks text against your Ditto workspace's style guide rules and returns suggested edits — the same rule-based suggestions shown in the Ditto app. Check an existing text item by its developer ID, or check freeform text that isn't saved as a text item yet. Results are cached per exact input; pass `forceNewGeneration` to force a fresh check. An empty result means the text already complies with the style guide, or that this exact suggestion was already reviewed and dismissed in the app. ### list\_styleguides Lists all style guides in the workspace with their developer IDs, descriptions, sections, and rules. Use this to find a style guide's developer ID and section IDs before calling `create_styleguide_rules` or `update_styleguide`. ### create\_styleguide Create a new style guide (name, description, sections, whether it's enabled by default). Sections organize rules and word list entries; if omitted, a default set of sections is used. If you provide sections, each section ID must be unique and the list must include at least one rules section and one word list section. Returns the new style guide's developer ID. Pass `variantId` to scope the style guide to a single variant — because a locale in Ditto is a variant's locale code, this is how a style guide is associated with a locale. The variant must have a locale code — for now, style guides can only be scoped to variants that have one. ### update\_styleguide Update an existing style guide by developer ID (any field: name, description, enabled by default, variant, sections). Only the fields you provide are changed. Pass developer ID `default` to toggle the built-in Ditto Style Guide's enabled-by-default setting. To rename a style guide's developer ID, set `newDeveloperId` — it can be combined with other field changes in the same call, and they're applied together or not at all. The new developer ID must be unique among the workspace's style guides; `default` is reserved for the built-in Ditto Style Guide, whose developer ID can't be renamed. Pass `variantId` to scope the style guide to a single variant — because a locale in Ditto is a variant's locale code, this is how a style guide is associated with a locale — or `null` to unscope it so it applies to all text. The variant must have a locale code — for now, style guides can only be scoped to variants that have one. Updating sections replaces the full list — each section ID must be unique, and the list must include at least one rules section and one word list section — rules in a removed section move automatically to the first remaining rules section. ### create\_styleguide\_rules Create one or more rules in a style guide section. Use the `{name, description, examples, tags, enabled}` shape for a 'rules' section, or the `{term, disallowed, description, tags, enabled}` shape for a 'wordlist' section — the shape must match the target section's kind. Find the style guide's developer ID and section IDs with `list_styleguides`; only tags returned by `get_workspace_tags` can be used. Batch calls are all-or-nothing: if any rule fails, none are created. ### update\_styleguide\_rules Update one or more existing rules by ID (from `get_styleguide_rules` or `create_styleguide_rules`). Only the fields you provide are changed. Use `name`/`examples` for a rule in a 'rules' section, or `term`/`disallowed` for a rule in a 'wordlist' section — the shape must match that rule's own section. Batch calls are all-or-nothing: if any update fails, none are applied. ### delete\_styleguide\_rules Permanently delete one or more rules by ID. This cannot be undone. Batch calls are all-or-nothing: if any rule ID doesn't exist, none are deleted. ## Text items ### search\_ditto\_text Searches existing text items and/or library components by content and filters (status, tags, assignee, notes). Use it after drafting candidate text for UI to find existing text that could be reused instead of writing new copy: an exact or near-exact match means reuse the existing item by its developer ID; a partial match is worth reusing if the wording still fits. * Reads `ditto/config.yml` project IDs the same way as `get_styleguide_rules`. * Searches the base language by default; pass `variants` (variant developer IDs, optionally including `base`) to search translations. Use `list_variants` to look up IDs. * Pass several entries in `queries` to run independent searches in one call. Each query succeeds or fails on its own and returns its own `results`, with an `error` string instead of failing the whole call. * Results are paged per query: when more exist, the entry includes a `cursor` to pass back in a later call. * Results are trimmed to a default set of fields to keep responses small; request more with the `fields` parameter. Requesting `figma` adds the Figma text node IDs linked to each text item, both in the project's main Figma file and grouped by branch — useful for checking what a text item is already connected to before calling `link_figma_nodes_to_text_items`. ### create\_text\_items Creates new text items in a project. Provide the project's developer ID and the items to add. Each item supports text, an optional developer ID, status, tags, assignee (by email), notes, a character limit, a development-integration flag, and plural forms. Use `search_ditto_text` first to check whether equivalent text already exists to reuse. Set `ruleIds` on an item to record the style guide rules you applied to its text — see `get_styleguide_rules`. Variant text set at creation time is attributed alongside the base text. Batch calls are all-or-nothing: if any item in the call fails (e.g. a duplicate developer ID), none of the items are created. ### update\_text\_items Updates existing text items by their developer IDs. Only the fields you provide are changed; the rest are left as-is. Supports text, status, tags, assignee, notes, character limit, the development-integration flag, and plural updates. Tags are replaced wholesale — pass the full desired set. Set `ruleIds` on an update to record the style guide rules you applied to its text — see `get_styleguide_rules`. The call-wide `variantId` decides which text the rules are recorded against, so a variant update is attributed to that variant rather than to the base text. Batch calls are all-or-nothing: if any update in the call fails, none of the updates are applied. ### translate\_text\_items Translates project text items into a variant's locale and saves each translation as that variant's text. Provide the project's developer ID, the variant's developer ID (from `list_variants`), and the developer IDs of the text items to translate. The variant must have a locale code, which is targeted for translation. Rules from style guides scoped to that locale are applied. The source is each item's base text. Existing variant text is replaced; items that don't have the variant yet attach it, reported as `variantAttached`. If the variant has plural forms, the first one is updated too. Library text item instances can't be translated here — use `translate_library_components` on their component instead. Up to 100 text items per call. ### delete\_text\_items Permanently deletes text items by their developer IDs. Optionally provide a projectId to disambiguate instances of library components and base text items that appear in multiple projects. This cannot be undone. ### link\_figma\_nodes\_to\_text\_items Links already-synced Figma text nodes to existing Ditto text items. Pass `links`, a list of `{ textItemId, figmaNodeIds }` pairs mapping each text item's developer ID (from `search_ditto_text`) to the Figma node IDs that should link to it (read them from the file with the [Figma MCP](https://www.figma.com/mcp-catalog/)); `projectId` is the developer ID of the Ditto project connected to the Figma file. Pass `branchId` (from the branch file's Figma URL) to link nodes in a Figma branch instead of the main file. Every node in a call must be in that one branch, or in the main file when `branchId` is omitted. Up to 500 distinct node IDs per call across all links; repeated IDs count once. An unknown text item developer ID, or a node mapped to more than one text item, rejects the whole call. The tool only links nodes the Ditto Figma plugin has already synced. Each node gets an outcome of `linked`, `already_linked`, `linked_to_other_text_item` (left linked to the text item named in `linkedTextItemId`), `not_found` (sync the file or page in the plugin, then retry), `not_text_node`, `wrong_file_or_branch`, or `main_component_text_node`. The response returns a count per outcome and lists only the nodes that did not end up linked to their requested text item. Walkthrough: [Connect Figma files](/guides/connect-figma-files). ## Library components ### create\_library\_components Creates new components in the Ditto component library. Library components are reusable text shared across projects, as opposed to project-scoped text items. Each component supports a name, text, an optional developer ID, folder, status, tags, assignee (by email), notes, a character limit, a development-integration flag, and plural forms. Use `search_ditto_text` first to check whether equivalent text already exists to reuse. Set `ruleIds` on a component to record the style guide rules you applied to its text — see `get_styleguide_rules`. Variant text set at creation time is attributed alongside the base text. Batch calls are all-or-nothing: if any component in the call fails (e.g. a duplicate developer ID), none of the components are created. ### update\_library\_components Updates existing library components by their developer IDs. Only the fields you provide are changed; the rest are left as-is. Supports text, folder, status, tags, assignee, notes, character limit, the development-integration flag, and plural updates. Tags are replaced wholesale — pass the full desired set. Set `ruleIds` on an update to record the style guide rules you applied to its text — see `get_styleguide_rules`. The call-wide `variantId` decides which text the rules are recorded against, so a variant update is attributed to that variant rather than to the base text. Batch calls are all-or-nothing: if any update in the call fails, none of the updates are applied. Each component can appear only once per call — a repeated developer ID is rejected. ### translate\_library\_components Translates library components into a variant's locale and saves each translation as that variant's text on the component. Provide the variant's developer ID (from `list_variants`) and the developer IDs of the components to translate. The variant must have a locale code, which is targeted for translation. Rules from style guides scoped to that locale are applied. The source is each component's base text. Existing variant text is replaced; components that don't have the variant yet attach it, reported as `variantAttached`. If the variant has plural forms, the first one is updated too. The translation edit is propagated to text item instances of the component. Up to 100 components per call. ### publish\_library\_component Publishes an existing project text item to the Ditto component library, turning it into a reusable library component. The text item's current text, variables, variants, and other fields are carried over to the new component, and the text item becomes its first linked instance. The new component keeps the text item's existing developer ID, and any text item later linked to it takes on that same developer ID, so one developer ID can end up shared across the component and every text item linked to it. In workspaces with [component versioning](/concepts#component-versioning), the new component instead gets a new developer ID generated from its name, and the text item, like any text item later linked to it, keeps its own. The text item must not already be a library instance — use `link_text_items_to_component` to attach additional items to an existing component instead. Pass `folderId` (from `list_component_folders`) to file the component in a folder; otherwise it lands at the library root. ### link\_text\_items\_to\_component Links one or more existing project text items to an existing library component by developer ID, so they share the component's text going forward. Each linked text item takes on the component's current text, variables, variants, and other fields, including its developer ID: the text item's own developer ID is replaced by the component's, so it can no longer be looked up by the old one. Text items already linked to a different component are skipped. Use this after a scan to attach design or project text to the library components created from your codebase; see [Connect Figma files](/guides/connect-figma-files). ### unlink\_text\_items\_from\_component Unlinks one or more text items from their library component by developer ID, turning them back into standalone project text items with a freshly generated developer ID each. The text items keep their current text; future edits to the library component no longer affect them. Every text item linked to the same component shares that component's developer ID with its sibling instances, so a given developer ID unlinks every text item that currently carries it, not just one. Pass `projectId` to scope the unlink to only the instances in one project, leaving instances elsewhere untouched. ## Component folders ### list\_component\_folders Lists all folders in your component library with their names, developer IDs, and parent folder (by developer ID; no parent means the folder sits directly under the library root). Use this to find a folder's developer ID before filing a component into it with `create_library_components` or `update_library_components`, or before managing the folders themselves with `create_component_folders`, `update_component_folders`, and `delete_component_folders`. Folders are a separate namespace from projects — `list_projects` will not surface them. ### create\_component\_folders Creates new folders in the component library. Each folder can optionally specify a developer ID (must be unique in the workspace) and a parent folder to nest inside — omit the parent to create the folder at the library root. Batch calls are all-or-nothing: if any folder in the call fails (e.g. a duplicate developer ID), none of the folders are created. ### update\_component\_folders Updates existing folders by their developer IDs. Only the fields you provide are changed. Set `parentId` to move a folder — provide `null` or `"root"` to move it to the library root, or omit to leave it in place. To rename a folder's developer ID, set `newDeveloperId`. Batch calls are all-or-nothing: if any update in the call fails (e.g. a duplicate developer ID, or a move that would create a circular hierarchy), none of the updates are applied. ### delete\_component\_folders Permanently deletes folders from the component library by their developer IDs. This cannot be undone. Components and subfolders inside a deleted folder are not deleted — they're moved up into the deleted folder's parent folder (or the library root). ## Projects ### list\_projects Lists all projects in your workspace with their names and developer IDs. Use this to find a project's developer ID when you only know its name — for example, before creating, updating, or searching text items. ### list\_project\_folders Lists all project folders in the workspace with their developer IDs, names, and parent folder (by developer ID; no parent means the folder sits directly under the workspace root). Use this to find a folder's developer ID before `create_ditto_project`, which takes a folderId to create the project inside a folder. Folders are a separate namespace from projects — `list_projects` will not surface them. ### create\_ditto\_project Creates a new project in the workspace. Projects hold project-scoped text items, as opposed to library components which are reusable across projects. Use the returned developer ID (or `list_projects`) to reference it afterward, for example with `create_text_items`. ### update\_project Updates an existing project by its Developer ID (find them with `list_projects`). Supports updating the project's name, developer ID, and/or connected base. Returns the updated project. ## Project blocks ### list\_project\_blocks Lists the blocks in a Ditto project with their developer IDs and names, in display order. Blocks group text items within a project. Use this to find a block's developer ID before `update_project_blocks` or `delete_project_blocks`. If you only know the project's name, call `list_projects` to look up its developer ID. ### create\_project\_blocks Creates new blocks in a Ditto project. Blocks group text items within a project. New blocks are appended to the end of the project. Use the returned developer IDs (or `list_project_blocks`) to reference them afterward. ### update\_project\_blocks Updates existing blocks in a Ditto project by their developer IDs (find them with `list_project_blocks`). Only the fields you provide are changed. To rename a block's developer ID, set `newDeveloperId` — it can be combined with a name change in the same update object. Batch calls are all-or-nothing: if any update fails (e.g. a duplicate developer ID within the project), none of the updates in the call are applied. ### delete\_project\_blocks Permanently deletes blocks from a Ditto project by their developer IDs (find them with `list_project_blocks`). This cannot be undone. Text items in a deleted block are not deleted — they're unassigned and moved to the end of the project's root level. ## Variables ### list\_variables Lists all variables in the workspace with their developer IDs, types, and data. Use this to find a variable's developer ID (its name) before referencing it in `create_text_items` or `update_text_items` — list the id in the `variables` field and reference it as a `{{variable_name}}` placeholder in the text. ### create\_variables Creates variables in the workspace. A variable is a reusable placeholder that resolves to a different value at runtime. Each has a type that determines what it holds: a `string` or `number` variable holds an example and an optional fallback, a `hyperlink` holds link text and a url, a `list` holds the set of values the variable can take, and a `map` holds key-value pairs. A variable's name doubles as its developer ID: letters, numbers, and underscores only, and unique in the workspace. Creating a variable adds it to no text on its own — reference it from `create_text_items` or `update_text_items` by listing it in the `variables` field and writing it as a `{{variable_name}}` placeholder in the text. Batch calls are all-or-nothing: if any variable in the call fails (for example, a name already in use), none of them are created. ### update\_variables Renames variables or changes their type or value. Because a variable's name is its developer ID, renaming one with `newName` changes how it is referenced — but nothing inside Ditto is orphaned, because every `{{placeholder}}` using it is rewritten to match, across text items, library components, variant text, and plurals. What breaks is outside Ditto: anything naming the old id, such as code, an integration config, or CLI output, along with later API and MCP calls. Use `list_variables` to find a variable's current developer ID. Only the fields you provide are changed, and each update must change at least one of `newName`, `type`, or `data`. Changing a type requires sending `data` alongside it, even when the value itself stays the same. How `data` is applied depends on the type: string, number, and hyperlink variables have fixed fields, so sending only some of them leaves the rest alone — send an empty string to clear one. A list's value is an array and a map's is an object, and each is the variable's entire content, so what you send replaces it outright. Batch calls are all-or-nothing: if any update in the call is invalid, none of the updates are applied. ### delete\_variables Permanently deletes variables from the workspace by their developer IDs (their names). This cannot be undone. A variable cannot be deleted while any text item or library component still references it, and one such variable rejects the whole batch — remove the `{{placeholder}}` from that text first, then retry. Confirm the developer IDs with the user before calling. ## Variants ### list\_variants Lists all variants in the workspace with their developer IDs, names, descriptions, and locale codes. Use this to find a variant's developer ID when you only know its name — for example, before `create_text_items` or `update_text_items`, which take variant developer IDs to set variant-specific text. When translating text for a variant, treat its localeCode (e.g. `es`, `fr-ca`) as the authoritative target locale — do not infer the locale from the variant's name or description. If a variant's localeCode is null, it is not locale-scoped; confirm the intended locale with the user rather than guessing. ### create\_variants Creates variants in the workspace. A variant is a named alternative that text items and library components can each have their own text for — most often a locale to translate into, but also a tone or brand variation. Creating a variant adds no text on its own: use `create_text_items` or `update_text_items` with the variant's developer ID to set a text item's text for that variant. Set localeCode whenever the variant represents a language, because translation tools treat it as the authoritative target locale rather than inferring one from the name; at most one variant per workspace can hold a given locale code. A developer ID is generated from the name unless you pass one explicitly, so read the developer IDs off the response rather than assuming them. ### update\_variants Renames variants or changes their description, locale code, or developer ID. A variant's name and its developer ID are independent. The name is the human-readable label shown in Ditto; the developer ID is the stable key that code, integrations, and CLI output use to refer to the variant. Renaming a variant leaves its developer ID untouched, so anything already referencing it keeps resolving — set newDeveloperId only when you want that key itself to change. Nothing inside Ditto is orphaned or rewritten by either change: text items and library components reference a variant by an internal id rather than its developer ID, so each keeps its text for the variant, and variant-scoped style guides stay attached. What breaks is outside Ditto — anything naming the old developer ID, such as CLI output, an integration config, or code, along with later API and MCP calls, which have to use the new one. Use `list_variants` to find a variant's current developer ID. At most one variant per workspace can hold a given locale code. Changing a locale code reaches past the variant itself: variant-scoped style guides and AI translation targeting both resolve a locale to a variant, so moving a locale onto a different variant changes which style guide governs that locale's text and where translated text is suggested. The whole batch is rejected if any update is invalid. ### delete\_variants Permanently deletes variants from the workspace by their developer IDs. This cannot be undone. A variant cannot be deleted while any text item or library component still has text for it, and one such variant rejects the whole batch — remove that text first, then retry. Style guides scoped to a deleted variant survive but stop being variant-scoped. ## Workspace ### list\_statuses Lists the statuses which text items and library components can be set to in your workspace, in workflow order — the order your workspace moves text through, from earliest (a first draft) to latest (ready to ship). A workspace can rename the default statuses or replace the set entirely, so none of the defaults are guaranteed to exist. Call this before setting or filtering by a status rather than assuming a particular key is valid. Statuses are set by key, not by the display name shown in the app. ### get\_workspace\_tags Returns all tags currently in use across the workspace. Useful before adding tags to a rule — only existing tags are allowed. # Android XML string format Source: https://developer.dittowords.com/string-formats/android The strings.xml resource format Ditto exports for Android via the API and CLI, including xliff variables, plurals, and variant metadata. String formats are how Ditto hands text to code. The CLI writes these files on `pull` according to the `outputs` in your [config](/cli-reference/configuration#outputs), and the API returns the same formats from the export endpoints ([`GET /v2/textItems/export`](/api-reference/text-items/export-text-items) and [`GET /v2/components/export`](/api-reference/components/export-components)). Every format can be fetched for the base text or for a single variant; [Set up localization](/guides/set-up-localization) covers how locales map to files. Ditto allows Android developers to fetch the latest text as pre-formatted XML resources (`.xml`) in accordance with [Android strings](https://developer.android.com/guide/topics/resources/string-resource) and [Android localization](https://developer.android.com/guide/topics/resources/localization) guidance. If you're fetching this format via the API, it will have an HTTP `Content-Type` header of `text/xml`. **Example:** ```xml theme={null} Welcome to the app! Logout ``` **Instructions:** To fetch this via the **API**, provide the query parameter `?format=android`. To fetch this via the **CLI**, specify `format: android` in the `config.yml` file. **Format details:** * Text items in Ditto are mapped to `` tags. Each tag is given a `name` property with a value that corresponds to a given text item's Developer ID; to comply with Android requirements, `-` characters in the ID are replaced with `_`. An additional `ditto_api_id` field is also included with the unmodified Developer ID. * Variables in Ditto are mapped to `` tags. They're given `id` and `example` properties with values that correspond to a given variable's name and example values, respectively. * A text item with plurals will have a `` tag generated for it, along with an `` for each plural form that is configured in Ditto (note: `` will always be generated). Plural items also support variables via `` tags. * If this format is exported for a specific variant, two comments containing variant metadata will also be included: ```xml theme={null} Welcome to the app! Logout ``` # iOS string formats Source: https://developer.dittowords.com/string-formats/ios The .strings and .stringsdict formats Ditto exports for iOS via the API and CLI, including variable placeholders, plurals, and variant metadata. String formats are how Ditto hands text to code. The CLI writes these files on `pull` according to the `outputs` in your [config](/cli-reference/configuration#outputs), and the API returns the same formats from the export endpoints ([`GET /v2/textItems/export`](/api-reference/text-items/export-text-items) and [`GET /v2/components/export`](/api-reference/components/export-components)). Every format can be fetched for the base text or for a single variant; [Set up localization](/guides/set-up-localization) covers how locales map to files. ## Strings The `.strings` format is used to surface text in iOS applications. If you're fetching this format via the API, it will have an HTTP `Content-Type` header of `text/plain`. **Example:** ```swift theme={null} "welcome.title" = "Welcome, %1$@!"; /* This should only be used on the logout screen */ "logout" = "Logout"; ``` **Instructions:** To fetch this via the **API**, provide the query parameter `?format=ios-strings`. To fetch this via the **CLI**, specify `format: ios-strings` in the `config.yml` file. **Format details:** * Ditto Variables are replaced with interpolation placeholders **with a string (@) format specifier**; in your client code, you'll need to ensure that all variables passed as arguments are first converted into strings. * Notes associated with text items or components will be included as comments above a given key-value pair. * To use string plural forms, you will need to use the `.stringsdict` format ([see below](#stringsdict)) in conjunction with the `.strings` format. * If this format is exported for a specific variant, two comments containing variant metadata will also be included: ```swift theme={null} /* Variant Name: French */ /* Variant Description: Copy localized for native French speakers */ "welcome.title" = "Welcome, %1$@!"; ... ``` ## StringsDict The `.stringsdict` format is used to surface plural form text in iOS applications; text without pluralization enabled in Ditto is excluded from output. For integrating strings that are not pluralized, you should use `.stringsdict` in conjunction with iOS `.strings`. If you're fetching this format via the API, it will have an HTTP `Content-Type` header of `text/xml`. **Example:** ```xml theme={null} cart.header NSStringLocalizedFormatKey %1$#@count@ count NSStringFormatSpecTypeKey NSStringPluralRuleType NSStringFormatValueTypeKey d one %3$@, you have one item in the cart. two %3$@, you have a couple of items in the cart. other %3$@, you have %2$@ items in the cart. ``` **Instructions:** To fetch this via the **API**, provide the query parameter `?format=ios-stringsdict`. To fetch this via the **CLI**, specify `format: ios-stringsdict` in the `config.yml` file. **Format details:** The order in which variables are provided as arguments in Swift is relevant to the output. The following code could be used with the example output above: ```swift theme={null} let localizedString = String.localizedStringWithFormat( NSLocalizedString( "cart", comment: "A description of the user's cart" ), // The first argument should always be an integer to determine which plural // form to use pluralcount, // Subsequent arguments should be provided in alphabetical order according // to the names of the Ditto Variables used in a given text item String(pluralcount), username, ) ``` * A comment structured like `` is included above each key in the `.stringsdict` file to indicate the order in which variables should be passed (this will always be alphabetical). * All Ditto Variables are replaced with interpolation placeholders **with a string (@) format specifier** -- in your client code, you'll need to ensure that all variables passed as arguments are first converted into strings. * If this format is exported for a specific variant, two comments containing variant metadata will also be included: ```xml theme={null} ... ``` ## Integrating `.strings` and `.stringsdict` files The Ditto CLI includes special behavior for iOS environments designed to streamline the process of integrating Ditto with iOS projects. It can automatically package string files into bundles that are ready to import into Xcode, as well as generate Swift code to make string consumption type-safe. See the [iOS](/cli-reference/files#ios-strings) section of the [CLI Files](/cli-reference/files) page to learn more about the CLI's iOS-specific behavior, or check out the [CLI Installation](/cli-reference/authentication) page to get started. # JSON Source: https://developer.dittowords.com/string-formats/json JSON string formats Ditto exports via the API and CLI: flat, structured, nested, ICU, and ARB, with plurals, variables, and variant metadata. String formats are how Ditto hands text to code. The CLI writes these files on `pull` according to the `outputs` in your [config](/cli-reference/configuration#outputs), and the API returns the same formats from the export endpoints ([`GET /v2/textItems/export`](/api-reference/text-items/export-text-items) and [`GET /v2/components/export`](/api-reference/components/export-components)). Every format can be fetched for the base text or for a single variant; [Set up localization](/guides/set-up-localization) covers how locales map to files. [JSON (Javascript Object Notation)](https://www.json.org/json-en.html) is a standard file format that allows you to map data in key-value pairs. You can fetch text from Ditto in several JSON string file formats: **flat, structured, nested, ICU, and ARB**. As this format is framework-agnostic, JSON files are frequently used to store strings in web development. If you're fetching this format via the API, all JSON formats have a HTTP `Content-Type` header of `application/json`. ## Flat JSON This format provides a direct mapping from string IDs to strings, without additional metadata. **Example:** ```json theme={null} { "[TEXT ID]": "[TEXT OF TEXT ITEM]", "[TEXT ID]": "[TEXT OF TEXT ITEM]", // Text items with plural values will have those plural values // included in the output as individual key/value pairs. // Plural forms: zero, one, two, few, many, other "[TEXT ID]_[PLURAL FORM]": "[TEXT OF PLURAL FORM]", ... } ``` **Instructions:** To fetch this via the **API**, provide the query parameter `?format=flat`. To fetch this via the **CLI**, specify `format: flat` in the `config.yml` file. ## Structured JSON This format maps string IDs to an object of each text item's metadata. This format is recommended if metadata fields are used directly in development. The `text` field will always be included, with additional fields based on those included on the text item: * `status` * `notes` * `variables` * `plurals` * `variants` * `rich_text` **Example:** ```json theme={null} { "[TEXT ID]": { "text": "[TEXT OF TEXT ITEM]", "status": "[STATUS OF TEXT ITEM]", // optional "notes": "[NOTES OF TEXT ITEM]", // optional "tags": [ ... ], // optional: array of strings "variables": { // optional "[VARIABLE NAME]": { ... // variable properties: example, fallback, etc } }, "plurals": { // optional: only if plurals are enabled for text // Plural forms specified are listed in object // Plural forms: zero, one, two, few, many, other "[PLURAL FORM]": "[TEXT FOR PLURAL FORM]", ... }, "variants": { // optional: only present if text has variant values and not a single-variant export "[VARIANT ID]": { "text": "[TEXT OF VARIANT]", "variables": { ... }, // see above `variables` property "plurals": { ... } // see above `plurals` property }, }, // Only included when `?includeRichText=true` is passed (API) or // `richText: html` is set (CLI). Rich text is the text item's // formatting (bold, italic, links, lists) rendered as HTML. "rich_text": "[HTML CONTENT OF TEXT ITEM]" }, "[TEXT ID]": { ... }, ... } ``` **Instructions:** To fetch this via the **API**, provide the query parameter `?format=structured`. To fetch this via the **CLI**, specify `format: structured` in the `config.yml` file. ## Nested JSON > [!NOTE] > > **Note:** This format is only available when fetching components (not projects). This format nests the JSON output based on nesting in the component library. In Ditto, library component names can use forward slashes to express hierarchy (for example `onboarding/header/title`), mirroring the folders and groups they are organized into in the component library. When a developer ID is generated from such a name, the workspace's [Developer ID configuration](/concepts#configuration) replaces the `/` with a delimiter you choose (the **character replacement** rule for `/`). In the nested JSON format, component IDs are split on that delimiter, and each part becomes a level of depth in the output. **Example:** ```json theme={null} { "onboarding": { "header": { "title": "Welcome!", "subtitle": "We're happy to have you here" }, "body": { "CTA": "Let's get started" } }, "authentication": { "new-user-cta": "Create an account", "existing-user-cta": "Sign in" }, "toast": "Thanks for signing up!" } ``` Assuming a `/` replacement character of `.` in the component ID settings, the JSON above would be generated from the following component IDs: ```text theme={null} onboarding.header.title onboarding.header.subtitle onboarding.body.CTA authentication.new-user-cta authentication.existing-user-cta toast ``` **Instructions:** To fetch this via the **API**, provide the query parameter `?format=nested`. To fetch this via the **CLI**, specify `format: nested` in the `config.yml` file. ## ICU JSON This format provides a mapping from string IDs to strings in accordance with the [ICU message format](https://icu.unicode.org/home). **Example:** ```json theme={null} { "onboarding.header.title": "Welcome, {username}!", "onboarding.header.subtitle": "We''re happy to have you here", "authentication.message": "You are {role, select, admin {an administrator} viewer {a viewer} other {currently logged in}}.", "cart.count": "{count, plural, =0 {No items in the cart} zero {No items in the cart} =1 {One item in the cart} one {One item in the cart} other {{cartCount} items in the cart}}" } ``` * A Map or List variable in Ditto translates to an ICU SelectFormat argument, with the variable's first value used as the "other" key. * Single quotes (`'`) will be escaped with another single quote (i.e "You're logged in" -> "You''re logged in") * Pluralization in Ditto translates to an ICU PluralFormat argument: * The PluralFormat, if present, will always encompass the rest of the string, within which variables can be nested. * The name of the argument will always be `count` (i.e. `{count, plural, ...}`) * `zero`, `one`, and `two` plural forms are each output twice: once as an exact-value selector (`=0`, `=1`, `=2`) and once as the matching CLDR category keyword (`zero`, `one`, `two`), both mapped to the same text. This is intentional, not a duplicate to clean up — some locales (e.g. English) have no `zero` category at all, so without the `=0` selector that text would never display for a literal 0. Other locales' categories cover more than the literal number (e.g. Russian's `one` category also applies to 21, 31, 101, ...), so the keyword selector is needed too. **Instructions:** To fetch this via the **API**, provide the query parameter `?format=json_icu`. To fetch this via the **CLI**, specify `framework: icu` alongside `format: json` in the `config.yml` file. See [Frameworks](/cli-reference/files#framework-specific-files) for more details. ## ARB JSON This format provides a mapping from string IDs to strings in the [ARB (Application Resource Bundle)](https://github.com/google/app-resource-bundle/wiki/ApplicationResourceBundleSpecification) format, commonly used by Flutter's `intl` package for localization. Message text uses the same [ICU message format](https://icu.unicode.org/home) syntax as ICU JSON above (variable interpolation, pluralization, and Map/List SelectFormat arguments all work identically). In addition: * If a text item has notes, they're included as a `description` field in that item's `@`-prefixed metadata entry, to give translators context. * If a text item has variables, they're included as a `placeholders` field in that item's `@`-prefixed metadata entry, mapping each variable name to its ARB type (`num` for number variables, `String` for all other variable types). * If the export has a known base or variant locale, it's included as a top-level `@@locale` key. **Example:** ```json theme={null} { "@@locale": "en", "onboarding.header.title": "Welcome, {username}!", "@onboarding.header.title": { "placeholders": { "username": { "type": "String" } } }, "cart.count": "{count, plural, =0 {No items in the cart} =1 {One item in the cart} other {{count} items in the cart}}", "@cart.count": { "description": "Shown at the top of the shopping cart page", "placeholders": { "count": { "type": "num" } } } } ``` **Instructions:** To fetch this via the **API**, provide the query parameter `?format=arb`. To fetch this via the **CLI**, specify `framework: arb` alongside `format: json` in the `config.yml` file. See [Frameworks](/cli-reference/files#framework-specific-files) for more details. > [!WARNING] > > `@@locale` is the **only** thing identifying an ARB file's language. Ditto file names follow the [standard `{project_id}___{variant_id}` convention](/cli-reference/files#string-file-naming-conventions) (e.g. `my-project___base.arb`), not Flutter's own `app_.arb` convention — so a file without `@@locale` carries no language information at all, and tools like `intl_utils`/`gen-l10n` won't be able to place it correctly. > > For a base (non-variant) export, `@@locale` comes from the project's base locale — set under **Variant & locale settings** in the app. This is unset (`null`) by default, so `@@locale` won't appear until it's configured there. > > For a variant export, `@@locale` comes from that variant's own locale code, set the same way. Configure these before relying on the `arb` format or framework. ## Variant-specific exports For each of these JSON formats, you can choose to fetch the text of a single variant. For flat, structured, nested, and ICU JSON, variant-specific exports also include two metadata keys for the variant: ```json theme={null} { "__variant-name": "French", "__variant-description": "Copy localized for native French speakers", ... } ``` > [!NOTE] > > ARB exports don't include `__variant-name`/`__variant-description`. Unlike the other formats, ARB is consumed directly by third-party tooling (e.g. Flutter's `intl_utils`/`gen-l10n`), which treats any non-`@`-prefixed top-level key as a translatable message — so these keys would leak into a generated app as fake strings. Use the variant's locale, included as `@@locale`, to identify an ARB export's variant instead.