# Language Packages

https://dev.wisecp.com/es/language-packages

The seven endpoints that see, copy, set up and remove the installed languages.

## Overview

A language lives in three places at once in WISECP: the **package file** (name, country, order, state), the **translation files** and the **language tables** holding product names, category titles and notification templates. This article manages the first and the whole.

A new language is never born empty; it is set up by **copying** one that is installed. The copy takes both the table rows and the file tree, which makes it heavy work.

One language is the **default** and that one is privileged: it cannot be closed, cannot be removed, and carries no language prefix in its addresses. Moving the default swaps those three behaviours between two languages.

## Reference

### Listing the Installed Languages

get/api/v1/admin/languages

`Languages/GetLanguages` admin

Returns every language on the installation, the closed ones included.

Response fields data[] — 15 + meta — 2

keystringThe language key. This is what the addresses carry.

namestringThe language name in English.

show_namestringThe name shown to a client.

codestringThe language code.

code_hyphenstringThe language and country code together.

country_idintThe country id. Look to the reference endpoints for its name.

country_codestringThe country code.

statusboolWhether the language is on.

localboolWhether it is the default. One language on the installation carries this.

rtlboolWhether the language reads right to left.

rankintWhere it sits in the listing.

permalinkboolWhether permalinks are supported.

prefixstringWhere the address prefix stands. Off on the default language and on for the rest.

copiedstringThe name of the language it was copied from.

created_atstringWhen it was made.

countintHow many languages there are. It comes back under meta.

defaultstringThe default language key.

Errors 1

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/languages' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/languages', {
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data, meta } = await res.json();
const live = data.filter((l) => l.status);
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/languages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The closed ones come too; filter on status for the languages open to a client.
$r    = Api::Languages()->GetLanguages();
$live = array_values(array_filter($r['data'], fn ($l) => $l['status']));
$def  = $r['meta']['default'];
```

### Listing What Can Be Downloaded

get/api/v1/admin/languages/available

`Languages/GetAvailableLanguages` admin a remote service

Returns the languages the WISECP translation service offers.

Response fields data[] + meta — 1

dataobject[]The language definitions the remote service returns. An empty list comes back when the service cannot be reached.

countintHow many came back. It comes back under meta.

Errors 1

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/languages/available' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/languages/available', {
  headers: { Authorization: `Bearer ${apiKey}` },
});

const { data, meta } = await res.json();
if (meta.count === 0) showRetry();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/languages/available');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// AN EMPTY LIST means one of two things: none exist or the remote service failed. They look alike.
$out = Api::Languages()->GetAvailableLanguages();
if (! $out['data']) $logger->warning('empty language list — check the service is reachable');
```

### Reading One Language

get/api/v1/admin/languages/{key}

`Languages/GetLanguage` admin

Returns one language package.

Response fields data — 15

dataobjectThe language package. Same shape as an item in the listing.

Errors 2

language_not_found404No such language.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl 'https://panel.example.com/api/v1/admin/languages/de' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res  = await fetch(`https://panel.example.com/api/v1/admin/languages/${key}`, {
  headers: { Authorization: `Bearer ${apiKey}` },
});
const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The key is not the language CODE: a second country under one code takes the 'en-gb' form.
$pkg = Api::Languages()->GetLanguage(['key' => $key])['data'];
```

### Making a Language by Copying

post/api/v1/admin/languages

`Languages/CreateLanguage` admin heavy work

Sets up a new language by copying one that is installed.

Body 6

copy_languagestringreqThe key of the language to copy. It has to be installed.

languagestringreqThe new language code. It needs letters; digits alone are turned down.

country_idintreqThe country id.

show_namestringreqThe name to show a client.

rankintWhere it sits in the listing.

rtlintSend 1 when it reads right to left.

Response fields 201 — data — 15

dataobjectThe language package set up. Read its key here; it can differ from the code you sent.

Errors 7

country_required422The country id is missing.

language_required422The language code is empty or all digits.

copy_language_invalid422The source language is not installed.

show_name_required422The display name is empty.

language_exists422That language and country pair is already installed.

copy_tables_failed500The language table rows could not be copied.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X POST 'https://panel.example.com/api/v1/admin/languages' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"copy_language":"en","language":"de","country_id":276,"show_name":"Deutsch"}'
```

```javascript
const res = await fetch('https://panel.example.com/api/v1/admin/languages', {
  method: 'POST',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    copy_language: 'en',
    language: 'de',
    country_id: 276,
    show_name: 'Deutsch',
    rank: 2,
  }),
});

const { data } = await res.json();
const realKey = data.key;   // 'de' olmayabilir
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/languages');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode([
        'copy_language' => 'en',
        'language'      => 'de',
        'country_id'    => 276,
        'show_name'     => 'Deutsch',
    ]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// When the code you send IS already installed the key becomes 'code-country'; read it, do not assume.
$pkg = Api::Languages()->CreateLanguage([
    'copy_language' => 'en', 'language' => 'en',
    'country_id' => 826, 'show_name' => 'English (UK)',
])['data'];

$key = $pkg['key'];    // 'en-gb'
```

### Changing a Language's Settings

patch/api/v1/admin/languages/{key}

`Languages/UpdateLanguage` admin promoting is heavy

Changes the fields you send and can promote the language to default.

Body 5

show_namestringThe name shown to a client. It cannot be empty once sent.

rankintWhere it sits in the listing.

rtlintSend 1 when it reads right to left.

statusintWhether the language is on. It is ignored on the default language.

localintSend 1 to make this the default. It changes the address prefix of two languages at once.

Response fields data — 15

dataobjectThe language package as it now stands.

Errors 3

language_not_found404No such language.

show_name_required422The name you sent is empty.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PATCH 'https://panel.example.com/api/v1/admin/languages/de' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"show_name":"Deutsch","rank":2}'
```

```javascript
const res = await fetch(`https://panel.example.com/api/v1/admin/languages/${key}`, {
  method: 'PATCH',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ show_name: 'Deutsch', rank: 2 }),
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PATCH',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['show_name' => 'Deutsch', 'rank' => 2]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// On the DEFAULT language status is IGNORED: the answer is 200 while the language stays on.
$pkg = Api::Languages()->UpdateLanguage(['key' => $key, 'status' => 0])['data'];
if ($pkg['status']) $logger->info('the default language cannot be closed — nothing changed');
```

### Turning a Language On and Off

put/api/v1/admin/languages/{key}/status

`Languages/SetLanguageStatus` admin

Opens a language to clients or closes it.

Body 1

statusintreqThe new state: 0 for off and 1 for on.

Response fields data — 2

keystringThe language key.

statusboolWhere it now stands.

Errors 5

language_not_found404No such language.

invalid_status422The state is neither 0 nor 1.

language_is_default422The default language cannot be closed.

no_change422The language already sits in the state you asked for.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X PUT 'https://panel.example.com/api/v1/admin/languages/de/status' \
  -H "Authorization: Bearer $API_KEY" \
  -H 'Content-Type: application/json' \
  -d '{"status":1}'
```

```javascript
const res = await fetch(`https://panel.example.com/api/v1/admin/languages/${key}/status`, {
  method: 'PUT',
  headers: {
    Authorization: `Bearer ${apiKey}`,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({ status: 1 }),
});

if (res.status === 422) { /* zaten aciksa buraya duser */ }
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key . '/status');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'PUT',
    CURLOPT_HTTPHEADER     => [
        'Authorization: Bearer ' . $apiKey,
        'Content-Type: application/json',
    ],
    CURLOPT_POSTFIELDS => json_encode(['status' => 1]),
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// Writing the same state again is an ERROR; read the current one first during a bulk sync.
$pkg = Api::Languages()->GetLanguage(['key' => $key])['data'];
if ($pkg['status'] !== $wanted) Api::Languages()->SetLanguageStatus(['key' => $key, 'status' => (int) $wanted]);
```

### Removing a Language

delete/api/v1/admin/languages/{key}

`Languages/DeleteLanguage` admin cannot be undone

Removes a language, its translations and every row belonging to it.

Response fields data — 2

deletedboolWhether the delete ran.

keystringThe key of the language removed.

Errors 4

language_not_found404No such language.

language_is_default422The default language cannot be removed.

remove_tables_failed500The language table rows could not be removed.

insufficient_scope403The key lacks the required scope.

Request cURL JavaScript PHP (HTTP) PHP (Internal)

```bash
curl -X DELETE 'https://panel.example.com/api/v1/admin/languages/de' \
  -H "Authorization: Bearer $API_KEY"
```

```javascript
const res = await fetch(`https://panel.example.com/api/v1/admin/languages/${key}`, {
  method: 'DELETE',
  headers: { Authorization: `Bearer ${apiKey}` },
});

const body = await res.json();
```

```php
$ch = curl_init('https://panel.example.com/api/v1/admin/languages/' . $key);
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_CUSTOMREQUEST  => 'DELETE',
    CURLOPT_HTTPHEADER     => ['Authorization: Bearer ' . $apiKey],
]);

$body = json_decode(curl_exec($ch), true);
curl_close($ch);
```

```php
// The product and category names IN THAT LANGUAGE go as well; export and keep them first.
$dump = Api::Languages()->ExportLanguageTranslations(['key' => $key])['data'];
file_put_contents("backup-$key.json", json_encode($dump));

Api::Languages()->DeleteLanguage(['key' => $key]);
```

## Pitfalls

> **The key made is not always the code you sent**
> 
> When the language code you send is **already installed**, the new key takes the `code-country` form. Setting up a second English makes `en-gb` rather than `en`. Read the key from the answer, since every later call wants it.

> **Moving the default changes the addresses of two languages**
> 
> Making a language the default **opens** the prefix on the old default and **closes** it on the new one. The public addresses of both languages change and the old links shift. The job also rewrites the settings file and forces the new language open. Do not run it unplanned on a live installation.

> **Two paths handle one state differently**
> 
> The status endpoint turns down closing the default language with `422`. The settings endpoint **ignores** the same request quietly and answers `200` while the language stays open. A script mixing the two believes it closed something. Look at the state in the answer.

> **Writing the same state gives an error**
> 
> The status endpoint answers `422` with `no_change` when the state you ask for is the one already there. The endpoint is not repeatable: a sync loop setting every language to open errors on the ones that are open. Read the current state before writing.

> **Removing takes more than the translations**
> 
> Removing a language takes the translation files, the notification templates and the **language table rows**: the product names, category titles and page content in that language go with it. The only way back is a backup. Export the translations first.

> **An empty download list can mean a failure**
> 
> The downloadable languages come from a remote service. When it cannot be reached the endpoint raises no error and returns an **empty list**. That makes "no languages" and "the service failed" look alike. Leave a way to retry before showing an empty result as "nothing to pick".

## Related Articles

- [Translation Strings](https://dev.wisecp.com/en/translation-strings)
- [Reference Lists](https://dev.wisecp.com/en/reference-lists)
