Repository: pimcore/pimcore (language: php)

Issue:
[Bug]: yaml translations prioritized over backend translations with fallback
### Pimcore version

10.6.2

### Steps to reproduce

I've a project with multiple languages, the part with the fallback language is more important:

de: default language
de_AT: fallback to de
de_DE: fallback to de

Define a translation in translations/messages.de.yml:
test_message: Test

Case 1: Override the translation in the backend for the key test_message:
de: Test 2
de_AT: empty
de_DE: empty

Case 2: Override all Values
de: Test 2
de_AT: Test 2
de_DE: Test 2


### Expected / actual behavior

de-at document present in the frontend.

I would expect that de (Test 2) would be used as fallback for case 1 if de_AT/de_DE is empty. If i remove the translation from the yaml file, the output is correct for case 1. It looks like the yaml translation (Test) will get picked (first) instead of the backend translation respecting fallback languages (Test 2).

Translation output is correct for case 2.


Discussion / hints:
Thanks a lot for reporting the issue. We did not consider the issue as "Priority" or "Backlog", so we're not going to work on that anytime soon. Please create a pull request to fix the issue if this is a bug report. We'll then review it as quickly as possible. If you're interested in contributing a feature, please contact us first here before creating a pull request. We'll then decide whether we'd accept it or not. Thanks for your understanding.
so currently is doing 
`DB:de_AT`,  (skipping `DB:de`), `YML:de_AT`,`YML:de`, `YML:en` 
i tried to change on https://github.com/pimcore/pimcore/pull/15790 and would going like
`DB:de_AT`, `YML:de_AT`, `DB:de`, `YML:de`, `DB:en`, `YML:en`

Still on draft because have to check if it should go recursively on DB values first or make ABABAB; and also if it affects the perfomance.

Fix patch:
diff --git a/.github/ci/files/translations/messages.de.yml b/.github/ci/files/translations/messages.de.yml
new file mode 100644
index 00000000000..de4605c8687
--- /dev/null
+++ b/.github/ci/files/translations/messages.de.yml
@@ -0,0 +1,5 @@
+fallback_to_YML_AT: wrong fallback DE YML
+fallback_to_YML_DE: YML DE
+fallback_to_YML_EN:
+fallback_to_DE: wrong fallback DE YML
+fallback_to_EN:
\ No newline at end of file
diff --git a/.github/ci/files/translations/messages.de_AT.yml b/.github/ci/files/translations/messages.de_AT.yml
new file mode 100644
index 00000000000..48c689c56ca
--- /dev/null
+++ b/.github/ci/files/translations/messages.de_AT.yml
@@ -0,0 +1,5 @@
+fallback_to_YML_AT: YML AT
+fallback_to_YML_DE:
+fallback_to_YML_EN:
+fallback_to_DE:
+fallback_to_EN:
\ No newline at end of file
diff --git a/.github/ci/files/translations/messages.en.yml b/.github/ci/files/translations/messages.en.yml
new file mode 100644
index 00000000000..6fcab6ed952
--- /dev/null
+++ b/.github/ci/files/translations/messages.en.yml
@@ -0,0 +1,5 @@
+fallback_to_YML_AT: wrong fallback EN YML
+fallback_to_YML_DE: wrong fallback EN YML
+fallback_to_YML_EN: YML EN
+fallback_to_DE: wrong fallback EN YML
+fallback_to_EN: wrong fallback EN YML
\ No newline at end of file
diff --git a/.github/ci/scripts/setup-pimcore-environment.sh b/.github/ci/scripts/setup-pimcore-environment.sh
index b319acb56d3..84f246ebc70 100755
--- a/.github/ci/scripts/setup-pimcore-environment.sh
+++ b/.github/ci/scripts/setup-pimcore-environment.sh
@@ -7,6 +7,7 @@ mkdir -p var/config
 cp -r .github/ci/files/config/. config
 mkdir -p config/local/
 cp -r .github/ci/files/templates/. templates
+cp -r .github/ci/files/translations/. translations
 cp -r .github/ci/files/bin/console bin/console
 cp -r .github/ci/files/src/. src
 cp -r .github/ci/files/public/. public
diff --git a/lib/Translation/Translator.php b/lib/Translation/Translator.php
index 5b4c1e82b81..cd54184fc62 100644
--- a/lib/Translation/Translator.php
+++ b/lib/Translation/Translator.php
@@ -23,6 +23,7 @@
 use Symfony\Component\HttpKernel\CacheWarmer\WarmableInterface;
 use Symfony\Component\HttpKernel\Kernel;
 use Symfony\Component\Translation\Exception\InvalidArgumentException;
+use Symfony\Component\Translation\Exception\LogicException;
 use Symfony\Component\Translation\MessageCatalogue;
 use Symfony\Component\Translation\MessageCatalogueInterface;
 use Symfony\Component\Translation\TranslatorBagInterface;
@@ -187,7 +188,20 @@ public function lazyInitialize(string $domain, string $locale): void
             }
 
             if ($catalogue) {
-                $this->getCatalogue($locale)->addCatalogue($catalogue);
+                $c = $this->getCatalogue($locale);
+                $c->addCatalogue($catalogue);
+                $fallbackCatalogue = $c->getFallbackCatalogue();
+                if($fallbackCatalogue) {
+                    $this->lazyInitialize($domain, $fallbackCatalogue->getLocale());
+
+                    try {
+                        $this->getCatalogue($locale)->addFallbackCatalogue(
+                            $this->getCatalogue($fallbackCatalogue->getLocale())
+                        );
+                    } catch (LogicException $e) {
+                        // couldn't add fallback because of a circular reference
+                    }
+                }
             }
         }
     }


Test patch:
diff --git a/tests/Support/Resources/system_settings.json b/tests/Support/Resources/system_settings.json
index 3c3585d9898..6bd52fa5b38 100644
--- a/tests/Support/Resources/system_settings.json
+++ b/tests/Support/Resources/system_settings.json
@@ -5,11 +5,13 @@
     "valid_languages": [
       "en",
       "de",
+      "de_AT",
       "fr"
     ],
     "fallback_languages": {
       "en": "",
-      "de": "en"
+      "de": "en",
+      "de_AT": "de"
     },
     "default_language": "",
     "debug_admin_translations": false
diff --git a/tests/Unit/Translation/TranslatorTest.php b/tests/Unit/Translation/TranslatorTest.php
index 0e8c0b826d7..b0f29b6f530 100644
--- a/tests/Unit/Translation/TranslatorTest.php
+++ b/tests/Unit/Translation/TranslatorTest.php
@@ -36,6 +36,7 @@ class TranslatorTest extends TestCase
     protected array $locales = [
         'en' => '',
         'de' => 'en',
+        'de_AT' => 'de',
         'fr' => '',
     ];
 
@@ -51,10 +52,32 @@ class TranslatorTest extends TestCase
             'count_plural_n' => '%count% Items',
             'case_key' => 'Lower Case Key',
             'CASE_KEY' => 'Upper Case Key',
+            'fallback_to_EN' => 'EN',
+            'fallback_to_DE' => 'wrong: EN DB',
+            'fallback_to_YML_EN' => '',
+            'fallback_to_YML_DE' => 'wrong: EN DB',
+            'fallback_to_YML_AT' => ''
         ],
         'de' => [
             'simple_key' => 'DE Text',
             'fallback_key' => '',
+            'fallback_to_EN' => '',
+            'fallback_to_DE' => 'DE',
+            'fallback_to_YML_EN' => '',
+            'fallback_to_YML_DE' => '',
+            'fallback_to_YML_AT' => '',
+            'Text As Key' => '',
+            'text_params' => '',
+            'count_key' => '',
+        ],
+        'de_AT' => [
+            'simple_key' => 'AT Text',
+            'fallback_key' => '',
+            'fallback_to_EN' => '',
+            'fallback_to_DE' => '',
+            'fallback_to_YML_EN' => '',
+            'fallback_to_YML_DE' => '',
+            'fallback_to_YML_AT' => '',
             'Text As Key' => '',
             'text_params' => '',
             'count_key' => '',
@@ -270,4 +293,20 @@ public function testSanitizedTranslation(): void
         );
         $this->assertEquals('!@#$%^abc\'" 测试< edf > "', html_entity_decode($dbValue), 'Asserting translation is persisted as sanitized');
     }
+
+    public function testFallbackBetweenYMLandDB(): void
+    {
+        $this->translator->setLocale('de_AT');
+        $this->assertEquals('YML AT', $this->translator->trans('fallback_to_YML_AT'));
+
+        //fallback to german translation when no de_AT is set
+        $this->assertEquals('DE', $this->translator->trans('fallback_to_DE'));
+        $this->assertEquals('YML DE', $this->translator->trans('fallback_to_YML_DE'));
+
+        //fallback to english translation when no de_AT nor german is set
+        $this->translator->setLocale('de_AT');
+        $this->assertEquals('EN', $this->translator->trans('fallback_to_EN'));
+        $this->assertEquals('YML EN', $this->translator->trans('fallback_to_YML_EN'));
+    }
+
 }