Skip to content
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

feat: New command lang:sync #9023

Open
wants to merge 12 commits into
base: 4.6
Choose a base branch
from

Conversation

neznaika0
Copy link
Contributor

@neznaika0 neznaika0 commented Jul 5, 2024

Description
A simple command to copy existing translation files to a new locale.
From the file Language/en/Example.php:

<?php

return [
    'title' => 'Default title',
    'status' => [
        'error'    => 'Error!',
        'done'     => 'Done!',
        'critical' => 'Critical!',
    ],
    'nullableKey' => null,
    'numericKey' => 100500,
    'more' => [
        'nested' => [
            'key' => 'More nested key...',
        ],
    ],
];

After the command php spark lang:sync --locale en --target ru we get Language/ru/Example.php .

<?php

return array (
  'more' => 
  array (
    'nested' => 
    array (
      'key' => 'Example.more.nested.key',
    ),
  ),
  'nullableKey' => 'Example.nullableKey',
  'numericKey' => 'Example.numericKey',
  'status' => 
  array (
    'error' => 'Example.status.error',
    'done' => 'Example.status.done',
    'critical' => 'Example.status.critical',
  ),
  'title' => 'Example.title',
);

applied cs-fix:

<?php

return [
    'more' => [
        'nested' => [
            'key' => 'Example.more.nested.key',
        ],
    ],
    'nullableKey' => 'Example.nullableKey',
    'numericKey'  => 'Example.numericKey',
    'status'      => [
        'error'    => 'Example.status.error',
        'done'     => 'Example.status.done',
        'critical' => 'Example.status.critical',
    ],
    'title' => 'Example.title',
];

And so it is with all other translations

=====

When there are translations in the target folder Language/ru, their keys are saved only if they exist in the Language/en folder:

Language/ru/Example.php:

<?php

return [
    // Deleted keys
    // 'more' => [
    //     'nested' => [
    //         'key' => 'Example.more.nested.key',
    //     ],
    // ],
    'nullableKey' => null,
    'numericKey'  => 100000,
    'status'      => [
        'error'    => 'ru.status.error',
        'done'     => 'ru.status.done',
        'critical' => 'ru.status.critical',
    ],
    'title' => 'ru.title',
    'mistakeKey' => 'ru.mistake_key',
];

Language/ru/Example.php after sync:

<?php

return [
    'more' => [
        'nested' => [
            'key' => 'Example.more.nested.key',
        ],
    ],
    'nullableKey' => null,
    'numericKey'  => 100000,
    'status'      => [
        'error'    => 'ru.status.error',
        'done'     => 'ru.status.done',
        'critical' => 'ru.status.critical',
    ],
    'title' => 'ru.title',
];

Checklist:

  • Securely signed commits
  • Component(s) with PHPDoc blocks, only if necessary or adds value
  • Unit testing, with >80% coverage
  • User guide updated
  • Conforms to style guide

@neznaika0
Copy link
Contributor Author

neznaika0 commented Jul 5, 2024

There are some questions:

  1. Does anyone else need it? 😄
    I have indicated the reason for using it as a more precise translation control.

  2. I need help with the documentation. I don't know English

  3. Formatting new arrays requires the use of php-cs-fixer. This was discussed in another PR feat: Language translations finder and update #7896 (comment) . How can we take this out for general use?

  4. phpstan. I don't quite understand what he wants from me. I have specified a common type for the values

@neznaika0
Copy link
Contributor Author

Userguide:

image

Changelog:

Снимок экрана от 2024-07-05 20-58-21

@ddevsr ddevsr added the 4.6 label Jul 6, 2024
@kenjis kenjis added the enhancement PRs that improve existing functionalities label Jul 6, 2024
@kenjis
Copy link
Member

kenjis commented Aug 1, 2024

I don't get the description "When there are translations in the target folder Language/ru, their keys are saved only if they exist in the Language/en folder:". Is that correct?

If app/Language/en/Example.php is:

return [
    // Deleted keys
    // 'more' => [
    //     'nested' => [
    //         'key' => 'Example.more.nested.key',
    //     ],
    // ],
    'nullableKey' => null,
    'numericKey'  => 100000,
    'status'      => [
        'error'    => 'ru.status.error',
        'done'     => 'ru.status.done',
        'critical' => 'ru.status.critical',
    ],
    'title' => 'ru.title',
    'mistakeKey' => 'ru.mistake_key',
];

Then, app/Language/ru/Example.php will be:

return [
    'mistakeKey'  => 'Example.mistakeKey',
    'nullableKey' => 'Example.nullableKey',
    'numericKey'  => 'Example.numericKey',
    'status'      => [
        'error'    => 'Example.status.error',
        'done'     => 'Example.status.done',
        'critical' => 'Example.status.critical',
    ],
    'title' => 'Example.title',
];

That is, deleted keys are gone.

@kenjis
Copy link
Member

kenjis commented Aug 1, 2024

If app/Language/en/Example.php is:

return [
    'title'  => 'Default title',
    'status' => [
        'error'    => 'Error!',
        'done'     => 'Done!',
        'critical' => 'Critical!',
    ],
    'nullableKey' => null,
    'numericKey'  => 100500,
    'more'        => [
        'nested' => [
            'key' => 'More nested key...',
        ],
    ],
];

Then, app/Language/ru/Example.php will be:

return [
    'more' => [
        'nested' => [
            'key' => 'Example.more.nested.key',
        ],
    ],
    'nullableKey' => 'Example.nullableKey',
    'numericKey'  => 'Example.numericKey',
    'status'      => [
        'error'    => 'Example.status.error',
        'done'     => 'Example.status.done',
        'critical' => 'Example.status.critical',
    ],
    'title' => 'Example.title',
];

That is, the key order changed.
This makes comparison of the two files, original and translated, difficult.

.. versionadded:: 4.6.0

After working on your translation for the current language for a long time, in some cases you will need to create files for another language.
The ``spark lang:find`` command can be used, it is not prohibited. But it cannot fully detect absolutely all translations. Especially if the parameters are dynamically set as ``lang('App.status.' . $key, ['payload' => 'John'], 'en')``.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why you added , it is not prohibited. What do you want to say by it?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, I don't get the following whole explanation.

The spark lang:find command can be used, it is not prohibited. But it cannot fully detect absolutely all translations. Especially if the parameters are dynamically set as lang('App.status.' . $key, ['payload' => 'John'], 'en'). In this case, the best solution is to copy the finished language files and translate them. This way you can save your unique keys that the command did not find.

"to copy the finished language files and translate them"
What do I need to do exactly? If I copy the file, there is no need of lang commands.

I also don't get why the explanation is there. What's the relation with the lang:sync command?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I warned that my English is bad.

I wrote that the lang:find helps you find NEW keys. What if you don't need to search? Or lang:find couldn't find complex translations as lang('App.title', [], $locale)?
In this case, you must manually add the translation.

What do I need to do exactly? If I copy the file, there is no need of lang commands.

You will copy a identical array. After that, you need to manually search for and change existing keys (if available). When there is no file for --target locale, yes it is logical (copy-paste).
Command is more suitable for merging.

Is it embarrassing? Then why do the make:controller and others commands, is it possible to copy an available controller?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. remove , it is not prohibited. I think it is not needed.
  2. move the whole explanation to the section "Generating Translation Files via Command". Because it is an explanation for lang:find, not lang:sync.

@kenjis
Copy link
Member

kenjis commented Aug 1, 2024

This command would be useful.
However, I would like the order of the keys to match the original.

@neznaika0
Copy link
Contributor Author

I don't get the description "When there are translations in the target folder Language/ru, their keys are saved only if they exist in the Language/en folder:". Is that correct?

If app/Language/en/Example.php is:
...

No. Look at the order of the files. We from EN combine RU. There are translations in RU ru.title, so the keys are saved and not overwritten Example.title. Old keys remain.

That is, the key order changed.
This makes comparison of the two files, original and translated, difficult.

Yes. The order has changed. I'm sorting a new array. I think it can be fixed. But the keys will be chaotic when combined.

@kenjis
Copy link
Member

kenjis commented Aug 2, 2024

This PR branch is too old. Can you rebase?

@kenjis
Copy link
Member

kenjis commented Aug 2, 2024

No. Look at the order of the files. We from EN combine RU. There are translations in RU ru.title, so the keys are saved and not overwritten Example.title. Old keys remain.

Okay, I got what you say.

app/Language/en/Example.php:

return [
    'title'  => 'Default title',
    'status' => [
        'error'    => 'Error!',
        'done'     => 'Done!',
        'critical' => 'Critical!',
    ],
    'nullableKey' => null,
    'numericKey'  => 100500,
    'more'        => [
        'nested' => [
            'key' => 'More nested key...',
        ],
    ],
];

app/Language/ru/Example.php:

<?php

return [
    // Deleted keys
    // 'more' => [
    //     'nested' => [
    //         'key' => 'Example.more.nested.key',
    //     ],
    // ],
    'nullableKey' => null,
    'numericKey'  => 100000,
    'status'      => [
        'error'    => 'ru.status.error',
        'done'     => 'ru.status.done',
        'critical' => 'ru.status.critical',
    ],
    'title' => 'ru.title',
    'mistakeKey' => 'ru.mistake_key',
];
$ php spark lang:sync --locale en --target ru
$ composer cs-fix

app/Language/ru/Example.php:

return [
    'more' => [
        'nested' => [
            'key' => 'Example.more.nested.key',
        ],
    ],
    'nullableKey' => null,
    'numericKey'  => 100000,
    'status'      => [
        'error'    => 'ru.status.error',
        'done'     => 'ru.status.done',
        'critical' => 'ru.status.critical',
    ],
    'title' => 'ru.title',
];

@kenjis
Copy link
Member

kenjis commented Aug 2, 2024

Yes. The order has changed. I'm sorting a new array. I think it can be fixed. But the keys will be chaotic when combined.

I think the key order should not be changed.

Can't you rewrite with recursive calls?
This is PoC sample code by ChatGPT:

<?php

function syncTranslationsRecursive($original, $translations) {
    // Initialize an array to store the results
    $syncedTranslations = [];

    // Loop through the original message array
    foreach ($original as $key => $message) {
        if (is_array($message)) {
            // If the message is an array, recursively synchronize it
            $syncedTranslations[$key] = syncTranslationsRecursive(
                $message,
                array_key_exists($key, $translations) ? $translations[$key] : []
            );
        } else {
            // If the message is not an array, check if the translations array contains the same key
            if (array_key_exists($key, $translations)) {
                $syncedTranslations[$key] = $translations[$key];
            } else {
                // If the key doesn't exist in the translations array, set an empty string
                $syncedTranslations[$key] = '';
            }
        }
    }

    return $syncedTranslations;
}

// Original message array
$original = [
    'title' => 'Default title',
    'status' => [
        'error'    => 'Error!',
        'done'     => 'Done!',
        'critical' => 'Critical!',
    ],
    'more' => [
        'nested' => [
            'key' => 'More nested key...',
        ],
    ],
];

// Translated message array (partially translated)
$translations = [
    'title' => 'Título predeterminado',
    'status' => [
        'error' => '¡Error!',
        'done'  => '¡Hecho!',
    ],
];

// Synchronize the translations array with the original message array
$syncedTranslations = syncTranslationsRecursive($original, $translations);

// Print the results
print_r($syncedTranslations);

@neznaika0
Copy link
Contributor Author

@kenjis
Copy link
Member

kenjis commented Aug 2, 2024

I would like the key order is never changed in any case.
If so, I don't care about the implementation.

@neznaika0
Copy link
Contributor Author

neznaika0 commented Aug 5, 2024

Results based on the data above:

// translation RU is empty
return [
    'title'  => 'Example.title',
    'status' => [
        'error'    => 'Example.status.error',
        'done'     => 'Example.status.done',
        'critical' => 'Example.status.critical',
    ],
    'nullableKey' => 'Example.nullableKey',
    'numericKey'  => 'Example.numericKey',
    'more'        => [
        'nested' => [
            'key' => 'Example.more.nested.key',
        ],
    ],
];
// translation RU exist
return [
    'nullableKey' => null,
    'numericKey'  => 100000,
    'status'      => [
        'error'    => 'ru.status.error',
        'done'     => 'ru.status.done',
        'critical' => 'ru.status.critical',
    ],
    'title' => 'ru.title',
    'more'  => [
        'nested' => [
            'key' => 'Example.more.nested.key',
        ],
    ],
];

@kenjis
Copy link
Member

kenjis commented Aug 5, 2024

When translation RU exist, the key order will be different from EN.
In any case, the key order should be the same in all lang files.

@neznaika0
Copy link
Contributor Author

// Before: app/Language/en/Example.php
return [
    'title'  => 'Default title',
    'status' => [
        'error'    => 'Error!',
        'done'     => 'Done!',
        'critical' => 'Critical!',
    ],
    'nullableKey' => null,
    'array'       => [],
    'numericKey'  => 100500,
    'more'        => [
        'nested' => [
            'key' => 'More nested key...',
        ],
    ],
];
// Before: app/Language/ru/Example.php
return [
    // Deleted keys
    // 'more' => [
    //     'nested' => [
    //         'key' => 'Example.more.nested.key',
    //     ],
    // ],
    'nullableKey' => null,
    'status'      => [
        'critical' => 'ru.status.critical',
        'done'     => 'ru.status.done',
        'error'    => 'ru.status.error',
    ],
    'mistakeKey' => 'ru.mistake_key',
    'numericKey'  => 100000,
    'title' => 'ru.title',
];
// After: app/Language/ru/Example.php
// 1. Right sort as in EN
// 2. Delete 'mistakeKey' (in EN language not exist)
// 3. Untranslated keys have a placeholder ''Example.more.nested.key'
// 4. Previously translated keys are saved
return [
    'title'  => 'ru.title',
    'status' => [
        'error'    => 'ru.status.error',
        'done'     => 'ru.status.done',
        'critical' => 'ru.status.critical',
    ],
    'nullableKey' => null,
    'array'       => [
    ],
    'numericKey' => 100000,
    'more'       => [
        'nested' => [
            'key' => 'Example.more.nested.key',
        ],
    ],
];

The array intersection search function ArrayHelper::intersectKeyRecursive is no longer needed.
Combining arrays should now initially not have extra keys (current mistakeKey) from the target array.

@datamweb
Copy link
Contributor

datamweb commented Aug 9, 2024

I prefer the following result. To make it easier for the developer to translate the file.

// After: app/Language/ru/Example.php
return [
    'title'  => '(To be translated) Default title',
    'status' => [
        'error'    => '(To be translated) Error!',
        'done'     => '(To be translated) Done!',
        'critical' => '(To be translated) Critical!',
    ],
    'nullableKey' => null,
    'array'       => [
    ],
    'numericKey' => 100000,
    'more'       => [
        'nested' => [
            'key' => '(To be translated) More nested key...',
        ],
    ],
];

Comment on lines +113 to +114
'b' => 2000,
'c' => null,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do not accept int or null as lang messages.
So should show an error message when processing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe. These values are more for the translation search test.
I can filter out the bad lines for RU keys, if it's important.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please do one of the following:

  • show errors (or just throw Exception?) if there are invalid values in any lang files.
  • add comments in the test code that "values other than string is invalid, but we use invalid values just for testing".

@neznaika0
Copy link
Contributor Author

neznaika0 commented Aug 9, 2024

@datamweb I think it's wrong. If the current locale is RU and the translations are the same (EN), it is unclear on the page whether the translation was made or not. Because the fallback translation is EN.

Additional text interferes with the preview of the page (too long for some places).

Placeholders clearly show untranslated lines.

It is possible to make it configurable --replace-old

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
4.6 enhancement PRs that improve existing functionalities
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants