Zum Inhalt springen

Einleitung

Wer das erste Mal ein mehrsprachiges Formular mit EXT:form erstellen möchte, wird wahrscheinlich auf die in TYPO3 üblichen LLL-Strings zurückgreifen wollen (z.B. „LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:localized.label“). Diese können aber in den Form-Definitionen nicht ausgelesen werden.

Stattdessen muss man in seiner Lokalisierungsdatei (locallang.xlf) einen genauen Aufbau der Übersetzungs-Schlüssel beachten, dann werden die Übersetzungen vom TYPO3 Form Framework herangezogen.

Mit Schlüssel ist der String gemeint, den man in der Sprachdatei im <trans-unit>-Element innerhalb des id-Attributs einträgt.
Beispiel: <trans-unit id="element.email.properties.label">

Inzwischen ist die offizielle Dokumentation des Form Frameworks auch im Bereich Übersetzungen gut ausgebaut und hilfreich. In diesem Tutorial möchte ich dennoch ein paar Bereiche vertiefen und ergänzen.
Zuerst erkläre ich die allgemeine Arbeitsweise, dann folgen die Beispiele für die verschiedenen Bereiche. Am Ende zeige ich noch ein paar Tricks, z.B. wie man eine mehrsprachige Anrede in den E-Mails erhält.

Ich werde diese Beispiele auf dem aktuellen Stand halten und bei Bedarf erweitern.

Wichtige Unterschiede zwischen TYPO3-Versionen

  • Bei der Registrierung von Sprachdateien ist zu beachten: in TYPO3 v8 und v9 trägt die YAML-Option den Namen translationFile. In TYPO3 v10 wurde sie umbenannt in translationFiles (TYPO3 Changelog)!
    In TYPO3 v8 und v9 müsst ihr zudem die Sprachdatei von EXT:form zwingend mitladen, da sie in der Standard-Konfiguration nicht in der obigen Array-Schreibweise geladen wird.
  • Ab TYPO3 v12 können die ersten drei Zeilen mit dem Namespace TYPO3.CMS.Form in der Form Configuration entfallen (TYPO3 Changelog).
  • Ab TYPO3 v12 gibt es die neue Option $GLOBALS['TYPO3_CONF_VARS']['SYS']['lang']['requireApprovedLocalizations']. Standardmäßig ist diese aktiv. Als Folge müsst ihr bei euren XLIFF-Übersetzungen das Attribut approved="yes" ergänzen (TYPO3 Changelog).

Vorbereitungen

Um Übersetzungen anlegen zu können, benötigst du eine oder mehrere XLF-Dateien, welche du dann in deiner Form-Konfiguration verlinkst. Ich empfehle dir, eine eigene TYPO3-Extension eigens für die Formulare, Formular-Templates und die betreffenden Konfigurationen anzulegen. Die grundlegende Einrichtung einer solchen Extension beschreibe ich im ersten Tutorial der Form-Reihe.

Die grundlegende Funktionsweise von Übersetzungsdateien (locallang.xlf) wird in dieser Dokumentation gut erklärt.

EXT:form stellt bereits eine Sprachdatei mit ein paar Schlüsseln zur Verfügung. Weitere Sprachdateien musst du daher in deiner Form-Konfiguration in der Array-Schreibweise von YAML registrieren, ohne das Original zu überschreiben.

Ab TYPO3 v10 könnt ihr im Backend prüfen, welche Form-Konfigurationen aktuell verwendet werden. Ihr findet diese neue Übersicht im Modul SYSTEM > Konfiguration (EXT:lowlevel muss installiert sein).

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:
          formElementsDefinition:
            Form:
              renderingOptions:
                translation:
                  # Beachte die Schreibweise: 'translationFiles' erst ab TYPO3 v10! Vorher 'translationFile'.
                  translationFiles:
                    # Standard-Sprachdatei von EXT:form (wird immer benötigt, muss aber nur in TYPO3 v8 und v9 manuell verlinkt werden):
                    10: 'EXT:form/Resources/Private/Language/locallang.xlf'
                    # Lade deine eigenen Sprachdateien nachfolgend:
                    20: 'EXT:form_examples/Resources/Private/Language/locallang.xlf'

Erläuterung

Vorab: Eine Formular-Definition als Beispiel

Dies ist die Definition für ein einfaches Kontaktformular. Wie du sehen kannst, haben alle Elemente (renderables) sowohl einen festgelegten Typ (Page, Text, ...) als auch einen frei wählbaren Identifier. Ausnahmen sind etwa die Finisher, die nur über einen Identifier angesprochen werden.

In den folgenden Beispielen werden wir einige dieser Elemente über den Identifier oder Type übersetzen.

renderingOptions:
  submitButtonLabel: Submit
identifier: MyForm
label: 'My form example'
type: Form
prototypeName: standard
finishers:
  -
    options:
      subject: 'Message from your website'
      recipientAddress: your.company@example.com
      recipientName: 'Your Company name'
      senderAddress: '{email}'
      senderName: '{lastname}'
      replyToAddress: ''
      carbonCopyAddress: ''
      blindCarbonCopyAddress: ''
      format: html
      attachUploads: true
    identifier: EmailToReceiver
  -
    options:
      message: 'Thank you for your message! We will get back to you as soon as possible.'
    identifier: Confirmation
renderables:
  -
    renderingOptions:
      previousButtonLabel: 'Previous step'
      nextButtonLabel: 'Neue Seite'
    identifier: page-1
    label: 'Contact Form'
    type: Page
    renderables:
      -
        properties:
          options:
            male: Mr
            female: Ms
          prependOptionLabel: 'Please choose ...'
          fluidAdditionalAttributes:
            required: required
        type: SingleSelect
        identifier: title
        label: Title
        validators:
          -
            identifier: NotEmpty
      -
        defaultValue: ''
        identifier: lastname
        label: 'Last name'
        type: Text
        properties:
          fluidAdditionalAttributes:
            placeholder: 'Your last name'
        validators:
          -
            identifier: NotEmpty
      -
        defaultValue: ''
        identifier: subject
        label: Subject
        type: Text
        properties:
          fluidAdditionalAttributes:
            placeholder: Subject
        validators:
          -
            identifier: NotEmpty
      -
        defaultValue: ''
        identifier: email
        label: Email
        type: Text
        properties:
          fluidAdditionalAttributes:
            placeholder: 'Email address'
        validators:
          -
            identifier: NotEmpty
          -
            identifier: EmailAddress
      -
        defaultValue: ''
        identifier: message
        label: Message
        type: Textarea
        properties:
          fluidAdditionalAttributes:
            placeholder: ''
        validators:
          -
            identifier: NotEmpty
  -
    renderingOptions:
      previousButtonLabel: 'Previous step'
      nextButtonLabel: 'Neue Seite'
    identifier: summarypage
    label: 'Summary page'
    type: SummaryPage

Übersetzungs-Schlüssel und die Fallback-Kette

Die folgende Erklärung ähnelt sehr der offiziellen Dokumentation, da dieser Teil kaum verbessert werden kann:

Wie zu Beginn angemerkt, folgen die Schlüssel in der Übersetzungsdatei einem genau festgelegten Schema. Außerdem gibt es mehrere Fallbacks, durch die man Werte für einzelne Formulare nochmals überschreiben kann.

Dadurch erhält man die folgenden Möglichkeiten zur Übersetzung:

  1. Übersetzung einer Eigenschaft für ein bestimmtes Formular und ein bestimmtes Formular-Element
  2. Übersetzung einer Eigenschaft eines bestimmten Formular-Elements in verschiedenen Formularen zugleich
  3. Übersetzung einer Eigenschaft für einen Elementen-Typ (z.B. alle vom Typ SummaryPage) in verschiedenen Formularen

Diese Möglichkeiten werden durch die folgenden Schlüssel erreicht:

  1. <form-definition-identifier>.element.<element-identifier>.properties.<property-name>
  2. element.<element-identifier>.properties.<property-name>
  3. element.<element-type>.properties.<property-name>

Was sind nun diese Schlüssel-Bestandteile? Nehmen wir das obige Beispielformular:

  • <form-definition-identifier>: Die eindeutige Bezeichner des Formulars, hier: "MyForm"
  • <element-type>: Alle Textfelder sind vom Typ "Text", alle Übersichtsseiten vom Typ "SummaryPage", etc.
  • <element-identifier>: Ein Identifier jedes Felds wird durch das Formular-Modul im Backend gesetzt, kann aber nach Wunsch angepasst werden. Du kannst z.B. "nachname" als Identifier für das entsprechende Feld in allen deinen Formularen setzen, um sie dann einheitlich übersetzen zu können.
  • <property-name>: Die Eigenschaften jedes Felds, z.B. label oder placeholder

Die Übersetzungen sucht das Form Framework in der oben genannten Reihenfolge (von spezifisch nach allgemein).

Nur wenn gar kein möglicher Wert in einer Sprachdatei existiert, wird der Wert in der Form-Definition verwendet.

Andersherum gesagt: sobald die XLIFF-Datei einen passenden Schlüssel enthält, wird der Wert in YAML nicht mehr ausgelesen! Das Label im <source> Attribut der XLIFF-Datei ist dann der Fallback für alle Sprachen.

Auf diese Weise kann man einen allgemeinen Wert z.B. für alle Betreff-Felder anlegen und diesen für einzelne Formulare nach Wunsch überschreiben.

Im Folgenden gehe ich auf die verschiedenen Bereiche ein, die im TYPO3 Form Framework übersetzt werden können.

Übersetzungen

Übersetzen von Formular-Labels

Beginnen wir mit den normalen <label> Elementen für die erstellten Formularfelder. Im Folgenden liste ich erst die anwendbaren Schlüssel in der Reihenfolge auf, in der sie in den Sprachdateien gesucht werden. Anschließend folgt ein Beispiel, wie man sie in einer Sprachdatei benutzen würde.

Übersetzungs-Schlüssel:

  1. <form-identifier>.element.<element-identifier>.properties.label
  2. element.<element-identifier>.properties.label
  3. element.<element-type>.properties.label

Beispiele:

<trans-unit id="MyForm.element.subject.properties.label" approved="yes">
    <source>Subject</source>
    <target>Betreff</target>
</trans-unit>
<trans-unit id="element.lastname.properties.label" approved="yes">
    <source>Last name</source>
    <target>Nachname</target>
</trans-unit>

Übersetzen von Formularseiten-Labels

Bei den Labels von Formular-Seiten gibt es zwei Fallstricke zu beachten:

  1. Labels aus XLIFF werden nur dann gerendert, wenn auch ein Label-Wert in der Form-Definition vorhanden ist!
  2. Neben dem Identifier der Seite muss immer auch der Identifier des Formulars angegeben werden.

Formular-Definition (Ausschnitt):

renderables:
  -
    renderingOptions:
      previousButtonLabel: 'Previous step'
      nextButtonLabel: 'Next step'
    identifier: page-1
    label: 'Contact Form'
    type: Page

Übersetzungs-Schlüssel:

  • <form-identifier>.element.<element-identifier>.properties.label

Beispiel:

<trans-unit id="BasicContactFormExample.element.page-1.properties.label" approved="yes">
    <source>Basic contact form</source>
    <target>Einfaches Kontaktformular</target>
</trans-unit>

 

Übersetzen von Auswahllisten (Select-Optionen), Radio-Buttons und Multiselect-Checkboxen

Auswahllisten werden beispielsweise für die Anrede verwendet. Sie bestehen aus einem <select> Element und mehreren <option> Elementen. Du kannst zum einen die Auswahlmöglichkeiten übersetzen, zum anderen das Label, das zu Beginn angezeigt wird (etwa "Bitte wählen...").

Die Multi-Checkboxen und Radio-Buttons werden auf die gleiche Weise übersetzt: die gesetzten Auswahlmöglichkeiten sind immer unterhalb von options verfügbar.

Übersetzungs-Schlüssel:

  1. <form-identifier>.element.<element-identifier>.properties.prependOptionLabel
  2. element.<element-identifier>.properties.prependOptionLabel
  1. <form-identifier>.element.<element-identifier>.properties.options.<value>
  2. element.<element-identifier>.properties.options.<value>

Gut zu wissen: Übersetzungs-Schlüssel dürfen auch Leerzeichen und Sonderzeichen enthalten. Punkte könnt ihr ebenfalls verwenden, obwohl sie als Trennzeichen im Schlüssel dienen. Bei der Wahl von Options-Werten seid ihr also nicht beschränkt.

Beispiele:

<trans-unit id="element.title.properties.prependOptionLabel" approved="yes">
    <source>Please choose ...</source>
    <target>Bitte wählen ...</target>
</trans-unit>
<trans-unit id="element.title.properties.options.male" approved="yes">
    <source>Mr</source>
    <target>Herr</target>
</trans-unit>
<trans-unit id="element.title.properties.options.female" approved="yes">
    <source>Ms</source>
    <target>Frau</target>
</trans-unit>
<trans-unit id="element.weight.properties.options.1,5 kg" approved="yes">
    <source>1.5 kilogram</source>
    <target>1,5 Kilogramm</target>
</trans-unit>
<trans-unit id="element.length.properties.options.1.000 m" approved="yes">
    <source>1.000 metres</source>
    <target>1.000 Meter</target>
</trans-unit>

Übersetzen von Platzhalter-Attributen

Platzhalter unterscheiden sich bei der Lokalisierung nur durch den Eigenschaftsnamen am Ende.

Übersetzungs-Schlüssel:

  1. <form-identifier>.element.<element-identifier>.properties.placeholder
  2. element.<element-identifier>.properties.placeholder
  3. element.<element-type>.properties.placeholder

Beispiele:

<trans-unit id="element.lastname.properties.placeholder" approved="yes">
    <source>Your last name</source>
    <target>Ihr Nachname</target>
</trans-unit>
<trans-unit id="MyForm.element.message.properties.placeholder" approved="yes">
    <source>How can we be of help?</source>
    <target>Wie können wir Ihnen behilflich sein?</target>
</trans-unit>

Übersetzen von Formular-Buttons

Bei den Buttons gibt es ein paar Abweichungen zu beachten. Beim submit-Button steht der Formular-Identifier an zweiter Stelle, was ein Bug zu sein scheint (Stand: Oktober 2018).

Auch bei den Buttons zum Aufruf der nächsten oder vorherigen Formular-Seite gibt es Besonderheiten:

  1. Die Label setzt man getrennt für normale Seiten und Übersichtsseiten.
  2. Den Form-Identifier kann man nicht zusammen mit dem allgemeinen Element-Type verwenden. Abhilfe schafft hier der Element-Identifier.

Für mehr Übersicht habe ich die globale und die individuelle Übersetzung voneinander getrennt:

Übersetzungs-Schlüssel (global):

  • element.Form.renderingOptions.submitButtonLabel
  • element.Page.renderingOptions.previousButtonLabel
  • element.Page.renderingOptions.nextButtonLabel
  • element.SummaryPage.renderingOptions.previousButtonLabel
  • element.SummaryPage.renderingOptions.nextButtonLabel

Beispiele (global):

<trans-unit id="element.Form.renderingOptions.submitButtonLabel" approved="yes">
    <source>Send message</source>
    <target>Nachricht senden</target>
</trans-unit>
<trans-unit id="element.Page.renderingOptions.nextButtonLabel" approved="yes">
    <source>continue</source>
    <target>weiter</target>
</trans-unit>
<trans-unit id="element.SummaryPage.renderingOptions.previousButtonLabel" approved="yes">
    <source>back</source>
    <target>zurück</target>
</trans-unit>

Übersetzungs-Schlüssel (individuell):

  • element.<form-identifier>.renderingOptions.submitButtonLabel
  • <form-identifier>.element.<element-identifier>.renderingOptions.nextButtonLabel
  • element.<element-identifier>.renderingOptions.nextButtonLabel
  • <form-identifier>.element.<element-identifier>.renderingOptions.previousButtonLabel
  • element.<element-identifier>.renderingOptions.previousButtonLabel

Beispiele (individuell):

<trans-unit id="element.MyForm.renderingOptions.submitButtonLabel" approved="yes">
    <source>Send message!</source>
    <target>Nachricht senden!</target>
</trans-unit>
<trans-unit id="MyForm.element.page-1.renderingOptions.nextButtonLabel" approved="yes">
    <source>load summary</source>
    <target>zur Übersicht</target>
</trans-unit>
<trans-unit id="element.summarypage.renderingOptions.previousButtonLabel" approved="yes">
    <source>update</source>
    <target>verbessern</target>
</trans-unit>

Übersetzen von Finisher-Optionen

Selbstverständlich müssen auch Finisher übersetzbar sein. So kannst du unter anderem den E-Mail-Betreff lokalisieren. Und auch die Bestätigungsmeldung nach dem Absenden lässt sich übersetzen.

Vorsicht bei der translation Option von Finishern. Hier kann eine feste Sprache gesetzt werden. Die gewählte Frontend-Sprache wird dann nicht verwendet. Schon ein leerer Wert genügt, damit in den E-Mails nur noch die Defaultsprache verwendet wird. Lasse die Option im Zweifel also lieber ganz weg.

Übersetzungs-Schlüssel:

  • <form-identifier>.finisher.<finisher-identifier>.<option-name>
  • finisher.<finisher-identifier>.<option-name>

Beispiele:

<trans-unit id=" MyForm.finisher.EmailToSender.subject" approved="yes">
    <source>Your message to ACME Ltd.</source>
    <target>Ihre Nachricht an die ACME GmbH</target>
</trans-unit>
<trans-unit id="finisher.EmailToReceiver.subject" approved="yes">
    <source>Message from website</source>
    <target>Nachricht von Website</target>
</trans-unit>
<trans-unit id="finisher.Confirmation.message" approved="yes">
    <source>Thank you for your message! We will get back to you as soon as possible.</source>
    <target>Vielen Dank für Ihre Nachricht! Wir werden uns schnellstmöglich bei Ihnen melden.</target>
</trans-unit>

Übersetzen von Validator-Fehlermeldungen

Auch die Fehlermeldungen der Validatoren des TYPO3 Form Framework lassen sich übersetzen! Sowohl global, als auch für ein bestimmtes Feld oder Formular. Die passenden Error Codes zu jedem Validator findet ihr in der Original-Lokalisierungsdatei (locallang.xlf) von EXT:form.

Achtung: Damit die Übersetzungen der Fehlermeldungen im Formular verwendet werden, darf auf keinen Fall schon eine Meldung (message) in der Formular-Definition unter validationErrorMessages gesetzt sein! Diese würde immer Vorrang haben!

Übersetzungs-Schlüssel:

  • <form-identifier>.validation.error.<element-identifier>.<error-code>
  • <form-identifier>.validation.error.<error-code>
  • validation.error.<element-identifier>.<error-code>
  • validation.error.<error-code>

Beispiele:

<trans-unit id="MyForm.validation.error.1221559976" approved="yes">
    <source>Please enter valid email address. Thank you very much!</source>
    <target>Bitte gebe eine gültige E-Mail-Adresse ein. Vielen herzlichen Dank!</target>
</trans-unit>
<trans-unit id="validation.error.1221559976" approved="yes">
    <source>Please enter valid email address. Thanks!</source>
    <target>Bitte gebe eine gültige E-Mail-Adresse ein. Danke!</target>
</trans-unit>
<trans-unit id="validation.error.lastname.1221560910" approved="yes">
    <source>Please enter your last name</source>
    <target>Bitte gebe Deinen Nachnamen an.</target>
</trans-unit>

Übersetzen von Inhaltselementen in Formularen

Inhaltselemente, die über den Feldtyp "Content element" in das Formular geladen werden, müssen nicht über eine Sprachdatei übersetzt oder gar in besonderer Weise verlinkt werden. Es genügt, wenn du das Inhaltselement wie üblich auf der Unterseite übersetzt, welche das Element enthält: lege zuerst eine Seitenübersetzung an und lokalisiere dann das Inhaltselement. Fertig!

Falls trotzdem immer die Originalsprache des Inhaltselements angezeigt wird, prüfe bitte einmal die folgende Konfiguration:
config.sys_language_overlay = 1
Diese Einstellung muss aktiviert sein. Hier kannst du mehr zum Thema nachlesen.

Übersetzen von statischem Text

Dies nur der Vollständigkeit halber, denn inzwischen sollte das Prinzip klar sein. Angesprochen wird die Eigenschaft text.

Übersetzungs-Schlüssel:

  1. <form-identifier>.element.<element-identifier>.properties.text
  2. element.<element-identifier>.properties.text
  3. element.<element-type>.properties.text

Beispiele:

<trans-unit id="element.my-statictext.properties.text" approved="yes">
    <source>This is some static text.</source>
    <target>Dies ist irgendein statischer Textinhalt.</target>
</trans-unit>
<trans-unit id="MyForm.element.my-statictext.properties.text" approved="yes">
    <source>This is some static text.</source>
    <target>Dies ist irgendein statischer Textinhalt.</target>
</trans-unit>

Weitere Tipps

Übersetzen von Texten in E-Mails

Mit den Finisher-Optionen lassen sich wesentliche Bestandteile bereits anpassen. Weitere Texte kannst du in angepassten E-Mail-Templates bereitstellen. Hier ist die Übersetzung wieder wie üblich: verwende den f:translate Viewhelper und verweise mit dem LLL-String auf deine Sprachdatei. Der Schlüssel ist frei wählbar.

Wie man eigene E-Mail-Templates verwendet, erkläre ich im zweiten Tutorial dieser Reihe.

Beispiel:

<f:translate key="LLL:EXT:form_examples/Resources/Private/Language/locallang.xlf:email.yourMessageToUs"/>

Mehrsprachige Anrede in E-Mails

"Sehr geehrte Frau Schmitz" – auch diese Einleitung inklusive Nachname lässt sich übersetzen. Hierfür wird in der Sprachdatei die Formatzeichenfolge%s als Platzhalter für ein Argument verwendet (= printf-Funktion). Dieses Argument (der ausgelesene Nachname) wird vom f:translate Viewhelper übergeben.

Im Template verwendet man zudem den f:switch Viewhelper für die Wahl der männlichen oder weiblichen Anrede.

locallang.xlf:

<trans-unit id="email.salutation.male" approved="yes">
    <source>Dear Mr %s,</source>
    <target>Sehr geehrter Herr %s,</target>
</trans-unit>
<trans-unit id="email.salutation.female" approved="yes">
    <source>Dear Ms %s,</source>
    <target>Sehr geehrte Frau %s,</target>
</trans-unit>

E-Mail-Template:

<f:switch expression="{form.formState.formValues.title}">
    <f:case value="male">
        <f:translate key="LLL:EXT:form_examples/Resources/Private/Language/locallang.xlf:email.salutation.male"
            arguments="{0: '{form.formState.formValues.lastname}'}"/>
    </f:case>
    <f:case value="female">
        <f:translate key="LLL:EXT:form_examples/Resources/Private/Language/locallang.xlf:email.salutation.female"
            arguments="{0: '{form.formState.formValues.lastname}'}"/>
        </f:case>
    <f:defaultCase>Dear Sir or Madam,</f:defaultCase>
</f:switch>

Verzicht auf Legenden und Headings

Ein Tipp zum Schluss: Wenn du keine <legend> oder <h2> Überschriften in deinem Formular möchtest, lasse das Label in der Form-Definition einfach leer. In dem Fall entfallen diese Elemente einfach.

Demo-Extension!

Auf GitHub habe ich eine spezielle Extension mit Beispiel-Formularen zum Download bereitgestellt. Diese beinhaltet auch verschiedene Übersetzungen, die wie oben beschrieben angelegt wurden. So kannst du dir alle genannten Übersetzungs-Beispiele (und viele weitere) in einer funktionierenden Erweiterung ansehen. Einfach installieren und loslegen!

EXT:form_examples auf GitHub

Wenn du eigene Formulare für deine Website anlegen möchtest, empfehle ich dir aber meine zweite Formular-Extension als Grundlage: diese enthält nur die nötigsten Konfigurationen als Starthilfe und kann nach Belieben erweitert werden.

EXT:form_distribution auf GitHub