# Languages

Add, configure and remove the languages of a Comvi project, and list the system locales, through the REST API.

Canonical: https://comvi.io/docs/api/languages

Locales define which languages a project supports. Add a system locale from Comvi's list, or create a custom locale with your own code. The locales attached to a project are on `locales` in [`GET /v1/projects/{id}`](/docs/api/projects#get-v1projectsid).

Common errors: `401 AUTHENTICATION_REQUIRED` (missing or invalid credentials, or a Bearer key on a session-only route), `403 FORBIDDEN`, `403 READ_ONLY_MODE` on writes. See [Errors](/docs/api/errors).

## Locale object

```json
{ "id": 2, "code": "uk", "name": "Ukrainian", "nativeName": "Українська", "isCustom": false, "flagEmoji": "🇺🇦" }
```

### `GET /v1/projects/{id}/locales/stats`

Translation counts and percentages per language. A caller with language restrictions sees only their languages.

Session cookie or API key with `translations:read`.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | Project ID |

```bash
curl -H "Authorization: Bearer $COMVI_API_KEY" https://api.comvi.io/v1/projects/1/locales/stats
```

```json
[
{ "localeCode": "uk", "totalKeys": 250, "translated": 200, "notReviewed": 30, "notTranslated": 20, "translatedPct": 80, "notReviewedPct": 12, "notTranslatedPct": 8 }
]
```

### `POST /v1/projects/{id}/locales`

Add a language. Existing keys get placeholder values in the new language with `not_translated` status.

Session cookie only, with `locale.create`.

`isCustom` selects which field set applies; fields of the other set are ignored, and a missing field of the chosen set returns `400 VALIDATION_ERROR` rather than a schema error.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | Project ID |
| `isCustom` | body | `boolean` | Yes | `false` for a system locale, `true` for a custom one |
| `localeCode` | body | `string` | System | System locale code, such as `uk` |
| `code` | body | `string` | Custom | 1 to 50 characters; letters, digits, `-` and `_` |
| `name` | body | `string` | Custom | Display name |
| `nativeName` | body | `string` | Custom | Native name |
| `flagEmoji` | body | `string` | No | Up to 10 characters, custom only |

```bash
curl -X POST -b cookies.txt -H "Content-Type: application/json" \
-d '{"isCustom": true, "code": "en-pirate", "name": "Pirate English", "nativeName": "Pirate English", "flagEmoji": "🏴‍☠️"}' \
https://api.comvi.io/v1/projects/1/locales
```

Returns `201 Created` with the locale object.

| Status and code | When |
|-----------------|------|
| `400 LOCALE_NOT_FOUND` | `localeCode` is not a valid system locale |
| `400 VALIDATION_ERROR` | A required field for the chosen `isCustom` mode is missing |
| `403 LIMIT_EXCEEDED` | The organization has a custom language limit and the project is at it. No plan sets one |
| `409 LOCALE_ALREADY_EXISTS` | The locale is already on the project, or a custom `code` is a built-in language once canonicalised (`pt_br`, `PT-BR` and `pt-BR` are all the built-in `pt-BR`) |
| `422 FIELD_REQUIRED` | `isCustom` is missing |

### `PATCH /v1/projects/{id}/locales/{localeCode}`

Update per-language settings. The one setting is `aiContext`, the context passed to AI providers when translating into this language.

Session cookie or API key with `translations:write`.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | Project ID |
| `localeCode` | path | `string` | Yes | Locale code |
| `aiContext` | body | `string \| null` | No | Up to 2000 characters. `null` clears it; omitting it leaves the stored value |

```bash
curl -X PATCH -H "Authorization: Bearer $COMVI_API_KEY" -H "Content-Type: application/json" \
-d '{"aiContext": "Use formal address. Keep product names in English."}' \
https://api.comvi.io/v1/projects/1/locales/uk
```

Returns `204 No Content`. The value comes back as `locales[].aiContext` on the project. An empty body still checks that the locale is attached to the project.

| Status and code | When |
|-----------------|------|
| `404 LOCALE_NOT_FOUND` | No such locale, or not attached to the project |
| `422 FIELD_TOO_LONG` | `aiContext` is over 2000 characters |

### `DELETE /v1/projects/{id}/locales/{localeCode}`

Remove a language and permanently delete every translation value in it.

Session cookie only, with `locale.delete`.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `id` | path | `integer` | Yes | Project ID |
| `localeCode` | path | `string` | Yes | Locale code |

```bash
curl -X DELETE -b cookies.txt https://api.comvi.io/v1/projects/1/locales/uk
```

Returns `204 No Content`.

| Status and code | When |
|-----------------|------|
| `400 LOCALE_NOT_FOUND` | The code is invalid or not on the project |
| `400 CANNOT_REMOVE_DEFAULT_LOCALE` | The locale is the source language. Change the source locale first |
| `404 PROJECT_NOT_FOUND` | Project does not exist |

### `GET /v1/locales`

List every system locale. Public, no authentication.

```bash
curl https://api.comvi.io/v1/locales
```

Returns an array of locale objects.

| Status and code | When |
|-----------------|------|
| `429 TOO_MANY_REQUESTS` | The global limit of 500 requests per minute was exceeded |

### `GET /v1/locales/{code}`

Read one system locale. Public, no authentication.

| Parameter | In | Type | Required | Meaning |
|-----------|----|------|----------|---------|
| `code` | path | `string` | Yes | Locale code |

```bash
curl https://api.comvi.io/v1/locales/uk
```

Returns one locale object.

| Status and code | When |
|-----------------|------|
| `404 LOCALE_NOT_FOUND` | No locale with that code |

## Next steps

- [Projects](/docs/api/projects) to read the locales attached to a project
- [Translations](/docs/api/translations) for values per locale
- [Languages in the platform](/docs/platform/languages) for the dashboard
