Translatable
- Version
- 4.0 preview
- Updated
A text value per active language, stored in #__configbox_strings — an EAV table, not a column on
the model's own table.
Storage kind: derived · Column: none
Use it for every text a customer reads: product and question titles, answer labels, descriptions,
anything that appears in the storefront. Use string for text that only ever reaches
the operator — an internal note, a SKU, a template name. Getting this wrong is expensive in one
direction only: a string that should have been translatable means a migration plus re-entry of
every value, whereas an over-translated internal field just shows one input per language.
Settings
| Setting | Meaning |
|---|---|
langType | Required. The type discriminator identifying this field's strings in #__configbox_strings |
defaultlabel | Placeholder |
options | ALLOW_RAW / ALLOW_HTML, and USE_TEXTAREA / USE_HTMLEDITOR for the widget |
editorWidth / editorHeight | WYSIWYG size, as for string. Only with USE_HTMLEDITOR |
langType is an integer that must be unique across the whole install — it is the only thing
separating one field's strings from another's in a single shared table. Reusing a number silently
merges two fields' translations.
The form renders one input per active language, so a two-language install shows two boxes.
What a record carries
Three kinds of key, for a property named title:
| Key | Holds |
|---|---|
title | the current language's text |
title_en_GB | the English text |
title_de_DE | the German text |
One per active language. Because that set is per-install, the committed generated artifacts describe only the bare key — a consumer cannot know the per-language keys from the schema alone and must read the install's active languages.
Building the key
$key = KenedoLanguageHelper::getTranslationKey($this->propertyName, $tag); // title_en_GB
Never concatenate it yourself. The key was assembled by hand in six places, and three of them
were only found by chasing a failing test — one of which, checkForDuplicateUrlSegment(), is silent
when the key does not resolve. A key built by concatenation has no single definition to grep for.
Keys used a hyphen (title-en-GB) until 2026-07-27. See the breaking-changes log.
Storage
Reads LEFT JOIN #__configbox_strings on type = langType AND key = <record id> AND language_tag = <tag>, and appendDataForGetRecord() adds the per-language keys.
Writes loop over the active languages and, per language:
- a non-empty text →
REPLACE INTO #__configbox_strings - an empty text →
DELETEof that row
So an emptied translation does not become an empty string, it ceases to exist. Absence and emptiness are the same state here, which is why there is no way to store a deliberately blank translation distinct from an unset one.
delete() removes every language's row for the record, keyed on type + key. It does not
call the parent, which is harmless — a derived property has nothing external to clean up.
check() on a required translatable demands a value in every active language, not just the
current one. Adding a language to a live install therefore makes previously-valid records fail
validation the next time they are saved.
Reads are batched: preload() fetches many records' strings at once and forget() drops the cache,
which the model calls after any write. If you write to #__configbox_strings with hand SQL, call
KenedoPropertyTranslatable::forget() or subsequent reads in the same request serve stale text.
Because it is derived, this property contributes no column to the model's own table, type
generation excludes it from anything describing that table, and storeExternally does not apply.
@see string.md — for text the customer never sees @see ../../migration-to-cb4/breaking-changes-log.md @see ../../customization/com_configbox_language_overrides.md — overriding shipped texts