Commit e5da0a7b authored by Mathias Brodala's avatar Mathias Brodala Committed by Ralf Zimmermann
Browse files

[!!!][TASK] Use multiple translation files by default

Resolves: #87009
Releases: master
Change-Id: I533ccf9959f0e9e3a485a3317af8befc8befeb98
Reviewed-on: https://review.typo3.org/c/Packages/TYPO3.CMS/+/60593


Tested-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Tested-by: Björn Jacob's avatarBjörn Jacob <bjoern.jacob@tritum.de>
Tested-by: default avatarTYPO3com <noreply@typo3.com>
Tested-by: default avatarRalf Zimmermann <ralf.zimmermann@tritum.de>
Reviewed-by: Susanne Moog's avatarSusanne Moog <look@susi.dev>
Reviewed-by: Björn Jacob's avatarBjörn Jacob <bjoern.jacob@tritum.de>
Reviewed-by: default avatarRalf Zimmermann <ralf.zimmermann@tritum.de>
parent f202f793
......@@ -166,7 +166,7 @@ class validateRstFiles
$checkFor = [
[
'type' => 'index',
'regex' => '#^\.\. index:: (?:(?:TypoScript|TSConfig|TCA|FlexForm|LocalConfiguration|Fluid|FAL|Database|JavaScript|PHP-API|Frontend|Backend|CLI|RTE|ext:[a-zA-Z_0-9]+)(?:,\\s|$))+#',
'regex' => '#^\.\. index:: (?:(?:TypoScript|TSConfig|TCA|FlexForm|LocalConfiguration|Fluid|FAL|Database|JavaScript|PHP-API|Frontend|Backend|CLI|RTE|YAML|ext:[a-zA-Z_0-9]+)(?:,\\s|$))+#',
'title' => 'no or wrong index',
'message' => 'insert \'.. index:: <at least one valid keyword>\' at the last line of the file. See Build/Scripts/validateRstFiles.php for allowed keywords',
],
......
.. include:: ../../Includes.txt
========================================================================
Breaking: #87009 - Use multiple translation files by default in EXT:form
========================================================================
See :issue:`87009`
Description
===========
All :yaml:`translationFile` options in EXT:form setup and form definitions have been renamed to :yaml:`translationFiles`.
The following default translation files are now registered at index :yaml:`10` in all locations:
* :file:`EXT:form/Resources/Private/Language/locallang.xlf`
* :file:`EXT:form/Resources/Private/Language/Database.xlf`
Impact
======
Extending form setup or form definitions with additional translation files does not require adding the default translation files anymore.
The option :yaml:`translationFile` does not work anymore and must be migrated to :yaml:`translationFiles`.
Opening and saving a form with the form editor once also performs the migration of the corresponding form definition and makes it permanent.
Affected Installations
======================
All installations which use EXT:form and its :yaml:`translationFile` option.
Migration
=========
In your custom form configuration, migrate the single value :yaml:`translationFile` option to the multi value :yaml:`translationFiles` option.
Given that all default translation files of EXT:form are registered at index :yaml:`10` it is recommended to use a higher index for custom translation files.
Single file
-----------
Before:
.. code-block:: yaml
translationFile: path/to/locallang.xlf
After:
.. code-block:: yaml
translationFiles:
20: path/to/locallang.xlf
Multiple files
--------------
Before:
.. code-block:: yaml
translationFile:
10: EXT:form/Resources/Private/Language/locallang.xlf
20: path/to/locallang.xlf
25: path/to/other/locallang.xlf
After:
.. code-block:: yaml
translationFiles:
20: path/to/locallang.xlf
25: path/to/other/locallang.xlf
.. index:: YAML, NotScanned, ext:form
......@@ -25,9 +25,12 @@ use TYPO3\CMS\Core\Page\PageRenderer;
use TYPO3\CMS\Core\Site\Entity\Site;
use TYPO3\CMS\Core\Site\Entity\SiteLanguage;
use TYPO3\CMS\Core\Utility\ArrayUtility;
use TYPO3\CMS\Core\Utility\Exception\MissingArrayPathException;
use TYPO3\CMS\Core\Utility\GeneralUtility;
use TYPO3\CMS\Extbase\Mvc\View\JsonView;
use TYPO3\CMS\Fluid\View\TemplateView;
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessing;
use TYPO3\CMS\Form\Domain\Configuration\ArrayProcessing\ArrayProcessor;
use TYPO3\CMS\Form\Domain\Configuration\ConfigurationService;
use TYPO3\CMS\Form\Domain\Configuration\FormDefinitionConversionService;
use TYPO3\CMS\Form\Domain\Exception\RenderingException;
......@@ -281,7 +284,7 @@ class FormEditorController extends AbstractBackendController
$formElementConfiguration = TranslationService::getInstance()->translateValuesRecursive(
$formElementConfiguration,
$this->prototypeConfiguration['formEditor']['translationFile'] ?? null
$this->prototypeConfiguration['formEditor']['translationFiles'] ?? []
);
$formElementsByGroup[$formElementConfiguration['group']][] = [
......@@ -306,7 +309,7 @@ class FormEditorController extends AbstractBackendController
$groupConfiguration = TranslationService::getInstance()->translateValuesRecursive(
$groupConfiguration,
$this->prototypeConfiguration['formEditor']['translationFile'] ?? null
$this->prototypeConfiguration['formEditor']['translationFiles'] ?? []
);
$formGroups[] = [
......@@ -346,7 +349,7 @@ class FormEditorController extends AbstractBackendController
$formEditorDefinitions = ArrayUtility::reIndexNumericArrayKeysRecursive($formEditorDefinitions);
$formEditorDefinitions = TranslationService::getInstance()->translateValuesRecursive(
$formEditorDefinitions,
$this->prototypeConfiguration['formEditor']['translationFile'] ?? null
$this->prototypeConfiguration['formEditor']['translationFiles'] ?? []
);
return $formEditorDefinitions;
}
......@@ -493,6 +496,7 @@ class FormEditorController extends AbstractBackendController
}
$formDefinition = $this->filterEmptyArrays($formDefinition);
$formDefinition = $this->migrateTranslationFileOptions($formDefinition);
// @todo: replace with rte parsing
$formDefinition = ArrayUtility::stripTagsFromValuesRecursive($formDefinition);
......@@ -599,6 +603,51 @@ class FormEditorController extends AbstractBackendController
return $array;
}
/**
* Migrate singular "translationFile" options to plural "translationFiles"
*
* @param array $formDefinition
* @return array
* @deprecated since v10 and will be removed in TYPO3 v11
*/
protected function migrateTranslationFileOptions(array $formDefinition): array
{
GeneralUtility::makeInstance(ArrayProcessor::class, $formDefinition)->forEach(
GeneralUtility::makeInstance(
ArrayProcessing::class,
'translationFile',
'((.+)\.translationFile)(?:\.|$)',
function ($key, $value, $matches) use (&$formDefinition) {
[, $singleOptionPath, $parentOptionPath] = $matches;
try {
$translationFiles = ArrayUtility::getValueByPath($formDefinition, $singleOptionPath, '.');
} catch (MissingArrayPathException $e) {
// Already migrated by a previous "translationFile.N" entry
return;
}
if (is_string($translationFiles)) {
// 10 is usually used by EXT:form
$translationFiles = [20 => $translationFiles];
}
$formDefinition = ArrayUtility::setValueByPath(
$formDefinition,
sprintf('%s.translationFiles', $parentOptionPath),
$translationFiles,
'.'
);
$formDefinition = ArrayUtility::removeByPath($formDefinition, $singleOptionPath, '.');
return $value;
}
)
);
return $formDefinition;
}
/**
* @return FormDefinitionConversionService
*/
......
......@@ -266,7 +266,7 @@ class FormManagerController extends AbstractBackendController
} else {
$controllerConfiguration = TranslationService::getInstance()->translateValuesRecursive(
$this->formSettings['formManager']['controller'],
$this->formSettings['formManager']['translationFile']
$this->formSettings['formManager']['translationFiles'] ?? []
);
$this->addFlashMessage(
......@@ -333,7 +333,7 @@ class FormManagerController extends AbstractBackendController
$formManagerAppInitialData = ArrayUtility::reIndexNumericArrayKeysRecursive($formManagerAppInitialData);
$formManagerAppInitialData = TranslationService::getInstance()->translateValuesRecursive(
$formManagerAppInitialData,
$this->formSettings['formManager']['translationFile'] ?? null
$this->formSettings['formManager']['translationFiles'] ?? []
);
return json_encode($formManagerAppInitialData);
}
......
......@@ -628,7 +628,7 @@ class ConfigurationService implements SingletonInterface
}
$formElement['predefinedDefaults'] = $this->getTranslationService()->translateValuesRecursive(
$formElement['predefinedDefaults'],
$prototypeConfiguration['formEditor']['translationFile'] ?? null
$prototypeConfiguration['formEditor']['translationFiles'] ?? []
);
$formElements[$name] = $formElement;
}
......
......@@ -379,15 +379,15 @@ class DataStructureIdentifierHook
array $finishersDefinition,
array $prototypeConfiguration
): array {
if (isset($finishersDefinition[$finisherIdentifier]['FormEngine']['translationFile'])) {
$translationFile = $finishersDefinition[$finisherIdentifier]['FormEngine']['translationFile'];
if (isset($finishersDefinition[$finisherIdentifier]['FormEngine']['translationFiles'])) {
$translationFiles = $finishersDefinition[$finisherIdentifier]['FormEngine']['translationFiles'];
} else {
$translationFile = $prototypeConfiguration['formEngine']['translationFile'];
$translationFiles = $prototypeConfiguration['formEngine']['translationFiles'];
}
$finishersDefinition[$finisherIdentifier]['FormEngine'] = TranslationService::getInstance()->translateValuesRecursive(
$finishersDefinition[$finisherIdentifier]['FormEngine'],
$translationFile
$translationFiles
);
return $finishersDefinition;
......
......@@ -176,34 +176,29 @@ class TranslationService implements SingletonInterface
* Recursively translate values.
*
* @param array $array
* @param array|string|null $translationFile
* @param array $translationFiles
* @return array the modified array
* @internal
*/
public function translateValuesRecursive(array $array, $translationFile = null): array
public function translateValuesRecursive(array $array, array $translationFiles = []): array
{
$result = $array;
foreach ($result as $key => $value) {
if (is_array($value)) {
$result[$key] = $this->translateValuesRecursive($value, $translationFile);
$result[$key] = $this->translateValuesRecursive($value, $translationFiles);
} else {
$translationFiles = null;
if (is_string($translationFile)) {
$translationFiles = [$translationFile];
} elseif (is_array($translationFile)) {
$translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFile);
}
$this->sortArrayWithIntegerKeysDescending($translationFiles);
if ($translationFiles) {
foreach ($translationFiles as $_translationFile) {
$translatedValue = $this->translate($value, null, $_translationFile, null);
if (!empty($translationFiles)) {
foreach ($translationFiles as $translationFile) {
$translatedValue = $this->translate($value, null, $translationFile, null);
if (!empty($translatedValue)) {
$result[$key] = $translatedValue;
break;
}
}
} else {
$result[$key] = $this->translate($value, null, $translationFile, null, $value);
$result[$key] = $this->translate($value, null, null, null, $value);
}
}
}
......@@ -234,16 +229,12 @@ class TranslationService implements SingletonInterface
}
$finisherIdentifier = preg_replace('/Finisher$/', '', $finisherIdentifier);
$translationFile = $renderingOptions['translationFile'] ?? null;
if (empty($translationFile)) {
$translationFile = $formRuntime->getRenderingOptions()['translation']['translationFile'];
$translationFiles = $renderingOptions['translationFiles'] ?? [];
if (empty($translationFiles)) {
$translationFiles = $formRuntime->getRenderingOptions()['translation']['translationFiles'];
}
if (is_string($translationFile)) {
$translationFiles = [$translationFile];
} else {
$translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFile);
}
$translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFiles);
if (isset($renderingOptions['translatePropertyValueIfEmpty'])) {
$translatePropertyValueIfEmpty = (bool)$renderingOptions['translatePropertyValueIfEmpty'];
......@@ -337,16 +328,12 @@ class TranslationService implements SingletonInterface
}
$defaultValue = empty($defaultValue) ? '' : $defaultValue;
$translationFile = $renderingOptions['translation']['translationFile'] ?? null;
if (empty($translationFile)) {
$translationFile = $formRuntime->getRenderingOptions()['translation']['translationFile'];
$translationFiles = $renderingOptions['translation']['translationFiles'] ?? [];
if (empty($translationFiles)) {
$translationFiles = $formRuntime->getRenderingOptions()['translation']['translationFiles'];
}
if (is_string($translationFile)) {
$translationFiles = [$translationFile];
} else {
$translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFile);
}
$translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFiles);
$language = null;
if (isset($renderingOptions['translation']['language'])) {
......@@ -460,16 +447,12 @@ class TranslationService implements SingletonInterface
}
$renderingOptions = $element->getRenderingOptions();
$translationFile = $renderingOptions['translation']['translationFile'] ?? null;
if (empty($translationFile)) {
$translationFile = $formRuntime->getRenderingOptions()['translation']['translationFile'];
$translationFiles = $renderingOptions['translation']['translationFiles'] ?? [];
if (empty($translationFiles)) {
$translationFiles = $formRuntime->getRenderingOptions()['translation']['translationFiles'];
}
if (is_string($translationFile)) {
$translationFiles = [$translationFile];
} else {
$translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFile);
}
$translationFiles = $this->sortArrayWithIntegerKeysDescending($translationFiles);
$language = null;
if (isset($renderingOptions['language'])) {
......
......@@ -380,7 +380,8 @@ TYPO3:
mixins:
translationSettingsMixin:
translation:
translationFile: 'EXT:form/Resources/Private/Language/locallang.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/locallang.xlf'
#translatePropertyValueIfEmpty: true
########### FORM ELEMENT MIXINS ###########
......
......@@ -8,7 +8,8 @@ TYPO3:
viewModel: 'TYPO3/CMS/Form/Backend/FormManager/ViewModel'
stylesheets:
100: 'EXT:form/Resources/Public/Css/form.css'
translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/Database.xlf'
javaScriptTranslationFile: 'EXT:form/Resources/Private/Language/locallang_formManager_javascript.xlf'
selectablePrototypesConfiguration:
100:
......@@ -30,7 +31,8 @@ TYPO3:
prototypes:
standard:
formEditor:
translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/Database.xlf'
dynamicRequireJsModules:
app: 'TYPO3/CMS/Form/Backend/FormEditor'
mediator: 'TYPO3/CMS/Form/Backend/FormEditor/Mediator'
......
......@@ -4,7 +4,8 @@ TYPO3:
prototypes:
standard:
formEngine:
translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/Database.xlf'
########### TCE Forms CONFIGURATION ###########
......
......@@ -2081,10 +2081,10 @@ attachUploads
If set, all uploaded items are attached to the email.
.. _apireference-finisheroptions-emailfinisher-options-translation-translationfile:
.. _apireference-finisheroptions-emailfinisher-options-translation-translationfiles:
translation.translationFile
+++++++++++++++++++++++++++
translation.translationFiles
++++++++++++++++++++++++++++
:aspect:`Data type`
string/ array
......
......@@ -511,9 +511,8 @@ The translation files of the ``form editor`` are loaded as follows:
prototypes:
standard:
formEditor:
translationFile:
# translation files for the form editor
10: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
# custom translation file
20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf'
The process searches for each option value within all of the defined
......@@ -530,14 +529,9 @@ Imagine, the following is defined for an option value:
First of all, the process searches for the translation key ``formEditor.elements.Form.editor.finishers.label``
within the file ``20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf'``
and after it inside the file ``10: 'EXT:form/Resources/Private/Language/Database.xlf'``.
If nothing is found, the option value will be displayed unmodified.
Due to compatibility issues, the setting ``translationFile`` is not defined
as an array in the default configuration. To load your own translation files,
you should define an array containing 'EXT:form/Resources/Private/Language/Database.xlf'
as first entry (key ``10``) followed by your own file (key ``20``) as
displayed in the example above.
and after it inside the file ``10: 'EXT:form/Resources/Private/Language/Database.xlf'``
(loaded by default). If nothing is found, the option value will be
displayed unmodified.
.. _concepts-formeditor-extending:
......
......@@ -116,9 +116,8 @@ The translation files of the ``form manager`` are loaded as follows:
CMS:
Form:
formManager:
translationFile:
# translation files for the form manager
10: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
# custom translation file
20: 'EXT:my_site_package/Resources/Private/Language/Form/Database.xlf'
The process searches for each option value within all of the defined
......@@ -135,11 +134,6 @@ Imagine, the following is defined for an option value:
First of all, the process searches for the translation key ``formManager.selectablePrototypesConfiguration.standard.label``
within the file ``20: 'EXT:my_site_package/Resources/Private/Language/Form/Database.xlf'``
and after it inside the file ``10: 'EXT:form/Resources/Private/Language/Database.xlf'``.
If nothing is found, the option value will be displayed unmodified.
Due to compatibility issues, the setting ``translationFile`` is not defined
as an array in the default configuration. To load your own translation files,
you should define an array containing 'EXT:form/Resources/Private/Language/Database.xlf'
as first entry (key ``10``) followed by your own file (key ``20``) as
displayed in the example above.
and after it inside the file ``10: 'EXT:form/Resources/Private/Language/Database.xlf'``
(loaded by default). If nothing is found, the option value will be
displayed unmodified.
......@@ -57,9 +57,8 @@ The translation files of the ``form plugin`` are loaded as follows:
prototypes:
standard:
formEngine:
translationFile:
# translation files for the form plugin (finisher overrides)
10: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
# custom translation file
20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf'
The process searches for each option value within all of the defined
......@@ -77,11 +76,6 @@ Imagine, the following is defined for an option value:
First of all, the process searches for the translation key
``tt_content.finishersDefinition.EmailToReceiver.label`` within the file
20: 'EXT:my_site_package/Resources/Private/Language/Database.xlf' and after
it inside the file 10: 'EXT:form/Resources/Private/Language/Database.xlf'.
If nothing is found, the option value will be displayed unmodified.
Due to compatibility issues, the setting ``translationFile`` is not defined
as an array in the default configuration. To load your own translation files,
you should define an array containing 'EXT:form/Resources/Private/Language/Database.xlf'
as first entry (key ``10``) followed by your own file (key ``20``) as
displayed in the example above.
it inside the file 10: 'EXT:form/Resources/Private/Language/Database.xlf'
(loaded by default). If nothing is found, the option value will be
displayed unmodified.
......@@ -505,19 +505,14 @@ Additional translation files can be defined as follows:
Form:
renderingOptions:
translation:
translationFile:
# translation files for the frontend
10: 'EXT:form/Resources/Private/Language/locallang.xlf'
translationFiles:
# custom translation file
20: 'EXT:my_site_package/Resources/Private/Language/Form/locallang.xlf'
Due to compatibility issues, the setting ``translationFile`` is not defined
as an array in the default configuration. To load your own translation files,
you should define an array containing 'EXT:form/Resources/Private/Language/Form/locallang.xlf'
as first entry (key ``10``) followed by your own file (key ``20``) as
displayed in the example above. The array is processed from the highest key
to the lowest, i.e. your translation file with the key ``20`` is processed
first. If the look-up process does not find a key within all of the provided
files, the property value will be displayed unmodified.
The array is processed from the highest key to the lowest, i.e. your
translation file with the key ``20`` is processed first. If the look-up
process does not find a key within all of the provided files, the
property value will be displayed unmodified.
The following properties can be translated:
......@@ -770,7 +765,8 @@ Pure YAML is sufficient to add simple, static values:
label: This is a %s feature
renderingOptions:
translation:
translationFile: path/to/locallang.xlf
translationFiles:
10: path/to/locallang.xlf
arguments:
label:
- useful
......@@ -800,7 +796,8 @@ should be enough:
label: I agree to the <a href="%s">terms and conditions</a>
renderingOptions:
translation:
translationFile: path/to/locallang.xlf
translationFiles:
10: path/to/locallang.xlf
The following TypoScript setup uses the named key :yaml:`fieldWithTranslationArguments` to refer
to the field and adds a page URL as translation argument:
......@@ -870,7 +867,8 @@ The same mechanism (YAML, YAML + TypoScript) works for finisher options:
recipientAddress: foo@example.org
senderAddress: bar@example.org
translation:
translationFile: path/to/locallang.xlf
translationFiles:
10: path/to/locallang.xlf
arguments:
subject:
- awesome
......
......@@ -374,7 +374,8 @@ Full default configuration
rendererClassName: TYPO3\CMS\Form\Domain\Renderer\FluidFormRenderer
renderingOptions:
translation:
translationFile: 'EXT:form/Resources/Private/Language/locallang.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/locallang.xlf'
templateRootPaths:
10: 'EXT:form/Resources/Private/Frontend/Templates/'
partialRootPaths:
......@@ -3775,7 +3776,8 @@ Full default configuration
maximum: '10M'
formEditor:
translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/Database.xlf'
dynamicRequireJsModules:
app: TYPO3/CMS/Form/Backend/FormEditor
mediator: TYPO3/CMS/Form/Backend/FormEditor/Mediator
......@@ -3860,11 +3862,13 @@ Full default configuration
page:
label: formEditor.formElementGroups.page.label
formEngine:
translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/Database.xlf'
mixins:
translationSettingsMixin:
translation:
translationFile: 'EXT:form/Resources/Private/Language/locallang.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/locallang.xlf'
formElementMixins:
BaseFormElementMixin:
formEditor:
......@@ -4649,7 +4653,8 @@ Full default configuration
viewModel: TYPO3/CMS/Form/Backend/FormManager/ViewModel
stylesheets:
100: 'EXT:form/Resources/Public/Css/form.css'
translationFile: 'EXT:form/Resources/Private/Language/Database.xlf'
translationFiles:
10: 'EXT:form/Resources/Private/Language/Database.xlf'
javaScriptTranslationFile: 'EXT:form/Resources/Private/Language/locallang_formManager_javascript.xlf'
selectablePrototypesConfiguration:
100:
......