8000 [Translation] translation:push command diff using intl-icu by Broutard · Pull Request #47986 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[Translation] translation:push command diff using intl-icu #47986

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from

Conversation

Broutard
Copy link
@Broutard Broutard commented Oct 25, 2022
Q A
Branch? 5.4
Bug fix? yes
New feature? no
Deprecations? no
Tickets Fix #47886
License MIT

Provider translations do not come back with "+intl-icu" in $provider->read($domains, $locales);, so local translations should not include it for the diff.

@welcoMattic
Copy link
Member
welcoMattic commented Oct 25, 2022

Hi @Broutard, thanks for your contribution. 6.0 is not a maintained version, can you rebase your branch on 5.4?

About the code, it seems fair, but can you add or modify tests to cover the bug you fix? Thank you

@stof
Copy link
Member
stof commented Oct 25, 2022

@welcoMattic the 6.0 branch is a maintained one (as its support has been extended by 6 months due to the bump of the min PHP version in 6.1): https://symfony.com/releases

However, if the 6.0 branch is affected, the 5.4 branch will also be affected, and it should indeed be the target of the fix.

@welcoMattic
Copy link
Member

@stof you are right, I forgot about this exception, sorry.

@Broutard Broutard changed the base branch from 6.0 to 5.4 October 25, 2022 14:17
@Broutard
Copy link
Author

I'm trying to fix tests.

But sorry, I don't understand how the push/pull should work.

  • The push command push the domains without the "+intl-icu" suffix (MessageCatalogue::all() remove the suffix).
    But the following times, the diff between local and provider is made with the suffix... this is which I try to fix.

  • The pull command (with the --intl-icu option) pull the domains from the provider (so without the suffix) to the intl+icu file
    But the tests of the pull command use two domains : --domains=messages --domains=messages+intl-icu

I don't understand if we shoud use a domain like "messages+intl-icu" or a "messages" domain with the --intl-icu option :(

@a-vallet
Copy link
a-vallet commented Nov 7, 2022

Hello, we are also having this bug in Symfony 5.4, pushing to Loco overwrite every translations due to bug in diff with intl-icu.
And we hope to fix it quick.

@Broutard for the pull command i've done some test to understand how it works, and i would say "a "messages" domain with the --intl-icu option "

Because, the TranslationProviderCollectionFactory will generate all the providers from the config translation.yaml

TranslationProviderCollectionFactory

(here for exemple my config)

image

and then if we come back on our TranslationPullCommand we'll be routed to the good provider by the providerCollection->get who's a collection of FilteringProvider.

so when u'll be at the line 159 $providerTranslations = $provider->read($domains, $locales);
u'll go into FilteringProvider->read function where locales and domains u've put in options will be filtered by provider config and then launch read from the provider in option exemple LocoProvider.

image

So if u don't put a name in --domain existing into your config file, FilteringProvider will gave an empty array to LocoProvider creating a domain ['*']

image

you probably put in your config domain name as messages so for me u'll have to use this to pull with --domain --intl-icu

@a-vallet
Copy link
a-vallet commented Nov 8, 2022

@Broutard After some researches on tests fail, the fix seems to be the problem.

if ($intlMessages = $catalogue->all($intlDomain)) { $filteredCatalogue->add($intlMessages, $domain); }

Doing this fix the push command well because Catalogue has same array key with domain name but this function is also use for pull command creating a new bug.

image

As you can see above we'll generate domain with +intl-icu and put into $intlMessages the translations catalogue if it found datas but with $domain into $filteredCatalogue->add we add them to a catalogue messages and not messages+intl-icu

second if does the diff between all key for domain (intl-icu or not) with $intlMessages who's keeping intl-icu keys in order to divide in two files if u've a domain messages and message+intl-icu as you've in test file.

So with this we create only one messages domain and in TranslationPullCommandTest for testPullNewXlf12Messages() function assertXmlStringEqualsXmlString is used on both domains looking for 2 keys into each but found all keys into messages one, so it return false not getting the content expected.

image

We've to take problem from another angle, LocoProvider havent information about intl-icu domain so we cant add into catalogue translations messages+intl-icu to fix push command

For me only way is to modify as you does TranslationTrait, used by pull and push command. We need to divide pull and push call.

  • Push is related to translations provider who like Loco arent taking intl-icu information, so we need to get locale translations into one same domain as Loco do all into messages (fix already done)

  • For Pull we need another way, keeping this +intl-icu management to divide again correctly domain with and without +intl-icu (for test at least, I dont know if its a common practice to have a domain with same name with and without but it'll fix test)

@Broutard
Copy link
Author
Broutard commented Nov 8, 2022

@a-vallet indeed, sorry for the inconvenience.
This is why my fix doesn't pass tests... It introduce another bug in the pull command.

I will try to find another way to fix it.

@Broutard
Copy link
Author

Fixed, by merging intl-icu domains only for the push command.

@Kocal
Copy link
Member
Kocal commented Nov 14, 2022

Hi everyone,

If it can helps you, I had many issues with pushing ICU and non-ICU messages from the same domains, this is how I fixed it, as a one-shot, for Crowdin (in symfony 5.4):

--- vendor/symfony/crowdin-translation-provider/CrowdinProvider.php.old	2022-11-03 22:15:27.000000000 +0100
+++ vendor/symfony/crowdin-translation-provider/CrowdinProvider.php	2022-11-04 11:47:08.000000000 +0100
@@ -15,6 +15,8 @@
 use Symfony\Component\Translation\Dumper\XliffFileDumper;
 use Symfony\Component\Translation\Exception\ProviderException;
 use Symfony\Component\Translation\Loader\LoaderInterface;
+use Symfony\Component\Translation\MessageCatalogue;
+use Symfony\Component\Translation\MessageCatalogueInterface;
 use Symfony\Component\Translation\Provider\ProviderInterface;
 use Symfony\Component\Translation\TranslatorBag;
 use Symfony\Component\Translation\TranslatorBagInterface;
@@ -63,33 +65,48 @@
         $responses = [];
 
         foreach ($translatorBag->getCatalogues() as $catalogue) {
-            foreach ($catalogue->getDomains() as $domain) {
-                if (0 === \count($catalogue->all($domain))) {
-                    continue;
-                }
+            foreach ($catalogue->getDomains() as $domainWithoutSuffix) {
+                foreach (['', MessageCatalogueInterface::INTL_DOMAIN_SUFFIX] as $suffix) {
+                    $domain = $domainWithoutSuffix.$suffix;
 
-                $content = $this->xliffFileDumper->formatCatalogue($catalogue, $domain, ['default_locale' => $this->defaultLocale]);
 
-                $fileId = $this->getFileIdByDomain($fileList, $domain);
+                    // Remove ICU messages from the non-ICU catalogue, as MessageCatalogue::all() merges them :/
+                    $fixedCatalogue = $catalogue;
 
-                if ($catalogue->getLocale() === $this->defaultLocale) {
-                    if (!$fileId) {
-                        $file = $this->addFile($domain, $content);
-                    } else {
-                        $file = $this->updateFile($fileId, $domain, $content);
-                    }
+                    if ($domainWithoutSuffix === $domain) {
+                        $messages = \Closure::bind(fn() => $this->messages, $catalogue, MessageCatalogue::class)();
+                        unset($messages[$domainWithoutSuffix.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX]);
 
-                    if (!$file) {
-                        continue;
+                        $fixedCatalogue = new MessageCatalogue($catalogue->getLocale(), $messages);
                     }
 
-                    $fileList[$file['name']] = $file['id'];
-                } else {
-                    if (!$fileId) {
+                    if (0 === \count($fixedCatalogue->all($domain))) {
                         continue;
                     }
 
-                    $responses[] = $this->uploadTranslations($fileId, $domain, $content, $catalogue->getLocale());
+                    $content = $this->xliffFileDumper->formatCatalogue($fixedCatalogue, $domain, ['default_locale' => $this->defaultLocale]);
+
+                    $fileId = $this->getFileIdByDomain($fileList, $domain);
+
+                    if ($fixedCatalogue->getLocale() === $this->defaultLocale) {
+                        if (!$fileId) {
+                            $file = $this->addFile($domain, $content);
+                        } else {
+                            $file = $this->updateFile($fileId, $domain, $content);
+                        }
+
+                        if (!$file) {
+                            continue;
+                        }
+
+                        $fileList[$file['name']] = $file['id'];
+                    } else {
+                        if (!$fileId) {
+                            continue;
+                        }
+
+                        $responses[] = $this->uploadTranslations($fileId, $domain, $content, $fixedCatalogue->getLocale());
+                    }
                 }
             }
         }

and for Lokalise (the most recent version):

--- vendor/symfony/lokalise-translation-provider/LokaliseProvider.php	2022-11-14 19:43:50
+++ vendor/symfony/lokalise-translation-provider/LokaliseProvider.php	2022-11-14 19:51:51
@@ -82,16 +82,18 @@

         foreach ($existingKeysByDomain as $domain => $existingKeys) {
             $allKeysForDomain = array_keys($defaultCatalogue->all($domain));
+            $allKeysForDomainIcu = array_keys($defaultCatalogue->all($domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX));
+
             foreach (array_keys($existingKeys) as $keyName) {
                 unset($allKeysForDomain[$keyName]);
             }
-            $keysToCreate[$domain] = $allKeysForDomain;
+            $keysToCreate[$domain.MessageCatalogueInterface::INTL_DOMAIN_SUFFIX] = $allKeysForDomainIcu;
+            $keysToCreate[$domain] = array_values(array_diff($allKeysForDomain, $allKeysForDomainIcu));
         }

         foreach ($keysToCreate as $domain => $keys) {
             $createdKeysByDomain[$domain] = $this->createKeys($keys, $domain);
         }
-

         $this->updateTranslations(array_merge_recursive($createdKeysByDomain, $existingKeysByDomain), $translatorBag);
     }

I'm not 100% sure that it 100% works, but maybe it can help you in a certain way.

@Broutard
Copy link
Author

@welcoMattic is my fix is okay for you ?
Does it have a chance of being merged quickly ?

Thanks

@welcoMattic
Copy link
Member

@Broutard If I understand correctly this fix, it will merge ICU translations with non-ICU translation in the same Catalogue? If yes, I'm not sure that it will be the right way to fix the issue.

If we take a step back, in theory, it's not recommended to work with ICU and non-ICU translations. However in real life, I understand that it could happen. But the fact is Symfony will not differentiate messages+intl-icu and messages domains. From the Catalogue point of view, they are the same domain.

@Kocal worked on some fixes for Crowdin and Lokalise Providers. Moreover, we discussed privately, and he's digging into all these issues to correctly understand what is wrong and how to fix it permanently. @Kocal can you let us know when you open issues/PRs?

@Kocal
Copy link
Member
Kocal commented Nov 16, 2022

@welcoMattic I'm currently donating my blood 😛, and after that I have to work. I will try to open the issues and PRs this evening.

@welcoMattic
Copy link
Member

@Kocal no rush 😉

@a-vallet
Copy link

@welcoMattic Was not really to merge ICU and not ICU, personnaly i've only ICU translations but when I want to push new keys to localise.biz (Loco) TranslationTrait which read local translations to compare with Loco and get new keys in local will duplicate Catalogue in the TranslatorBag, if i've a "messages+intl-icu" translation file i'll have two same catalogue "messages+intl-icu" and "messages", when diff will be done it will return all keys due to "messages+intl-icu" not existing in Loco tags.

This fix is to when you push to Loco merge on to same domain as Loco do with tags and differentiate working with pull command to get back ICU as before.

@welcoMattic
Copy link
Member

@a-vallet Ok, so what we have to fix here is to not have both messages+intl-icu and messages files content in the same catalogue. As I said, those 2 filename must be considered in Symfony as the same domain.

@Broutard
Copy link
Author
Broutard commented Nov 16, 2022

In fact, when we use translation:push, a diff is made between local and provider translations.
But provider translations don't include the ICU suffix so the diff doesn't work (it compare with an empty local messages array because our messages are in messages+intl-icu)

By merging ICU translations into the base domain (without +intl-icu) the diff works as expected.
This is the only purpose of this fix.

@a-vallet
Copy link

But it's a fix for Loco Provider in a Translations part used by others.

Question i've now is : Does this fix impact other providers as Crowdin or Lokalize ? Those was maybe working well.

@Broutard
Copy link
Author
Broutard commented Nov 21, 2022

@a-vallet
Tested with Loco & Crowdin (which has the same issue), and the fix works for both.

I did not succeed with Lokalise, I have systematically an error with it : Unable to export translations from Lokalise: "{"error":{"message":"No keys for export with current export settings","code":406}}" ... with or without this fix.
Their api has changed and for the moment, this provider does not work anymore.

@Broutard Broutard requested a review from maxbeckers December 3, 2022 10:57
@nicolas-grekas nicolas-grekas removed this from the 6.0 milestone Jan 25, 2023
@nicolas-grekas nicolas-grekas added this to the 6.2 milestone Jan 25, 2023
@welcoMattic
Copy link
Member

@Broutard if your fix works well with Loco and Crowdin, but fails with Lokalise because of a change on their own side, I guess we could consider your fix as ok. We will have to check with Lokalise once we have fixed the issue mentioned here.

6DAF

@welcoMattic
Copy link
Member

Closing in favor of #49833 which fix the bug at a lower level

fabpot added a commit that referenced this pull request Mar 30, 2023
…gue domains instead of operation domains (welcoMattic)

This PR was merged into the 5.4 branch.

Discussion
----------

[Translation] TranslatorBag::diff now iterates over catalogue domains instead of operation domains

| Q             | A
| ------------- | ---
| Branch?       | 5.4
| Bug fix?      | yes
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tickets       | Fix #47886 <!-- prefix each issue number with "Fix #", no need to create an issue if none exists, explain below instead -->
| License       | MIT
| Doc PR        |

For Loco Provider (at least, but I'm pretty sure it applies for Lokalise and Crowdin), the `--force` option on `translation:push` command has no effect.

The main cause is located in the `TranslatorBag::diff` method which was iterating over `$operation->getDomains()` to add new messages to the diff catalogue. But, the fact is `$operation->getDomains()` returns all domains *AND* their `+intl-icu` variants! Which is certainly not what we want here.

See the test that covers the case

```php
public function testDiffWithIntlDomain()
{
    $catalogueA = new MessageCatalogue('en', [
        'domain1+intl-icu' => ['foo' => 'foo', 'bar' => 'bar'],
        'domain2' => ['baz' => 'baz', 'qux' => 'qux'],
    ]);

    $bagA = new TranslatorBag();
    $bagA->addCatalogue($catalogueA);

    $catalogueB = new MessageCatalogue('en', [
        'domain1' => ['foo' => 'foo'],
        'domain2' => ['baz' => 'baz', 'corge' => 'corge'],
    ]);

    $bagB = new TranslatorBag();
    $bagB->addCatalogue($catalogueB);

    $bagResult = $bagA->diff($bagB);

    $this->assertEquals([
        'en' => [
            'domain1' => ['bar' => 'bar'],
            'domain2' => ['qux' => 'qux'],
        ],
    ], $this->getAllMessagesFromTranslatorBag($bagResult));
}
```

Without the change, `'foo' => 'foo'` was in the result bag.

Replaces #47986

Commits
-------

178abbf TranslatorBag::diff now iterates over catalogue domains instead of operation domains
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

8 participants
0