Skip to content

Introduction

The first time you want to translate a form which was built with EXT:form, you'll probably try to use the usual LLL strings (e.g. 'LLL:EXT:my_ext/Resources/Private/Language/locallang.xlf:localized.label'), which are common in TYPO3. But these cannot be read inside form definitions.

Instead, you'll need to use some special translation keys inside your localization file (locallang.xlf), which are specified by the TYPO3 Form Framework. Only then the translations will be used in your forms.

A translation key is the string which is entered in the id attribute of a <trans-unit> element (inside your localization file).
Example: <trans-unit id="element.email.properties.label">

The official documentation of the Form Framework now contains significant more information on how to provide translations. Still, I wrote this tutorial to complete and supplement some parts of this topic.
I'll explain the basic setup and method first. Then follow the examples for the different properties and components. At last I'll provide a couple of tricks, e.g. how to get a multilingual salutation in the e-mails.

I will keep these examples up-to-date and expand them as needed.

Important differences between TYPO3 versions

  • When registering language files, remember: in TYPO3 v8 and v9 the YAML option is named translationFile. In TYPO3 v10 it was renamed to translationFiles (TYPO3 Changelog)!
    Additionally, in TYPO3 v8 and v9 you must load the default language file from EXT:form, too! By default, the language file is not loaded as part of an array here.
  • Starting with TYPO3 v12, the first three lines with the namespace TYPO3.CMS.Form can be removed from the form configuration (TYPO3 Changelog).
  • Starting with TYPO3 v12, a new option $GLOBALS['TYPO3_CONF_VARS']['SYS']['lang']['requireApprovedLocalizations'] exists. It is enabled by default. As a result, you need to add the attribute approved="yes" to your custom XLIFF translations. (TYPO3 Changelog)

Preparations

To be able to create translations, you'll need one or more .xlf files which you then define in your form configuration. I recommend to prepare a dedicated TYPO3 extension just for your forms, form templates and respective configurations. The basic setup of such an extension is described in the first tutorial of the Form series.

The basic operation of translation files (locallang.xlf) is well explained in this documentation.

EXT:form already provides a language file with several keys. You'll have to load your own language file(s) inside your form configuration in YAML's array notation (without overwriting the original file).

Since TYPO3 v10, you're able to check the currently used Form configuration in the TYPO3 backend. You can find this new overview in the module SYSTEM > Configuration (EXT:lowlevel has to be installed).

TYPO3:
  CMS:
    Form:
      prototypes:
        standard:
          formElementsDefinition:
            Form:
              renderingOptions:
                translation:
                  # Note the spelling: 'translationFiles' starting with TYPO3 v10! In previous versions: 'translationFile'.
                  translationFiles:
                    # Default translation file of EXT:form (always needed, but only needs to be linked manually in TYPO3 v8 and v9):
                    10: 'EXT:form/Resources/Private/Language/locallang.xlf'
                    # Load your own translation file(s) below:
                    20: 'EXT:form_examples/Resources/Private/Language/locallang.xlf'

Explanation

First of all, an example form definition

Below is the form definition of a simple contact form. As you can see, all elements (renderables) have a certain type (Page, Text, ...) as well as a freely selectable identifier. Other parts like the finishers only have an identifier.

In the following examples we will translate some of these elements by addressing the identifier or type.

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

About translation keys and the fallback chain

The following explanation is similar to the official documentation, as this part can hardly be improved:

As I noted at the beginning, the translation keys used inside the translation file follow a well-defined pattern. This is complemented by several possible fallbacks, which allow to overwrite values for individual forms or elements.

This gives you the following translation options:

  1. You can translate a property for both a specific form and form element
  2. You can translate a property for a specific form element inside various forms
  3. You can translate a property for an element type (e.g. all of type SummaryPage) in various forms

These possibilities are achieved by the following keys:

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

So what exactly are these keys' components? By taking the example form above:

  • <form-definition-identifier>: The unique identifier of the form, here: "MyForm"
  • <element-type>: All text fields are of type "Text", all summary pages are of type "SummaryPage", etc.
  • <element-identifier>: An identifier for every field is set automatically by the form module in the TYPO3 Backend, but can be fully adjusted to your needs. So you could set 'lastname' as the identifier for the relevant field in all your forms, to translate them consistent.
  • <property-name>: The properties of every field, like label or placeholder

The Form Framework searches for translations in the order mentioned above (from specific to general).

It's only if there is no possible value found in every translation file, that the set value inside the form definition is used.

Or to put it differently: As soon as the XLIFF file contains a matching translation key, the YAML's value is no longer considered! The <source> attribute of the XLIFF file then becomes the fallback for all languages.

This allows you to set a common value for e.g. all subject field labels and then overwrite this for a single form.

In the following, I will list the different parts which can be translated inside the TYPO3 Form Framework.

Translations

Translating form labels

Let's begin with the usual <label> elements for every created form field. First, I will list all applicable translation keys in the order in which they are looked up. This is followed by an example how it's used inside a localization file.

Translation keys:

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

Example:

<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>

Translating form page labels

There are two pitfalls with the labels of form pages:

  1. Labels from XLIFF are only rendered if a label value also exists in the form definition!
  2. You must specify both the identifier of the page, as well as the identifier of the form.

Formular definition (excerpt):

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

Translation keys:

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

Example:

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

 

Translating select options (drop-downs), radio buttons and multi checkboxes

You could use select lists, for example, to let the user choose the salutation. They consist of a <select> element and several <option> elements. You can translate every existing option as well as the hint which is shown by default (like 'Please choose ..').

Radio buttons and Multi checkboxes are localized in the same way: your defined choices are available below options.

Translation keys:

  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>

Good to know: Translation keys may also contain spaces and special characters. You can even use periods, although they serve as separators in the translation key. Therefore, you are not limited in your choice of option values.

Examples:

<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>

Translating placeholder attributes

Placeholder attributes differ from labels only in the property name at the end.

Translation keys:

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

Examples:

<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>

Translating form buttons

You have to consider some differences with the buttons. If you use the form-identifier for the submit button, it has to be the second part of the key. This seems to be a bug (as of October 2018).

The previous and next buttons have characteristics as well:

  1. These labels are set separately for normal pages and summary pages.
  2. You cannot use the form-identifier together with the general element-type. It is possible in combination with the element-identifier, though.

For more clarity I separated the universal and the specific translations:

Translation keys (universal):

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

Examples (universal):

<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>

Translation keys (specific):

  • 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

Examples (specific):

<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>

Translating finisher options

Of course finishers have to be translated, too. For instance you can localize the e-mail subject. The confirmation message after submit can be translated as well.

Be careful when using the translation option of finishers. You can set a fixed language with this. In this case, the selected frontend language is not considered. A blank value is sufficient – the default language is used in all e-mails. In doubt, just omit the whole option.

Translation keys:

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

Examples:

<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>

Translating validation error codes

You can translate the validation error codes of the TYPO3 Form Framework, too! Please refer to the original localization file (locallang.xlf) of EXT:form to find the corresponding error code of each validator.

Attention: In order to use these error message translations in your form, you must not set a message in the form definition under validationErrorMessages! This would always have priority!

Translation keys:

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

Examples:

<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>

Translating content elements in forms

When you load a content element in a form with the corresponding field type, you don't have to translate it or interlink it in a special way in the language file. Just translate the content element the way you always do: translate the page on which the content element is located, then localize the latter. Done!

If the content element still shows only in the original language, please check the following configuration:
config.sys_language_overlay = 1
This setting has to be activated. Read here more on this topic.

Translating static text

Just for the sake of completeness, as the principle should now be clear. The property text is addressed.

Translation keys:

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

Examples:

<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>

More hints and tips

Translating texts in e-mails

You can translate most of the finisher parts through the options. In customized e-mail templates you can provide further content. The localization of these texts is performed as usual: use the f:translate viewhelper and point to your language file the with a LLL string. The translation key can be chosen freely.

I described in the second tutorial of this series how to set up customized e-mail templates.

Example:

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

Multilingual salutation in e-mails

'Dear Ms Smith' – you can also translate a salutation like this, including the last name. You can use the format specifier%s inside the language file as a placeholder for an argument (= printf function). This argument (the given last name) is passed by the f:translate viewhelper.

In the e-mail template you can use the f:switch viewhelper to choose between the male or female salutation.

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>

Remove legends and headings

One last tip: If you don't need the <legend> or <h2> headings in your form, just leave the labels in your form definition blank. In this case, these elements are omitted automatically.

Demo extension!

I have provided a special extension with example forms to download on GitHub. Among other things, it contains various translations just like I described above. It allows you to check out all translation examples (and many more) in a working extension. Just install and start!

EXT:form_examples on GitHub

If you want to create your own forms for a website, I recommend my second form extension as a basis. This contains only the most necessary configurations as a quick start and can be extended as desired.

EXT:form_distribution on GitHub