Mehrsprachige Felder für REDAXO YForm - Unterstützt einfache Text-, Textarea- und Media-Felder mit Editor-Integration (CKEditor 5, TinyMCE und weitere per Attributen).
Ab sofort wird die Editor-Auswahl bei lang_textarea ausschließlich über attributes gesteuert. Das Legacy-Flag editor wird nicht mehr ausgewertet.
Migration:
- Alt: 'editor' => true
- Neu: attributes mit class, z. B. cke5-editor oder tiny-editor
- 3 Feldtypen: Text, Textarea und Media mit vollständiger Mehrsprachigkeit
- Editor-Unterstützung für Textareas: CKEditor 5, TinyMCE und weitere Editoren über Attributübergabe
- REDAXO Mediapool: Nahtlose Integration mit dem REDAXO Media-Widget
- Optionale Textfelder: Zusätzliche Textfelder für Media (Alt-Text, Captions, etc.)
- Kompaktes MBlock-Design: Moderne Panel-basierte UI mit Delete-Buttons im Header
- Dynamisches Hinzufügen/Entfernen: Sprachen können jederzeit hinzugefügt oder entfernt werden
- Beschreibungstexte: Optionale Hilfstexte für alle Feldtypen
- Responsive Design: Optimiert für Desktop und Mobile
- Addon in
/redaxo/src/addons/yform_lang_fields/entpacken - Im REDAXO-Backend unter "Addons" installieren und aktivieren
- Sicherstellen, dass YForm (>= 4.0) installiert ist
- Mindestens eine Sprache in REDAXO konfiguriert haben
- REDAXO >= 5.15
- YForm >= 4.0
- Mediapool Addon (für Media-Felder)
- Font Awesome 6 (für Icons)
- Mindestens eine konfigurierte Sprache
Mehrsprachiges einzeiliges Textfeld.
$yform->setValueField('lang_text', [
'name' => 'title',
'label' => 'Titel',
'description' => 'Der Haupttitel der Seite'
]);Mehrsprachiges mehrzeiliges Textfeld mit Attribut-basierter Editor-Integration.
Ohne Editor:
$yform->setValueField('lang_textarea', [
'name' => 'description',
'label' => 'Beschreibung',
'rows' => 5,
'description' => 'Kurze Beschreibung'
]);Mit CKEditor 5:
$yform->setValueField('lang_textarea', [
'name' => 'content',
'label' => 'Inhalt',
'rows' => 10,
'attributes' => json_encode([
'class' => 'cke5-editor',
'profile' => 'default', // Alias für data-profile
'lang' => 'de' // Alias für data-lang (optional)
])
]);Mit TinyMCE:
$yform->setValueField('lang_textarea', [
'name' => 'content',
'label' => 'Inhalt',
'rows' => 10,
'attributes' => json_encode([
'class' => 'tiny-editor', // alternativ: tinyMCEEditor
'profile' => 'default'
])
]);Hinweis zu Attributen:
profilewird automatisch aufdata-profilegemappt.langwird automatisch aufdata-langgemappt.- Eigene Attribute werden beim Rendern und beim dynamischen Hinzufügen weiterer Sprachen übernommen.
Mehrsprachiges Media-Feld mit REDAXO Mediapool-Integration.
Einfaches Media-Feld:
$yform->setValueField('lang_media', [
'name' => 'image',
'label' => 'Bild',
'preview' => true,
'types' => 'jpg,png,gif',
'category' => 1
]);Media mit Textfeld (Alt-Text, Caption, etc.):
$yform->setValueField('lang_media', [
'name' => 'hero_image',
'label' => 'Hero Bild',
'preview' => true,
'with_text' => true,
'text_label' => 'Bildunterschrift',
'description' => 'Hauptbild für die Startseite'
]);- Neue Spalte erstellen
- Feldtyp wählen:
lang_text,lang_textareaoderlang_media - Parameter konfigurieren (siehe unten)
- Speichern
$yform = new rex_yform();
$yform->setObjectparams('form_name', 'my_form');
// Text-Feld
$yform->setValueField('lang_text', [
'name' => 'title',
'label' => 'Titel',
'description' => 'Der Seitentitel'
]);
// Textarea mit CKEditor 5 (Attribut-basiert)
$yform->setValueField('lang_textarea', [
'name' => 'content',
'label' => 'Inhalt',
'attributes' => json_encode([
'class' => 'cke5-editor',
'profile' => 'default'
])
]);
// Media mit Textfeld
$yform->setValueField('lang_media', [
'name' => 'image',
'label' => 'Bild',
'with_text' => true,
'text_label' => 'Alt-Text'
]);| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
name |
string | - | Feldname (Pflicht) |
label |
string | - | Feldbezeichnung (Pflicht) |
description |
string | '' | Optionaler Hilfstext |
notice |
string | '' | Hinweistext |
required |
bool | false | Pflichtfeld |
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
attributes |
json | '' | Zusätzliche HTML-Attribute |
use_writeassist |
bool | false | KI-Übersetzungs-Button (WriteAssist) für Zielsprachen aktivieren |
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
rows |
int | 5 | Anzahl der Zeilen |
attributes |
json | '' | Zusätzliche HTML-Attribute inkl. Editorsteuerung (class, optional profile, optional lang) |
use_writeassist |
bool | false | KI-Übersetzungs-Button (WriteAssist) für Zielsprachen aktivieren |
| Parameter | Typ | Standard | Beschreibung |
|---|---|---|---|
types |
string | '' | Erlaubte Dateitypen (z.B. 'jpg,png,gif') |
category |
int | '' | Mediapool-Kategorie ID |
preview |
bool | true | Bildvorschau anzeigen |
with_text |
bool | false | Zusätzliches Textfeld aktivieren |
text_label |
string | 'Beschreibung' | Label für Textfeld |
Ist das AddOn WriteAssist installiert und aktiviert, kann pro lang_text- oder lang_textarea-Feld die KI-Übersetzung eingeschaltet werden:
$yform->setValueField('lang_text', [
'name' => 'title',
'label' => 'Titel',
'use_writeassist' => true
]);Ist die Option aktiv, erscheint bei jeder Zielsprache (außer der Primärsprache) ein Button, der den Text der Primärsprache per Klick via DeepL/OpenAI übersetzt und in das Zielfeld einträgt. Die Zielsprache wird dabei, sofern verfügbar, über AutoTranslateService::getDeeplCode() von WriteAssist aufgelöst (korrektes Mapping z. B. für en_gb → EN-GB, pt_br → PT-BR).
In der YForm-Datensätzeliste wird je Sprachfeld die erste im Datensatz gespeicherte Übersetzung gekürzt angezeigt. Über ein Sprachumschalter-Dropdown in der Toolbar kann die angezeigte Sprache für alle Sprachfelder in der Liste live umgeschaltet werden (die Auswahl wird im Browser gemerkt). Zusätzliche Übersetzungen erscheinen als kleiner Badge, der beim Hovern/Fokussieren ein Popover ohne Layout-Sprung einblendet. Über die Option "Fehlende hervorheben" im selben Dropdown lassen sich Datensätze markieren, die nicht in allen online geschalteten Sprachen übersetzt sind.
Die eigentliche URL-Erzeugung bleibt im URL-Addon bzw. in YRewrite. Das YForm-Lang-Fields-Addon liefert nur den mehrsprachigen Inhalt, zum Beispiel den Titel, den man dann im URL-Addon pro Sprache in einen Slug übersetzen kann.
Ein typisches Muster im URL_PRE_SAVE-Hook ist:
rex_extension::register('URL_PRE_SAVE', static function (rex_extension_point $ep) {
$url = $ep->getSubject();
$params = $ep->getParams();
if (!$url instanceof \Url\Url) {
return $url;
}
$clangId = (int) ($params['clang_id'] ?? 0);
$dataId = (int) ($params['data_id'] ?? 0);
if ($clangId < 1 || $dataId < 1) {
return $url;
}
$dataset = \News::get($dataId); // Beispiel: eigene Modelklasse
if (!$dataset) {
return $url;
}
$title = (string) $dataset->getLangValue('title', $clangId);
if ('' === trim($title)) {
return $url;
}
$slug = rex_string::normalize($title);
$path = trim($url->getPath(), '/');
$parts = '' === $path ? [] : explode('/', $path);
if (!empty($parts)) {
$parts[count($parts) - 1] = $slug;
return new \Url\Url('/' . implode('/', $parts) . '/');
}
return $url;
});Wenn ihr keine Modelklasse verwendet, könnt ihr die Übersetzung auch direkt aus dem JSON-Feld laden und den passenden clang_id-Eintrag herausfiltern.
$yform = new rex_yform();
$yform->setObjectparams('form_name', 'blog_article');
// Titel
$yform->setValueField('lang_text', [
'name' => 'title',
'label' => 'Titel',
'description' => 'Der Artikeltitel',
'required' => true
]);
// Teaser
$yform->setValueField('lang_textarea', [
'name' => 'teaser',
'label' => 'Teaser',
'rows' => 3,
'description' => 'Kurze Zusammenfassung'
]);
// Hauptinhalt mit TinyMCE (Attribut-basiert)
$yform->setValueField('lang_textarea', [
'name' => 'content',
'label' => 'Inhalt',
'rows' => 15,
'attributes' => json_encode([
'class' => 'tiny-editor',
'profile' => 'default'
])
]);
// Titelbild mit Alt-Text
$yform->setValueField('lang_media', [
'name' => 'featured_image',
'label' => 'Beitragsbild',
'preview' => true,
'with_text' => true,
'text_label' => 'Alt-Text',
'types' => 'jpg,png,webp',
'description' => 'Hauptbild des Artikels'
]);// Rohdaten (JSON-String)
$titleJson = $dataset->getValue('title');
// Gibt zurück: '[{"clang_id":1,"value":"Deutscher Titel"},{"clang_id":2,"value":"English Title"}]'
// Als Array mit LangHelper
$titleArray = \KLXM\YformLangFields\LangHelper::normalizeLanguageData($titleJson);
// Gibt zurück: [
// ['clang_id' => 1, 'value' => 'Deutscher Titel'],
// ['clang_id' => 2, 'value' => 'English Title']
// ]
// Wert für spezifische Sprache
$germanTitle = \KLXM\YformLangFields\LangHelper::getValueForLanguage($titleJson, 1);
// Gibt zurück: 'Deutscher Titel'use KLXM\YformLangFields\LangDataset;
class BlogArticle extends LangDataset
{
}
// Modelklasse für die YForm-Tabelle registrieren (z.B. im boot.php des eigenen AddOns)
rex_yform_manager_dataset::setModelClass('rex_blog_article', BlogArticle::class);
// Jetzt automatisch als Array!
$article = BlogArticle::get(1);
$titleArray = $article->getValue('title');
// Gibt zurück: [
// ['clang_id' => 1, 'value' => 'Deutscher Titel'],
// ['clang_id' => 2, 'value' => 'English Title']
// ]
// Convenience-Methoden
$currentTitle = $article->getLang('title'); // Aktuelle Sprache
$germanTitle = $article->getLangValue('title', 1); // Spezifische Sprache
$allTitles = $article->getAllLangValues('title'); // [1 => 'Deutscher Titel', 2 => 'English Title']
// Wert setzen
$article->setLangValue('title', 1, 'Neuer Titel');
$article->save();
// Raw JSON wenn nötig
$titleJson = $article->getRawValue('title');KLXM\YformLangFields\LangQuery erweitert rex_yform_manager_query um JSON-bewusste WHERE/ORDER BY/SELECT-Bausteine für Sprachfelder:
use KLXM\YformLangFields\LangQuery;
// Datensätze mit Übersetzung in einer bestimmten Sprache
$articles = LangQuery::get('rex_blog_article')
->whereTranslationExists('title', 2)
->find();
// Textsuche innerhalb einer Sprache
$hits = LangQuery::get('rex_blog_article')
->whereTranslationLike('title', 1, 'Redaxo')
->find();
// Sortierung nach übersetztem Feld
$sorted = LangQuery::get('rex_blog_article')
->orderByTranslation('title', 1, 'DESC')
->find();
// Vollständig / unvollständig übersetzte Datensätze
$incomplete = LangQuery::get('rex_blog_article')
->whereIncompleteTranslations(['title', 'teaser'], [1, 2])
->find();Alle Methoden nutzen JSON_TABLE(), das MySQL ≥ 8.0.4 bzw. MariaDB ≥ 10.6 voraussetzt.
Daten werden als JSON-Array gespeichert:
[
{"clang_id": 1, "value": "Deutscher Text"},
{"clang_id": 2, "value": "English text"}
][
{"clang_id": 1, "value": "bild.jpg"},
{"clang_id": 2, "value": "image.jpg"}
][
{
"clang_id": 1,
"value": {
"media": "bild.jpg",
"text": "Bildbeschreibung"
}
},
{
"clang_id": 2,
"value": {
"media": "image.jpg",
"text": "Image description"
}
}
]Das Addon verwendet ein modernes, kompaktes Panel-Layout im MBlock-Stil:
- Panel-Header: Flaggen-Symbol, Sprachname und Delete-Button (oben rechts)
- Panel-Body: Feld-Content ohne zusätzliche Wrapper
- Halbtransparenter Hintergrund: 60% weißer Hintergrund für bessere Lesbarkeit
- Responsive: Optimiert für alle Bildschirmgrößen
CSS-Anpassungen können in /assets/lang-fields.css vorgenommen werden:
/* Wrapper-Hintergrund ändern */
.yform-lang-field {
background-color: rgba(255, 255, 255, 0.8); /* 80% statt 60% */
}
/* Panel-Farben anpassen */
.lang-field-item.panel {
border-color: #0066cc;
}yform_lang_fields/
├── assets/
│ ├── lang-fields.css # Styling
│ └── lang-fields.js # JavaScript-Funktionalität
├── lang/
│ ├── de_de.lang # Deutsche Übersetzungen
│ └── en_gb.lang # Englische Übersetzungen
├── lib/
│ ├── LangHelper.php # Helper-Klasse
│ ├── rex_yform_value_lang_text.php
│ ├── rex_yform_value_lang_textarea.php
│ └── rex_yform_value_lang_media.php
├── ytemplates/
│ └── bootstrap/
│ └── value.lang_field.tpl.php # Haupt-Template
├── boot.php # Addon-Bootstrap
├── package.yml # Addon-Konfiguration
└── README.md # Diese Datei
Das Addon verwendet die GPL-Version von CKEditor 5 mit dem "ck"-Präfix für IDs:
// Automatische Initialisierung
initCKE5ForNewField: function($field) {
var $textarea = $field.find('.cke5-editor');
if ($textarea.length && typeof ClassicEditor !== 'undefined') {
var editorId = 'ck' + $textarea.attr('id'); // GPL-konform
ClassicEditor.create(document.getElementById(editorId))
.catch(error => console.error(error));
}
}Das Addon nutzt die REDAXO Media-Widget-Funktionen:
// Numerische IDs für Media-Widgets
static $widgetCounter = 0;
$widgetCounter++;
$widgetId = $widgetCounter;
// Widget öffnen
openREXMedia(<?= $widgetId ?>, '<?= $mediaParams ?>');
// Widget löschen
deleteREXMedia(<?= $widgetId ?>);// Sprache hinzufügen
$(document).on('click', '.btn-add-lang-field', function(e) {
e.preventDefault();
YformLangFields.addLanguageField($(this));
});
// Sprache entfernen
$(document).on('click', '.btn-remove-lang-field', function(e) {
e.preventDefault();
YformLangFields.removeLanguageField($(this));
});- Stelle sicher, dass CKEditor 5 im Backend geladen wird
- Prüfe die Browser-Konsole auf JavaScript-Fehler
- Verwende das "ck"-Präfix für Editor-IDs (GPL-konform)
- Prüfe, ob das Mediapool-Addon installiert ist
- Stelle sicher, dass numerische IDs verwendet werden (keine Strings)
- Überprüfe die Browser-Konsole auf Fehler
- Mindestens eine Sprache muss in REDAXO konfiguriert sein
- Prüfe die Sprachkonfiguration unter System > Sprachen
- Stelle sicher, dass JavaScript geladen wird
- Prüfe auf CSS z-index Konflikte
- Überprüfe die Browser-Konsole auf Fehler
MIT License
KLXM Crossmedia / Thomas Skerbis
Website: https://klxm.de
Mit Unterstützung von GitHub Copilot
Version: 1.3.0
Letztes Update: September 2026