# Language Overrides (changing displayed text)

> How to change the wording of CBX's interface text — relabel a field, reword a tooltip, retitle a button — without editing core language files. This is the la…

Source: CBX documentation, version 3.x (released). Canonical page: https://docs.configbox.at/docs/customization/language_overrides. Last updated 2026-08-01.

---
How to **change the wording** of CBX's interface text — relabel a field, reword a tooltip, retitle a
button — without editing core language files. This is the `language_overrides/` mechanism: a single
`overrides.ini` per language whose entries are loaded **last**, so they win over the shipped strings.

It is a small mechanism, but it comes with one essential distinction: CBX has **two** text systems,
and `overrides.ini` only touches one of them. §1 sorts that out; get it right and the rest is a one-file
edit.

Read `com_configbox_customization_overview.md` first. All paths are relative to the component root
`docroot/components/com_configbox/`. Source references are point-in-time (component **3.4.1**) — verify
against the code.

---

## 1. Which text system are you changing? (read this first)

| If the text is… | It comes from… | Change it via… |
|---|---|---|
| **Static UI text** — field labels, tooltips, buttons, messages, headings (keyed strings like `FIELD_LABEL_COMPONENT_SKU`) | the **INI language files** (`language/<tag>/frontend.ini` / `backend.ini`), resolved by `KText::_()` | **`language_overrides/<tag>/overrides.ini`** — this guide |
| **Admin-authored content** — a product's name, a page's description, an option's label, country names | the **EAV `#__configbox_strings` table**, edited per-language in the admin (via the `translatable` property type) | **the admin UI** (edit the record in that language), **not** `overrides.ini` |

`overrides.ini` overrides **static UI strings only**. Content strings live in the database and are
translated by editing the record in each active language — there is no file override for them. (This is the
EAV i18n system the project notes refer to; it is separate from the INI files this guide covers.)

> Quick test: if the text has an **ALL_CAPS key** and is the same for every product (a label, a button, an
> error), it's an INI string → `overrides.ini`. If it's specific to one product/option/page and an admin
> typed it, it's a content string → edit it in the admin.

---

## 2. How the override is resolved

`KText::getLanguageFiles()` (`external/kenedo/classes/KText.php:173-207`) builds the list of INI files to
load for the active language, **in this order**:

```
language/en-GB/frontend.ini          ← en-GB is the built-in fallback (loaded first)
language/<tag>/frontend.ini          ← the active language
language/en-GB/backend.ini  ┐ (admin pages only)
language/<tag>/backend.ini  ┘
data/customization/language_overrides/<tag>/overrides.ini   ← YOUR overrides (loaded LAST)
```

Each file is parsed and merged into one map, **later files overwriting earlier keys** (`load()`,
`:214-234`):

```php
foreach ($strings as $key => $string) {
    self::$strings[$languageTag][$key] = $string;   // last write wins
}
```

Because `overrides.ini` is loaded **last** (`:195`), any key you set there beats the shipped value — for
both frontend and backend strings of that language. Missing files are skipped silently (`:199-203`), so you
only ship the `overrides.ini` for languages you actually customize.

> **Per language tag.** The override file is keyed by the **active language tag** (`<tag>`, e.g. `en-US`,
> `de-DE`). To override the same string in two languages, add an `overrides.ini` under each tag's folder.
> The active tag is the platform language if it's an active CB language, else the CB default
> (`determineLanguage()`, `:151-165`).

---

## 3. Finding the key to override

Keys are looked up **upper-cased** (`_()` does `strtoupper($key)`, `:261`), so override keys are by
convention ALL_CAPS and match case-insensitively. To find the key for a piece of text:

- **Grep the shipped INI files** for the visible text:
  `grep -ri "SKU/Code Segment" language/` → gives you the key (e.g. `FIELD_LABEL_COMPONENT_SKU`).
- Or grep templates/code for the `KText::_('SOME_KEY')` call that emits it.

Then copy that key into your `overrides.ini` with your new value.

> Two lookup quirks worth knowing (rarely needed, but they explain odd keys):
> - **Trailing colon** — `KText::_('Name:')` will match a key `NAME` and re-append the colon (`:258-266`).
>   So override `NAME`, not `NAME:`.
> - **Parentheses** in keys are encoded to `__POPEN__` / `__PCLOSE__` (`:255-256`). You'll see these in the
>   shipped files; match them if you override such a key.

---

## 4. Writing `overrides.ini`

Standard PHP INI syntax (`parse_ini_file`, `:221`): `KEY="value"`, one per line; **quote the value** and
keep it on one line. From the shipped `en-US/overrides.ini` (real example):

```ini
; data/customization/language_overrides/en-US/overrides.ini

FIELD_LABEL_COMPONENT_SKU="SKU/Code Segment"
TOOLTIP_COMPONENT_SKU="This code will be included in the configuration code. Enter (none) if you don't want this answer to add to the code."

FIELD_LABEL_QUESTION_HEADING="Heading (generic for both metric and imperial)"
TOOLTIP_QUESTION_HEADING="Tell what should show in the heading above the question"
```

Each line replaces the shipped string for that key in that language. INI gotchas:

- **Always quote values** (`KEY="…"`). Unquoted values with spaces/special characters break parsing or get
  truncated.
- **Escape inner double quotes** or use single quotes inside, and avoid raw `"` mid-value.
- **No multi-line values.** Keep each string on one line.
- A parse error logs and the whole override file is skipped (`:223-226`) — if your overrides "don't apply",
  check the INI syntax first.

---

## 5. Deployment checklist

```
data/customization/
  language_overrides/
    <tag>/                         ← one folder per language tag you customize (e.g. en-US, de-DE)
      overrides.ini                ← KEY="new value" lines; loaded LAST, wins over shipped strings
```

1. **Confirm it's a UI/INI string**, not admin content (§1). Content → edit in the admin instead.
2. **Find the key** by grepping `language/` for the current text (§3).
3. **Add `KEY="new value"`** to `data/customization/language_overrides/<tag>/overrides.ini`, quoted, one
   line (§4).
4. **Repeat per language** — one `overrides.ini` per tag.
5. **Verify manually** — reload the screen in that language and confirm the new text (and that you didn't
   break the INI: a syntax error silently drops *all* your overrides). CBX has **zero automated
   tests**.

---

## 6. Conventions & gotchas

- **`overrides.ini` is for INI/UI strings only.** Admin-authored content lives in `#__configbox_strings`
  and is translated in the admin, not here (§1).
- **Loaded last → wins.** Your keys override shipped frontend *and* backend strings for that language
  (`:195`).
- **Keys are upper-cased on lookup** — write ALL_CAPS keys; matching is case-insensitive (§3).
- **Per language tag** — add an `overrides.ini` under each `<tag>/` you want to change.
- **Quote values, single line, valid INI** — a parse error drops the whole file silently (§4).
- **Find keys by grepping `language/`** for the visible text, or for the `KText::_('KEY')` call.
- **Override `NAME`, not `NAME:`** — trailing colons are handled automatically (§3).

---

## See also

- `com_configbox_customization_overview.md` — the extension-point map (this is a **merge/append** mechanism:
  your file is combined with the shipped INI files, loaded last).
- `external/kenedo/classes/KText.php` — `getLanguageFiles()` (`:173`, the load order),
  `load()` (`:214`, last-write-wins), `_()` (`:241`, the lookup + quirks).
- `language/<tag>/` — the shipped INI files to grep for keys and copy from.
- `external/kenedo/properties/translatable.php` — the `translatable` property type that stores
  admin-authored content strings in `#__configbox_strings` (the *other* i18n system, not overridable here).
