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

# Localized text

> Store and read translations consistently in the FITsociety Public API.

Translatable content uses a language map per field and a resource-level
`defaultLanguage`. Responses return stored translations. FITsociety does not
generate translations or select a language based on request headers.

```json theme={null}
{
  "defaultLanguage": "nl",
  "name": { "nl": "Intakegesprek", "en": "Initial consultation" },
  "description": {}
}
```

## Supported resources

| Resource                                               | Localized fields                                                                           | Public authoring                                                                                                             |
| :----------------------------------------------------- | :----------------------------------------------------------------------------------------- | :--------------------------------------------------------------------------------------------------------------------------- |
| [Forms](/public-api/forms)                             | Form and section names/descriptions, question text, placeholders, help text, option labels | Create and update; stable question and option IDs identify answers.                                                          |
| [Habit templates](/public-api/habits-goals)            | `name`, `description`, `customUnitName`, `reminders[].message`                             | Create and update; applied habits and reminder delivery still use source text.                                               |
| [Product catalog](/public-api/finance)                 | `name`, `description` for memberships, credit packs, day/week passes and single sessions   | Create and update; financial snapshots remain unchanged.                                                                     |
| [Event types/templates](/public-api/calendar-bookings) | `name`, plus template `description`                                                        | Create and update; existing calendar storage currently supports `nl`, `en`, `de`, `fr`, `es` only. Other codes return `400`. |
| [Calendar events](/public-api/calendar-bookings)       | `name`                                                                                     | Read only.                                                                                                                   |
| [Measurement types](/public-api/measurements)          | `name`, including summary references                                                       | Read only; identifiers, units and measurement values are unchanged.                                                          |
| [Custom fields](/public-api/clients)                   | `label`, `helpText`, `placeholder`, option labels                                          | Definition text is read only. Client values keep stable keys and option values.                                              |
| [Company policies](/public-api/company-catalog)        | `privacy.content`, `terms.content`                                                         | Read only; each policy has `defaultLanguage` and an `updatedAt` map by language.                                             |

Internal apps and endpoints retain their current text contracts. The read-only
resources expose whichever languages their source storage contains; this
contract does not add internal translation editors or migrate existing data.
Personal names, free-form notes, client answers and operational snapshots keep
their existing representations.

## Writing translations

* Create requires `defaultLanguage`. Use canonical BCP-47 language codes, such as
  `nl`, `en`, or `en-GB`; the calendar restriction above still applies.
* Each text map accepts up to 25 languages. Codes are canonicalized; duplicate
  codes after canonicalization are rejected.
* Every nonempty map must include `defaultLanguage`. Values must be nonempty
  strings; surrounding whitespace is removed. Field limits apply per language.
* A supplied map replaces **all translations for that field**. Omitted PATCH
  fields retain their translations. Include the whole map when adding a language.
* Optional text can be cleared with `{}`. Required text cannot be empty.
  Plain strings, `null`, arrays, empty translation strings and the input language
  code `und` are rejected.
* A change of `defaultLanguage` requires the new language in all retained
  nonempty maps. It also changes the source text used by existing internal flows.
* Habit template `reminders` and form structure arrays follow their documented
  replacement rules. Include all required elements and their complete text maps.

For example, this request creates a bilingual single-session catalog item:

```bash cURL theme={null}
curl -X POST "https://api.fitsociety.io/public/v1/finance/single-sessions" \
  -H "Authorization: Bearer $FITSOCIETY_ACCESS_TOKEN" \
  -H "Idempotency-Key: bilingual-session-20260910" \
  -H "Content-Type: application/json" \
  -d '{
    "defaultLanguage": "nl",
    "name": { "nl": "Intakegesprek", "en": "Initial consultation" },
    "description": {},
    "price": 49.95,
    "currency": "EUR",
    "vatPercentage": 21
  }'
```

## Legacy records and display fallback

When the source language is unknown, `defaultLanguage` is `null`. A legacy
string is returned as `{ "und": "Original text" }`; an empty string becomes
`{}`. Existing maps retain their language codes. GET requests never write data
or guess a language from the company or coach settings.

The first Public API content update of a legacy form, habit template or product
requires an explicit `defaultLanguage`. This declares the language of retained
source strings. Habit templates store translations directly in the text fields;
their internal source-string adapter clears stale translations when the source
changes. Forms and finance products currently retain their legacy string storage
with a separate translation layer; the Public API stops returning stale
translations when an internal edit changes those strings. The public contract
can remain the same when those models later adopt native language maps.

Consumers can choose the requested language, then the declared default language,
then `und`. If none exists, use an explicitly chosen stored language or your
application's missing-text treatment. Missing translations are absent keys;
they are not copies of another language. The API does not perform this fallback
on behalf of the consumer.
