# Hello, developer

Frontitude is a collaborative platform for managing UX content from design to development. This is the place where you'll find out how to automate the UX content operations between design and development teams, in localization processes, and more.

## Why to use Frontitude?

* **It creates a single source of truth for UX content.** The real source of truth for UX content is obviously in the code, but it's isolated and inaccessible for the rest of the team and in many cases hard to manage. Frontitude offers a customized source of truth that is built specifically for managing UX content.
* **It seamlessly integrates with any process.** Not only Frontitude is built for UX content, it connects with design, development, and localization tools, to smoothly sync content changes across the board.
* **It makes handoffs a breeze.** So many tickets, copy-pastes, and spreadsheets are used just for communicating a few content changes, siloed from the code and design files. With Frontitude, everyone touches the same content in different tools, which allows you to sync any tool (including your codebase) with the latest content in a click or command.

## End-to-end demo

{% embed url="<https://youtu.be/SB0Lzd-U-0k?si=kqTEvOc_9mbGamDX>" %}
Frontitude's Developer CLI demo
{% endembed %}

## What's inside?

We offer different developer tools for different use cases. Zoom-in to get further details on any of them here:

{% content-ref url="/pages/VuQeZJOsozD3YlXjZOeS" %}
[Developer CLI](/developer-cli/installation)
{% endcontent-ref %}

{% content-ref url="/pages/wrvFQhyvhugB5HS5ciPX" %}
[Webhooks](/webhooks/introduction)
{% endcontent-ref %}


# Quick start

{% hint style="info" %}
Frontitude's Developer CLI can be installed and tested on our [Starter plan](https://www.frontitude.com/pricing), which is completely free!
{% endhint %}

## Step 1: Get content into Frontitude

There are two ways to get content into Frontitude to work with.

**Pull-first (design-led):** create content in Frontitude first, then pull it into your codebase.

* [Import content from JSON files](https://www.frontitude.com/guides/import-copy-components-from-json-files)
* [Import content from design tools](https://www.frontitude.com/guides/upload-content-to-frontitude)
* Manually create [copy components](https://www.frontitude.com/guides/copy-components) in the copy library

**Push-first (code-led):** write new copy components directly in your codebase and push them up to your Frontitude copy library. Great for AI-assisted development where a coding assistant generates new UI strings inline with your code. See [Step 6](#step-6-push-new-keys-from-your-code) below.

## Step 2: Install the Developer CLI

Install the Developer CLI globally via NPM or Yarn:

{% tabs %}
{% tab title="NPM" %}

```bash
npm install -g @frontitude/cli
```

{% endtab %}

{% tab title="Yarn" %}

```bash
yarn global add @frontitude/cli
```

{% endtab %}
{% endtabs %}

## Step 3: Get access to Frontitude

{% hint style="warning" %}
Make sure the Developer CLI integration is turned on in your [workspace settings](https://app.frontitude.com/settings/integrations).
{% endhint %}

### Use an access token

[Create an access token](/developer-cli/authentication#workspace-level-access-token) to use it in the next step. This is more flexible since it allows you to use the Developer CLI on remote systems such as CI/CD pipelines or any kind of Bash scripts.

## Step 4: Initialize the Developer CLI

To initialize the CLI, run the next command and follow the instructions:

```bash
$ frontitude init
```

## Step 5: Pull content from Frontitude

To pull the latest content from your connected sources:

```bash
$ frontitude pull
```

This will fetch all of your content from the library/project into your codebase. To learn about the additional options of this command, please refer to [this section](/developer-cli/commands#pull).

## Step 6: Push new keys from your code

If you've added new strings to your codebase (for example, because you built a new feature), you can push those keys up to Frontitude in one command:

```bash
$ frontitude push
```

The CLI will show you a preview of what would be created and ask for confirmation. Only components with new unique keys are created; anything that already exists in Frontitude is left untouched.

To see what would be pushed without making any changes:

```bash
$ frontitude push --dry-run
```

See the [full push options](/developer-cli/commands#push) for tagging, messaging, and CI usage.

## That's it!

You now have the full round-trip: pull content from Frontitude into your codebase, and push new keys from your codebase back to Frontitude. Happy coding!


# Installation

You can install the Developer CLI globally via NPM or Yarn:

{% tabs %}
{% tab title="NPM" %}

```bash
npm install -g @frontitude/cli
```

{% endtab %}

{% tab title="Yarn" %}

```bash
yarn global add @frontitude/cli
```

{% endtab %}
{% endtabs %}

Or locally:

{% tabs %}
{% tab title="NPM" %}

```bash
npm install --save-dev @frontitude/cli
```

{% endtab %}

{% tab title="Yarn" %}

```bash
yarn add --dev @frontitude/cli
```

{% endtab %}
{% endtabs %}

{% hint style="info" %}
After installation, you can run the Frontitude CLI using the `frontitude` command, or using the `fttd` alias.
{% endhint %}


# Authentication

To connect the CLI with your Frontitude account, you will need to provide a valid authentication.

Choose one of the next options according to your use case:

1. Personal access token (OAuth 2.0 authentication using Google/Microsoft Azure/SAML SSO) - Use this option when you want to try the CLI or pull copy changes manually.
2. Workspace-level access token - Use this option when you want to use the CLI in your CI/CD pipeline.

## Workspace-level access token

{% hint style="info" %}
If you don't have access to your team's Frontitude workspace, you can ask one of your teammates to generate the access token and send it to you. Make sure they send it over secure channels!
{% endhint %}

Create your workspace-level access token from [your account settings](https://app.frontitude.com/settings/integrations?utm_source=dev-cli-docs), under the Developer CLI integration. Once created, continue to run the next commands in the CLI.

{% embed url="<https://frontitude-public.s3.eu-west-1.amazonaws.com/website/cli-access-token.mp4>" fullWidth="false" %}
Creating a workspace-level access token in Frontitude
{% endembed %}

To set your workspace-level access token in the CLI:

```bash
$ frontitude config set access-token <your-token>
```

To remove your workspace-level access token:

```bash
$ frontitude config del access-token
```

## Personal access token (browser sign-in)

{% hint style="info" %}
When using this option, make sure that you were invited to your team's Frontitude workspace. Otherwise you won't have access to the team's content.
{% endhint %}

Frontitude CLI supports OAuth 2.0 authentication (using Google/Microsoft Azure/SAML SSO) via your default browser to automatically generate your personal access token.

To initiate the authentication process:

```bash
$ frontitude login
```

To remove your generated personal access token:

```bash
$ frontitude logout
```


# Configuration file

## Overview

The Frontitude CLI uses a local configuration file called `frontituderc.json` to determine which text sources to pull from your Frontitude workspace and where to write the output.

> **Note:** This file is managed by the Frontitude CLI. While you can edit it manually, it's recommended to run `frontitude init`, or `frontitude source set` if you’ve already completed `init`, to updated it.

## File location

`frontituderc.json` is created at the root of your project directory. It is safe — and recommended — to commit this file to version control so that your team shares the same CLI configuration.

## Properties

| Property       | Type      | Required | Description                                        |
| -------------- | --------- | -------- | -------------------------------------------------- |
| `filePath`     | `string`  | Yes      | Relative path to the output strings file.          |
| `library`      | `boolean` | No       | Whether to include texts from the Copy Library.    |
| `projects`     | `array`   | No       | Frontitude projects to pull texts from.            |
| `folders`      | `array`   | No       | Frontitude workspace folders to pull texts from.   |
| `xliffVersion` | `string`  | No       | XLIFF version when using `.xlf` / `.xliff` output. |

> At least one text source (`library`, `projects`, or `folders`) must be configured for `frontitude pull` to work.

#### `filePath`

**Type:** `string`

The relative path where the CLI writes the pulled texts (e.g. `./strings.json`). The file extension determines the output format:

| Extension  | Format        |
| ---------- | ------------- |
| `.json`    | JSON          |
| `.xml`     | Android XML   |
| `.xlf`     | XLIFF         |
| `.xliff`   | XLIFF         |
| `.strings` | Apple Strings |

**Default:** `./strings.json`

#### `library`

**Type:** `boolean`

Set to `true` to include texts from your workspace's Copy Library as a source. When `false` or omitted, the Copy Library is not included.

#### `projects`

**Type:** `Array<{ name: string, id: string }>`

A list of Frontitude projects to pull texts from. Each entry contains:

* `name` — The display name of the project. Stored for readability so you can identify entries without looking up UUIDs. Not sent to the API.
* `id` — The unique project identifier used by the CLI to fetch texts.

These values are populated automatically by `frontitude init` or `frontitude source set` — you should not need to look up or edit IDs manually. If a project is renamed in Frontitude, the `name` in the config will be stale until you re-run `frontitude source set`.

#### `folders`

**Type:** `Array<{ name: string, id: string }>`

A list of workspace folders to pull texts from. When a folder is selected, all projects inside that folder are included at pull time. Each entry contains:

* `name` — The display name of the folder. Stored for readability only and not sent to the API.
* `id` — The unique folder identifier used by the CLI to resolve projects at pull time.

As with projects, if a folder is renamed in Frontitude the `name` here will be stale until you re-run `frontitude source set`.

#### `xliffVersion`

**Type:** `string` — `"1.2"` or `"2.0"`

Only relevant when `filePath` uses a `.xlf` or `.xliff` extension. Specifies which version of the XLIFF standard to use for the output file. Defaults to `"1.2"` if not set.

## Examples

#### Full example

```json
{
  "filePath": "./locales/strings.json",
  "library": true,
  "projects": [
    {
      "name": "Authentication feature",
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }
  ],
  "folders": [
    {
      "name": "Mobile",
      "id": "79a64273-69c3-40bb-8a6a-5e405981707c"
    }
  ]
}
```

#### Minimal example

```json
{
  "filePath": "./strings.json",
  "library": false,
  "projects": [
    {
      "name": "My Project",
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }
  ]
}
```

#### XLIFF output

```json
{
  "filePath": "./translations/strings.xlf",
  "xliffVersion": "1.2",
  "library": false,
  "projects": [
    {
      "name": "My Project",
      "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
    }
  ]
}
```

## How it's generated

Running `frontitude init` walks you through an interactive setup:

1. **Select sources:** pick which projects, folders, or the Copy Library to pull texts from.
2. **Set output path:** choose the file path and format for the pulled strings.

The CLI writes your selections to `frontituderc.json`. To update your sources later without re-running the full init flow, use `frontitude source set`.

## Working with multiple config files

{% hint style="info" %}
Available from CLI version 1.4.0 and above.
{% endhint %}

By default the CLI reads `frontituderc.json` from the directory you run it in. The `--config <path>` flag points it at a different config file instead. The path can be absolute or relative, and the flag works on every command (`pull`, `push`, `init`, and so on).

Relative `filePath` values inside the config file are resolved against that file's own directory, so `--config` behaves exactly as if you had changed into that directory before running the CLI. Each invocation uses exactly one config file; to drive several, run the CLI once per file and let an npm script handle the orchestration.

### Monorepo: one install, many packages

Install the CLI once at the repository root and give each package its own config file (`packages/<name>/frontituderc.json`). A root npm script can then pull or push every package in one command:

```json
{
  "scripts": {
    "i18n:pull:web": "frontitude pull --config packages/web/frontituderc.json",
    "i18n:pull:mobile": "frontitude pull --config packages/mobile/frontituderc.json",
    "i18n:pull:admin": "frontitude pull --config packages/admin/frontituderc.json",
    "i18n:pull": "npm run i18n:pull:web && npm run i18n:pull:mobile && npm run i18n:pull:admin"
  }
}
```

### Multiple config profiles in one project

The same mechanism lets a single project keep several strings files side by side — for example a workspace-level Copy Library alongside a project-scoped file, or one file per project. Create a config file per profile and point `--config` at each in turn:

```json
{
  "scripts": {
    "i18n:pull:library": "frontitude pull --config frontituderc.library.json",
    "i18n:pull:app": "frontitude pull --config frontituderc.app.json",
    "i18n:pull": "npm run i18n:pull:library && npm run i18n:pull:app"
  }
}
```

Config files in the same directory are fully independent — they can target different projects, the Copy Library, or different output files without interfering with each other.

> **Note:** To create a new config file at a specific path, run `frontitude init --config path/to/frontituderc.json`.


# Commands

## Initialization

To get started in no time, the `init` command will walk you through the basic commands to help you set the string sources that you would like to connect to your codebase, and the output file location (existing or a newly created one).

To initialize the CLI, run the next command and follow the instructions:

```bash
$ frontitude init
```

## Connect to data source

Connecting string sources (Frontitude **projects**, **project folders**, or the **copy library**) to your codebase will let you to pull their latest copy into your codebase and stay up to date effortlessly.

To set the sources that you would like to connect to your codebase, run the next command and use the checkbox selection:

```bash
$ frontitude source set
```

To view the list of connected sources:

```bash
$ frontitude source list
```

## Pull

After setting the sources that you would like to connect to your codebase, you will be able to pull their latest copy directly to the configured output file, that later can be consumed in your codebase using any i18n framework of your choice (e.g. [i18next](https://www.i18next.com/), [react-i18next](https://react.i18next.com/), etc.), or directly (using require/import).

To pull the latest copy from your connected sources:

```bash
$ frontitude pull
```

Options:

| Name                     | Default | Description                                                                                                                                                                                                                                                                                                                                                    |
| ------------------------ | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--nested`               | `false` | *Applicable for JSON format only.* By default, content is pulled in a flat format. Setting this option to `true` will output the content in a nested structure, where keys are broken into namespaces according to the workspace key convention delimiter.                                                                                                     |
| `--status`               | `all`   | Available statuses: draft/review/final/all.                                                                                                                                                                                                                                                                                                                    |
| `--has-key`              | `false` | Use this flag to pull only content that has been assigned keys. By default, content without keys is assigned auto-generated, non-human-readable IDs.                                                                                                                                                                                                           |
| `--tags`                 | `''`    | Pull content by tags. Specify comma-separated tag names string to filter copy (AND relation).                                                                                                                                                                                                                                                                  |
| `--include-metadata`     | `false` | Wrap each text in an object carrying its tags, status, note, and source metadata. See [Metadata fields](#metadata-fields) below for the full list.                                                                                                                                                                                                             |
| `--include-translations` | `false` | Include latest translations. Each locale is saved to a dedicated file, in the same location as the source file. The locale will be concatenated to the file’s name as part of the extension. E.g. ./strings-es.json                                                                                                                                            |
| `--dry-run`              | `false` | Display the results in the CLI without creating or updating any files.                                                                                                                                                                                                                                                                                         |
| `--access-token`         |         | Authenticate the pull request using this access token, taking precedence over any other configured access token.                                                                                                                                                                                                                                               |
| `--pages`                |         | Filter content by Frontitude pages from your connected projects. Specify a comma-separated page names string to filter content (OR relation). **Applicable only when the source is a project.**                                                                                                                                                                |
| `--screens`              |         | Filter content by Frontitude screens from your connected projects. Specify a comma-separated screen names string to filter content (OR relation). **Applicable only when the source is a project.**                                                                                                                                                            |
| `--error-format`         | `text`  | Print errors as a human-readable message (`text`) or as a machine-readable `{ error: { code, message } }` object on stdout (`json`). Defaults to `json` when `CI=true`, otherwise `text`. Can also be set via the `FRONTITUDE_ERROR_FORMAT` environment variable. See [Error handling](/developer-cli/errors) for the full list of error codes and exit codes. |

### Metadata fields

When `--include-metadata` is set, each text in the pulled JSON becomes an object instead of a bare string. The exact set of fields depends on the source the text was pulled from.

#### Texts pulled from a project or project folder

These texts are backed by a Figma design layer, so they carry the design-hierarchy and Figma identifiers. Every field below is always present; a field is `null` when the underlying source is empty (for example, a text that is not an instance of a library component has `parentComponentId: null`).

| Field               | Description                                                                                                                                                                          |
| ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `value`             | The source-locale string for this text.                                                                                                                                              |
| `isMixedStyle`      | `true` when the source text mixes inline styles in Figma (bold runs, links, colored spans); `false` otherwise.                                                                       |
| `tags`              | Array of tag names applied to the text in Frontitude.                                                                                                                                |
| `status`            | Workflow status of the text: `New`, `Draft`, `Review`, or `Final`.                                                                                                                   |
| `note`              | Free-text note attached to the text in Frontitude.                                                                                                                                   |
| `updatedAt`         | ISO timestamp of the most recent revision to the text.                                                                                                                               |
| `lastEditedBy`      | Display name of the collaborator who authored the most recent revision.                                                                                                              |
| `frontitudeLink`    | Direct URL to open the text in the Frontitude editor.                                                                                                                                |
| `mergedTextId`      | Identifier used to group duplicate texts in the merged JSON output. For instances of a library component, equals `parentComponentId`; for any other text, a unique value of its own. |
| `projectId`         | Frontitude project the text belongs to.                                                                                                                                              |
| `figmaFileId`       | Figma file ID of the design file the project is linked to.                                                                                                                           |
| `pageId`            | Frontitude page the source text layer sits on.                                                                                                                                       |
| `figmaPageId`       | Figma page ID of the page the source text layer sits on.                                                                                                                             |
| `frameId`           | Frontitude frame the source text layer sits on.                                                                                                                                      |
| `figmaFrameId`      | Figma frame ID of the frame the source text layer sits on.                                                                                                                           |
| `figmaTextNodeId`   | Figma node ID of the source text layer itself.                                                                                                                                       |
| `parentComponentId` | ID of the Frontitude library component this text is an instance of, when the text is linked to a component.                                                                          |

#### Texts pulled from the copy library

Copy-library texts are not tied to a specific layer in a design, so they do not carry the Figma or design-hierarchy identifiers above. Instead they expose the library component's own identity.

| Field            | Description                                                                                                       |
| ---------------- | ----------------------------------------------------------------------------------------------------------------- |
| `id`             | ID of the library component.                                                                                      |
| `name`           | Full path of the component in the copy library.                                                                   |
| `value`          | The source-locale string for this text.                                                                           |
| `uniqueKey`      | The component's key, when one is assigned.                                                                        |
| `tags`           | Array of tag names applied to the component.                                                                      |
| `status`         | Workflow status of the text: `New`, `Draft`, `Review`, or `Final`.                                                |
| `note`           | Free-text note attached to the component.                                                                         |
| `updatedAt`      | ISO timestamp of the most recent revision.                                                                        |
| `lastEditedBy`   | Display name of the collaborator who authored the most recent revision.                                           |
| `frontitudeLink` | Direct URL to open the component in the Frontitude copy library.                                                  |
| `pluralId`       | Plural form id of the text's selected form (`zero`/`one`/`two`/`few`/`many`/`other`); empty for non-plural texts. |

#### Plural texts

In either shape, a plural text carries one additional field, `plurals`: an object mapping each plural form (`zero`, `one`, `two`, `few`, `many`, `other`) to its variant's value. The top-level `value` field holds the source/selected plural form's value.

## Push

{% hint style="info" %}
Available from CLI version 1.3.0 and above.
{% endhint %}

Push new strings from your local source-locale file into the Frontitude copy library. Only **new unique keys create new components**. Any key that already exists in Frontitude is skipped and left unchanged. No existing content is ever modified.

```bash
$ frontitude push
```

By default, the CLI shows a preview of what would be created and asks for confirmation before writing anything. To skip the prompt, pass `--non-interactive` or set `CI=true` in your environment.

To inspect what would be pushed without making any changes:

```bash
$ frontitude push --dry-run
```

Options:

| Name                | Default | Description                                                                                                                                                                                                                                                                                                                                                    |
| ------------------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `--dry-run`         | `false` | Inspect what would be pushed without writing anything. Prints the list of new keys and the count of already-existing keys that would be skipped.                                                                                                                                                                                                               |
| `--tag`             |         | Comma-separated tag names to apply to newly-created components (e.g. `--tag onboarding,checkout`). Existing components are not retagged.                                                                                                                                                                                                                       |
| `--message`         |         | Attach a message to this push, visible in the Frontitude activity log. Use `@<email>` to notify a workspace teammate (e.g. `--message "@alice@example.com please review"`).                                                                                                                                                                                    |
| `--non-interactive` | `false` | Skip the dry-run preview and confirmation prompt. Auto-detected when stdin is not a TTY or `CI=true` is set.                                                                                                                                                                                                                                                   |
| `--access-token`    |         | Authenticate the push request using this access token, taking precedence over any other configured access token.                                                                                                                                                                                                                                               |
| `--error-format`    | `text`  | Print errors as a human-readable message (`text`) or as a machine-readable `{ error: { code, message } }` object on stdout (`json`). Defaults to `json` when `CI=true`, otherwise `text`. Can also be set via the `FRONTITUDE_ERROR_FORMAT` environment variable. See [Error handling](/developer-cli/errors) for the full list of error codes and exit codes. |

See [Error handling](/developer-cli/errors) for exit codes, `--error-format` details, and the full error code catalog.

## Using command aliases

To make it easier to run long commands in web projects, you can use the [scripts section in your package.json](https://docs.npmjs.com/cli/v11/using-npm/scripts).

Here’s an example of a command you can add:

```json
"scripts": {
  "pull-copy": "frontitude pull --has-key --tags=Repo1"
}
```

This allows you to run the following command to fetch content from Frontitude:

```sh
npm run pull-copy
```


# Error handling

> **Note:** The `--error-format` flag is available from CLI version 1.5.0 and above.

Every command exits with a status code your shell or CI pipeline can check:

| Exit code | Meaning                                                                                             |
| --------- | --------------------------------------------------------------------------------------------------- |
| `0`       | Success. The command completed, a dry-run preview was shown, or an interactive prompt was declined. |
| `1`       | Failure. See the error message for details.                                                         |

## Error format

By default, errors are printed as a human-readable message, switching to `json` automatically when `CI=true` is set. That way CI pipelines, scripts, and AI agents get a machine-readable object without extra configuration. You can also request it explicitly with `--error-format json` (or set `FRONTITUDE_ERROR_FORMAT=json`):

```bash
$ frontitude pull --error-format json
{"error":{"code":"UNAUTHENTICATED","message":"Your access token is missing or invalid."}}
```

The object always has the shape `{ "error": { "code": string, "message": string } }`. The `code` is a stable identifier you can branch on; the `message` is a human-readable explanation when one is available.

## Error codes

| Code                   | Meaning                                                                                                                     |
| ---------------------- | --------------------------------------------------------------------------------------------------------------------------- |
| `UNAUTHENTICATED`      | The access token is missing, invalid, or expired. Re-authenticate with `frontitude login` or pass a valid `--access-token`. |
| `FORBIDDEN`            | The authenticated user does not have access to the requested project or resource.                                           |
| `INTEGRATION_DISABLED` | The developer integration add-on is turned off for the workspace. Enable it from the workspace settings.                    |
| `VALIDATION_ERROR`     | The request was rejected because the input was invalid.                                                                     |
| `INVALID_MENTION`      | A `@mention` in a push message did not match an active workspace member.                                                    |
| `UNIQUE_KEY_EXISTS`    | One or more of the keys being pushed already exist in the workspace.                                                        |
| `LIMIT_REACHED`        | The workspace has reached its plan's text-component limit.                                                                  |
| `EXPORT_TOO_LARGE`     | The export is too large to return in a single response. Narrow the pull with filters such as `--pages` or `--tags`.         |
| `NOT_FOUND`            | The requested resource could not be found.                                                                                  |
| `INTERNAL`             | An unexpected server error occurred. Retry, and contact support if it persists.                                             |

New codes may be added over time. The accompanying `message` is a human-readable explanation when one is available.


# Example app

## Try it out with Frontitodo

[Frontitodo](https://github.com/frontitude/frontitude-demo-app-react) is a demo todo app, demonstrating string management with Frontitude's developer tools and how to integrate the Developer CLI output with [React-i18next](https://www.npmjs.com/package/react-i18next). Follow the installation steps to see it in action, from design to development.


# Supported client-side frameworks

Frontitude's Developer CLI works in tandem with the popular [i18next](https://www.i18next.com/) standard, and works smooth with any library built on top of it.

This standard has open source libraries for a variety of front-end frameworks. Here are some of the most popular:

* [React](https://github.com/i18next/react-i18next)
* [Angular](https://github.com/Romanchuk/angular-i18next)
* [Vue](https://github.com/i18next/i18next-vue)
* [Flutter](https://pub.dev/packages/i18next)
* [Next](https://github.com/i18next/next-i18next)
* iOS (Native)
* Android (Native)
* And [more](https://www.i18next.com/overview/supported-frameworks)...


# File formats


# Web (.json)

Frontitude supports multiple JSON formats.

{% hint style="info" %}
Format is automatically detected based on the file extension, so there’s no need to specify the format type when using it. ✨
{% endhint %}

{% hint style="info" %}
`frontitude push` supports the **flat** format only. Nested, Metadata, and multi-locale Translations formats are pull-only.
{% endhint %}

## Flat

**Example**

{% code fullWidth="false" %}

```json
{
  "info_tooltip.title": "No worries, you won’t be charged",
  "info_tooltip.paragraph": "You can review this order before it's final"
}
```

{% endcode %}

## Nested

**Example**

```json
{
  "info_tooltip": {
    "title": "No worries, you won’t be charged",
    "paragraph": "You can review this order before it's final"
  }
}
```

## Metadata

Frontitude supports an extended JSON format, called ***Metadata***, which basically includes all metadata stored in Frontitude for each copy item.

**Example**

```json
{
  "info_tooltip.title": {
    "value": "No worries, you won’t be charged",
    "status": "Final",
    "tags": ["Title", "Info tooltip", "Payment flow"],
    "isMixedStyle": false,
    "note": "A tooltip title, should grab attention and give users more information about next step",
    "createdAt": "2021-11-24T11:37:44.896Z",
    "updatedAt": "2021-11-25T10:12:34.131Z",
    "lastEditedBy": "Owen Fogel",
    "frontitudeLink": "https://app.frontitude.com/project/becaf236-53e7-473a-be41-3d6f7fc66168?edit=10278ce3-e802-4750-9b33-ef31f70e7bd0"
  },
  "info_tooltip.paragraph": {
    "value": "No worries, you won’t be charged",
    "status": "Final",
    "tags": ["Title", "Info tooltip", "Payment flow"],
    "isMixedStyle": false,
    "note": "A tooltip title, should grab attention and give users more information about next step",
    "createdAt": "2021-11-24T11:37:44.896Z",
    "updatedAt": "2021-11-25T10:12:34.131Z",
    "lastEditedBy": "Owen Fogel",
    "frontitudeLink": "https://app.frontitude.com/project/becaf236-53e7-473a-be41-3d6f7fc66168?edit=10278ce3-e802-4750-9b33-ef31f70e7bd0"
  }
}
```

## Translations (Single file)

This format assumes that translations in all languages are defined in a single file. This is usually the case in small/medium applications.

**Example**

{% code title="strings.json" %}

```json
{
  "en": {
    "info_tooltip.title": "No worries, you won’t be charged",
    "info_tooltip.paragraph": "You can review this order before it's final"
  },
  "es": {
    "info_tooltip.title": "No te preocupes, no se te cobrará",
    "info_tooltip.paragraph": "Puedes revisar este pedido antes de que sea definitivo"
  },
  "de": {
    "info_tooltip.title": "Keine Sorge, es entstehen Ihnen keine Kosten",
    "info_tooltip.paragraph": "Sie können diese Bestellung überprüfen, bevor sie endgültig ist"
  },
  "ja": {
    "info_tooltip.title": "請求されませんのでご安心ください",
    "info_tooltip.paragraph": "この注文を確定する前に確認できます"
  }
}
```

{% endcode %}

## Translations (Separate files)

This format assumes that translations are stored in separated files, each dedicated for a specific language. This is usually the case in large applications.

**Example**

{% code title="strings.json" %}

```json
{
  "info_tooltip.title": "No worries, you won’t be charged",
  "info_tooltip.paragraph": "You can review this order before it's final"
}
```

{% endcode %}

{% code title="strings-es.json" %}

```json
{
  "info_tooltip.title": "No te preocupes, no se te cobrará",
  "info_tooltip.paragraph": "Puedes revisar este pedido antes de que sea definitivo"
}
```

{% endcode %}

{% code title="strings-fr\_CA.json" %}

```json
{
  "info_tooltip.title": "Ne t'inquiète pas, tu ne seras pas facturé",
  "info_tooltip.paragraph": "Tu peux revoir cette commande avant qu'elle soit définitive"
}‍
```

{% endcode %}


# XLIFF (.xliff)

Frontitude supports XLIFF 1.2 and 2.0.

{% hint style="info" %}
Format is automatically detected based on the file extension, so there’s no need to specify the format type when using it. ✨
{% endhint %}

{% hint style="info" %}
This format is supported by both `frontitude pull` and `frontitude push`.
{% endhint %}

## Translations

```xml
<xliff version="1.2" xmlns="urn:oasis:names:tc:xliff:document:1.2">
  <file source-language="en" target-language="es" datatype="plaintext" original="file.ext">
    <body>
      <trans-unit id="info_tooltip.title">
        <source>No worries, you won’t be charged</source>
        <target>No te preocupes, no se te cobrará</target>
      </trans-unit>
      <trans-unit id="info_tooltip.paragraph">
        <source>You can review this order before it's final</source>
        <target>Puedes revisar este pedido antes de que sea definitivo</target>
      </trans-unit>
    </body>
  </file>
  <file source-language="en" target-language="de" datatype="plaintext" original="file.ext">
    <body>
      <trans-unit id="info_tooltip.title">
        <source>No worries, you won’t be charged</source>
        <target>Keine Sorge, es wird Ihnen nichts berechnet</target>
      </trans-unit>
      <trans-unit id="info_tooltip.paragraph">
        <source>You can review this order before it's final</source>
        <target>Sie können diese Bestellung überprüfen, bevor sie endgültig ist</target>
      </trans-unit>
    </body>
  </file>
  <file source-language="en" target-language="ja" datatype="plaintext" original="file.ext">
    <body>
      <trans-unit id="info_tooltip.title">
        <source>No worries, you won’t be charged</source>
        <target>心配無用、請求はされません</target>
      </trans-unit>
      <trans-unit id="info_tooltip.paragraph">
        <source>You can review this order before it's final</source>
        <target>注文が確定する前に確認できます</target>
      </trans-unit>
    </body>
  </file>
</xliff>

```


# Android XML (.xml)

{% hint style="info" %}
Format is automatically detected based on the file extension, so there’s no need to specify the format type when using it. ✨
{% endhint %}

{% hint style="info" %}
Dashes (-) are automatically converted to underscores (\_) for unique keys embedded within the `name` attribute to comply with Android syntax.
{% endhint %}

{% hint style="info" %}
This format is supported by both `frontitude pull` and `frontitude push`.
{% endhint %}

## Translations with plurals

{% code title="res/values/strings.xml" %}

```xml
<resources>
    <string name="greeting">Hello, %1$s!</string>
    <plurals name="items_in_cart">
        <item quantity="one">You have %d item in your cart</item>
        <item quantity="other">You have %d items in your cart</item>
    </plurals>
</resources>
```

{% endcode %}

{% code title="res/values-es/strings.xml" %}

```xml
<resources>
    <string name="greeting">Hola, %1$s!</string>
    <plurals name="items_in_cart">
        <item quantity="one">Tienes %d artículo en tu carrito</item>
        <item quantity="other">Tienes %d artículos en tu carrito</item>
    </plurals>
</resources>
```

{% endcode %}

{% code title="res/values-de/strings.xml" %}

```xml
<resources>
    <string name="greeting">Hallo, %1$s!</string>
    <plurals name="items_in_cart">
        <item quantity="one">Sie haben %d Artikel in Ihrem Warenkorb</item>
        <item quantity="other">Sie haben %d Artikel in Ihrem Warenkorb</item>
    </plurals>
</resources>
```

{% endcode %}

{% code title="res/values-ja/strings.xml" %}

```xml
<resources>
    <string name="greeting">こんにちは、%1$sさん！</string>
    <plurals name="items_in_cart">
        <item quantity="one">カートには%dアイテムがあります</item>
        <item quantity="other">カートには%dアイテムがあります</item>
    </plurals>
</resources>
```

{% endcode %}


# Apple Strings (.strings)

{% hint style="info" %}
Format is automatically detected based on the file extension, so there’s no need to specify the format type when using it. ✨
{% endhint %}

{% hint style="info" %}
This format is supported by both `frontitude pull` and `frontitude push`.
{% endhint %}

### Supported project structure

The Developer CLI assumes a standard project structure in which translation files are named `Localizable.strings` and stored in locale-specific folders. For example:

```
my-ios-app
├── en.lproj
│   └── Localizable.strings     // English translations
├── fr-FR.lproj
│   └── Localizable.strings     // French translations
├── de-DE.lproj
│   └── Localizable.strings     // German translations
```

### Comments

{% hint style="info" %}
Existing comments in the file will be preserved when pulling content from Frontitude.
{% endhint %}

Comments are supported in the following format. Comments in other formats will be converted accordingly.

{% code title="Localizable.strings" %}

```javascript
/* Error message shown when there is a network connectivity issue */
"network_error_message" = "Unable to connect. Please check your internet connection.";
```

{% endcode %}

### Variables

While this format supports different types for variables, currently only string variables are supported. Variables defined in Frontitude will be converted into string variables.

{% code title="Localizable.strings" %}

```
"greetings.hello" = "Hello, %@!";
"time.last_seen" = "Last seen at %@";
```

{% endcode %}

### Plurals <a href="#h_6eca633a75" id="h_6eca633a75"></a>

The following key format is used for texts with plurals: `<key>_<plural_type>`. For example:

```
"greetings.hello_one" = "Hello, %@!";
"greetings.hello_many" = "Hello, folks!";
```

### Multiline support <a href="#h_6eca633a75" id="h_6eca633a75"></a>

Frontitude uses `\n` to handle multiline strings. For example:

{% code title="Localizable.strings" %}

```
"error.cant_recognize_email" = "We can't recognize this email.\nPlease try again.";
```

{% endcode %}

### File encoding

The supported file encoding is `UTF-16`, which is recommended by Apple. Please contact us if you require a different encoding.


# Flutter (.arb)

This format is in the making...

## Coming soon!

Contact us at <hi@frontitude.com> to find out exactly when we plan to add it.


# Gettext (.po, .pot)

This format is in the making...

## Coming soon!

Contact us at <hi@frontitude.com> to find out exactly when we plan to add it.


# Introduction

{% hint style="info" %}
Webhooks integration is part of the developer pack on our premium plans, and free to use on the **Starter** plan‍
{% endhint %}

Frontitude's Webhooks integration allows you to receive HTTP requests whenever data within Frontitude changes. This mechanism allows you to update other services on critical events occurred in Frontitude.

<figure><img src="/files/DuikYzhb8coi0inxkZzU" alt=""><figcaption></figcaption></figure>

## Possible use cases

A few examples of using Frontitude Webhooks:

* Upload a text and key to the localization tool when it’s finalized
* Upload a screenshot to the localization tool when a text is finalized
* Update button labels in help guides when text is finalized
* Update a JIRA ticket status and description upon text status or value change
* And many more…


# Setting up Webhooks

{% embed url="<https://frontitude-public.s3.eu-west-1.amazonaws.com/website/webhooks.mp4>" %}
Creating a webhook in Frontitude
{% endembed %}

## Turning On the Webhooks Integration

1. Go to the ***Webhooks*** section in your [workspace integrations page](https://app.frontitude.com/settings/integrations).
2. If it's turned of, click the toggle to turn on the integration. Notice that Webhooks is available only as part of the developer pack on our premium plans.

## Generating a Signing Key

Before you can start using Webhooks, you need to generate a signing key. This key is essential for ensuring the authenticity of incoming HTTP payloads and preventing malicious requests.

To generate a signing key:

1. Click on the ***Generate key*** button under the ***Webhooks*** section in your [workspace integrations page](https://app.frontitude.com/settings/integrations).
2. Click ***Copy*** to copy it to your clipboard.
3. Paste it somewhere safe and use it in your code to validate incoming events (see our code sample below).
4. You can always refresh the signing key by clicking ***Refresh key***. On refresh, the previous key will be valid for the next 24 hours.‍

## Creating a Webhook

To start receiving webhook events, you need to create a webhook:

1. Go to the ***Webhooks*** section in your [workspace integrations page](https://app.frontitude.com/settings/integrations).
2. Create a webhook by specifying a name, URL, and at least one trigger.
3. Ensure that the URL you specify points to a secure endpoint using HTTPS.


# Using Webhooks

## Request Headers

When Frontitude sends a webhook event, it includes three essential headers:

* **svix-id**: An ID that uniquely identifies each request. You can cross-reference this identifier with delivery history within Frontitude.
* **svix-timestamp**: A Unix timestamp indicating when the request was made. This timestamp is included in the signature to guard against replay attacks.
* **svix-signature**: A list of signatures (HMAC-SHA256) created using your workspace's webhook signing key and will be used to validate the event’s authenticity. It is composed of space delimited signatures and their corresponding version identifiers. For example: “v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo=”.\
  Make sure to remove the version prefix and delimiter (e.g. v1,) before verifying the signature.

‍

## Request Validation and Processing

Frontitude expects all webhook consumers to respond with a status code between 200 and 299 within a reasonable time-frame (up to 15s) to indicate successful event processing.

For enhanced security, follow these actions before processing a webhook event and issuing a success response:

* Validate the signature included in the **svix-signature** header. Concatenate the webhook ID, timestamp, and a string representation of the request body into a single string, separated by periods. Use HMAC-SHA256 to sign this value with your signing key and compare it against the header's signature list.
* (Optional) Validate event's timestamp against system's timestamp to make sure it's within tolerance in order to prevent [timestamp/replay attacks](https://en.wikipedia.org/wiki/Replay_attack).
* (Optional) Ensure idempotence by tracking request IDs (by using the **svix-id** header) in a persistent data store and ensuring that new events have not been processed before.

Our code sample below will help you get started quickly.

## Error Handling

If Frontitude receives an error response from your server, it will make additional attempts to send the event based on a retry schedule with exponential backoff.

The schedule (where each period is started following the failure of the preceding attempt):

1. Immediately
2. 5 seconds
3. 5 minutes
4. 30 minutes
5. 2 hours
6. 5 hours
7. 10 hours
8. 10 hours (in addition to the previous)

After the conclusion of the above attempts the event will be marked as Failed for this endpoint. If all attempts to a specific endpoint fail for a period of 5 days, the endpoint will be disabled.‍


# Events

{% hint style="info" %}
All webhook events sent to your endpoint will contain an **eventType** and **data** properties.‍
{% endhint %}

{% hint style="info" %}
The data property is specific to the event that triggered it and includes an array of affected copy components. This ensures that events happening in bulk will trigger your endpoints once for each action.
{% endhint %}

## Components creation

Triggered each time components are created.

```json
{
    eventType: "components.created",
    data: {
        components: [{
            _fttdInternalComponentId, // Frontitude's internal id, can be helpful for debugging.
            uniqueKey, // if a unique key isn't set, it will be auto-set with a temporary persistent unique id that starts with the 'frontitude-' prefix.
            name,
            value, // a string if the component is not pluralized, else, an object that represents plurals: { zero?, one?, two?, few?, many?, other? }.
            tags, // string array
            status, // "New"/"Draft"/"Review"/"Final"
            note,
            frontitudeLink
        }]
    }
}
```

## Components deletion

Triggered each time components are deleted.

```json
{
    eventType: "components.deleted",
    data: {
        components: [{
            _fttdInternalComponentId, // Frontitude's internal id, can be helpful for debugging.
            uniqueKey // if a unique key isn't set, it will be auto-set with a temporary persistent unique id that starts with the 'frontitude-' prefix.
        }]
    }
}‍
```

## Components value change

Triggered each time components’ values are changed (including plurals) in any language (source language or translations).

```json
{
    eventType: "components.value.changed",
    data: {
        components: [{
            _fttdInternalComponentId, // Frontitude's internal id, can be helpful for debugging.
            uniqueKey, // if a unique key isn't set, it will be auto-set with a temporary persistent unique id that starts with the 'frontitude-' prefix.
            localeId, // locale id is set for target translation languages, else, for source language it will be set as undefined.
            value, // a string if the component is not pluralized, else, an object that represents plurals: { zero?, one?, two?, few?, many?, other? }.
                   // if a translation was removed for the locale id, the value may be unset.
            previousValue // may be undefined if it's the first time this locale id was translated into
        }]
    }
}
```

‍

## Components status change

Triggered each time components' statuses are changed in any language (source language or translations).

```json
{
    eventType: "components.status.changed",
    data: {
        components: [{
            _fttdInternalComponentId, // Frontitude's internal id, can be helpful for debugging.
            uniqueKey, // if a unique key isn't set, it will be auto-set with a temporary persistent unique id that starts with the 'frontitude-' prefix.
            localeId, // locale id is set for target translation languages, else, for source language it will be set as undefined.
            status, // "New"/"Draft"/"Review"/"Final"
            previousStatus
        }]
    }
}
```

## Components unique key change

Triggered each time components' unique keys are changed.

```json
{
    eventType: "components.uniqueKey.changed",
    data: {
        components: [{
            _fttdInternalComponentId, // Frontitude's internal id, can be helpful for debugging.
            uniqueKey, // if a unique key isn't set, it will be auto-set with a temporary persistent unique id that starts with the 'frontitude-' prefix.
            previousUniqueKey
        }]
    }
}
```


# Example webhook handler

Use this example as a boilerplate for your webhook handler. We added extra comments to make it super clear:

{% tabs %}
{% tab title="Node.js" %}

```javascript
const express = require('express')
const router = express.Router()
const crypto = require('crypto')
const { logger } = global

// Create your signing key at https://app.frontitude.com/settings/integrations in the "Webhooks" section
const WEBHOOKS_SIGNING_KEY = process.env.WEBHOOKS_SIGNING_KEY || 'xxx'

router.post('/handle_frontitude_events', async function(req, res) {
    // It is important to disable CSRF protection for this endpoint if the framework you use enables them by default.
    
    const { 'svix-id': webhookId, 'svix-timestamp': webhookTimestamp, 'svix-signature': webhookSignature } = req.headers
    const { eventType, data } = req.body

    logger.info({
        message: 'Frontitude webhooks: new event received',
        eventType,
        webhookId
    })

    // Compare event's timestamp (in seconds since epoch) against system's timestamp to make sure it's within tolerance in order to prevent timestamp/replay attacks (see: https://en.wikipedia.org/wiki/Replay_attack)
    const VALID_TIMESTAMP_TOLERANCE_IN_MINUTES = 5
    const currentTimestampInSeconds = Math.floor(new Date().getTime() / 1000)
    const minutesAgo = currentTimestampInSeconds - (1000 * 60 * VALID_TIMESTAMP_TOLERANCE_IN_MINUTES)
    const minutesLater = currentTimestampInSeconds + (1000 * 60 * VALID_TIMESTAMP_TOLERANCE_IN_MINUTES)
    if (webhookTimestamp < minutesAgo || webhookTimestamp > minutesLater) {
        logger.error({
            message: 'Frontitude webhooks: invalid timestamp',
            webhookTimestamp,
            minutesAgo,
            minutesLater,
            request: {
                headers: req.headers,
                body: req.body
            }
        })

        return res.status(401).send('Invalid timestamp')
    }

    // Verify event's signature by comparing the signature sent in the webhook headers with the signature computed from the webhook payload
    // The content to sign is composed by concatenating the webhook id, timestamp and payload, separated by the full-stop character (.).
    signedContent = `${webhookId}.${webhookTimestamp}.${JSON.stringify(req.body)}`
    const expectedSignature = crypto
        .createHmac('sha256', Buffer.from(WEBHOOKS_SIGNING_KEY, 'base64'))
        .update(signedContent)
        .digest('base64')
    
    // The expected signature should match one of the signatures sent in the signature header
    // Signature header is composed of a list of space delimited signatures and their corresponding version identifiers. The list is most commonly of length one. Though there could be any number of signatures.
    // For example: "v1,g0hM9SsE+OTPJTGt/tmIKtSyZlE3uFJELVlNIOLJ1OE= v1,bm9ldHUjKzFob2VudXRob2VodWUzMjRvdWVvdW9ldQo= v2,MzJsNDk4MzI0K2VvdSMjMTEjQEBAQDEyMzMzMzEyMwo="
    const webhookPotentialSignatures = webhookSignature.split(' ').map(signature => signature.split(',')[1])
    if (!webhookPotentialSignatures.includes(expectedSignature)) {
        logger.error({
            message: 'Frontitude webhooks: invalid signature',
            expectedSignature,
            webhookSignature,
            request: {
                headers: req.headers,
                body: req.body
            }
        })

        return res.status(401).send('Invalid signature')
    }

    // Process the event
    // The way to indicate that a webhook has been processed is by returning a 2xx (status code 200-299) response to the webhook message within a reasonable time-frame (up to 15s). If processing the webhook takes longer than that, it's better to return a 2xx response and process the webhook asynchronously, to avoid Frontitude retrying the webhook delivery.

    logger.info({
        message: 'Frontitude webhooks: processed event successfully',
        eventType,
        data
    })

    res.sendStatus(200)
})

module.exports = router
```

{% endtab %}
{% endtabs %}


