Problem/Motivation

Postponed on at least:
#3581303: Convert locale batch callbacks
#2831617: [pp-1] Deprecate global constants in locale module

  • locale_translation_get_status
  • locale_translation_status_save
  • locale_translation_status_delete_languages
  • locale_translation_status_delete_projects
  • locale_translation_clear_status

These should be refactored and replaced

Steps to reproduce

Proposed resolution

Translation status was a different concept from sources, which is something that has already been partially refactored into the LocaleSource service. The objects were however the same thing, and calls were made into either direction. locale_translation_get_status() called buildSource() and loadSources() called locale_translation_get_status(). All remaining translation status related functions have been deprecated now and merged into the existing LocaleSource service. loadSources() and loadSource() now only load the specific projects that have been requested. They also always return a source for the requested projects/languages, if there is no stored source, one is built automatically.

  • locale_translation_get_status -> \Drupal::service(LocaleSource::class)->loadSources()
  • locale_translation_status_save -> \Drupal::service(LocaleSource::class)->saveSource()
  • locale_translation_status_delete_languages -> \Drupal::service(LocaleSource::class)->deleteSourcesByLanguage()
  • locale_translation_status_delete_projects -> \Drupal::service(LocaleSource::class)->deleteSources($projects)
  • locale_translation_clear_status -> \Drupal::service(LocaleSource::class)->clearSources()

New LocaleTranslationSource value object with the same public properties as the previous stdClass object, so it is backwards compatible.

Even though already in HEAD, functions like get_status() always returned an object for every project/langcode combination, many places have additional isset() checks and the safe function created a new source explicitly, which is could that could never be reached. A lot of thata has been cleaaned up, although checks for specific properties often still use isset() and similar checks.

We explored using a memory cache for this as the batch processes tend to read and write those source objects multiple times per request as there are 3-4 batch operations working on the same source, but it caused a lot of test fails in the installer. Even without that, this still results in significant memory and time improvements as the batch processes only load one project at a time. Previously, every call to locale_translation_get_status() loaded every project on every call.

Something that is still quite confusing but deemed too complex to refactor in this issue is that sources are stored per project, as an array keyed by the language codes. The save method for example receives specific project and langcode but then operates on the list of sources for that project, since that is the structure that's being saved, similar for the method that deletes sources for a given language, so it needs to resave all sources, but that is very rare operation.

Remaining tasks

Review

Follow up, update the CR that references buildSources

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3590050

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

nicxvan created an issue. See original summary.

berdir’s picture

Just copying some comments from the MR of [#] so I can mark them as resolved:

--

this calls get_status() with an array of a single project and langcode, which gets all project data from key value, if it doesn't exist it calls buildSources() again from within the loop on projects and langcodes, which again loops over the exactly one project and langcode and calls sourceBuild(). which always returns something, so the layers of isset() checks are meaningless. what on earth were we doing when we added this code in 2013.
on the plus side, this now already injects the LocaleSource service in this, so if we merge the translation status methods into LocaleSource then this already has the service for that.

--

didn't review closely yet, but was a bit surprised to see one of the batch methods access this key value store directly. This seems to bypass locale_translation_get_status() as that always builds non-existing info?
maybe we can explicitly not inject keyvaluestore and incorporate that use case into the replacement repository service for translation status stuff? (which is another concept/value structure that apparently not even that function knows what it means seems to be tightly coupled to LocaleSource, loadSources(), which is unused in core, seems to call that, so sources == translation status?). it is also highly inefficient, as it always unconditionally does a loadAll() on the key value store.

--

re the comment on sources vs status, this also apparently calls the translation status sources/a source (unrelated to this, just taking notes that maybe all that belongs into the LocaleSource service and we can merge those API's together)

nicxvan’s picture

Title: [pp-?] Deprecate and replace locale_status related functions » [pp-1] Deprecate and replace locale_status related functions
Parent issue: » #3215707: [META] Modernize Locale module
Related issues: +#3566536: [meta] eliminate core .module files

Postponed on batch callbacks

nicxvan’s picture

We can probably get rid of buildSources here and call sourceBuild directly.

berdir’s picture

Status: Postponed » Needs work

The blocker is in, I've already started on the MR but it's very much WIP.

nicxvan’s picture

Title: [pp-1] Deprecate and replace locale_status related functions » Deprecate and replace locale_status related functions
nicxvan’s picture

Got some questions here, but this is a long way towards what I think we need here!

Test failure is probably real.

berdir’s picture

I haven't managed to reproduce that one fail locally, but it does seem persistent. Can you reproduce that one?

nicxvan’s picture

Yes, I get this error:

Time: 00:07.576, Memory: 10.00 MB
There was 1 failure:
1) Drupal\FunctionalTests\Installer\InstallerTranslationMultipleLanguageForeignTest::testTranslationsLoaded
Behat\Mink\Exception\ResponseTextException: The text "German" was not found anywhere in the text of the current page.
/var/www/html/vendor/behat/mink/src/WebAssert.php:907
/var/www/html/vendor/behat/mink/src/WebAssert.php:293
/var/www/html/core/tests/Drupal/Tests/WebAssert.php:991
/var/www/html/core/tests/Drupal/FunctionalTests/Installer/InstallerTranslationMultipleLanguageTest.php:88

Going through the output in reverse order:
No route found for "GET http://web/admin/config/regional/language" (from "http://web/core/install.php?langcode=de&profile=testing_multilingual&id=3&op=finished")

Errors found
Translation
The German translation is not available.
The German translation file is not available at the translation server. Choose a different language or select English and translate your website later.
 2 translation files imported. 8 translations were added, 0 translations were updated and 0 translations were removed.
100%
Completed 4 of 4.

Nothing else looks suspicious, but we've had issues with this tests before.

nicxvan’s picture

I manually followed the steps:
1. Open the installer
2. Choose German

It fails with:

 Translation
The German translation is not available.
The German translation file is not available at the translation server. Choose a different language or select English and translate your website later.

I then replicated the test set up and added drupal-8.0.0.de.po to sites/default/files/translations and pasted:

msgid ""
msgstr ""

msgid "Save and continue"
msgstr "Save and continue de"

msgid "Anonymous"
msgstr "Anonymous de"

msgid "Language"
msgstr "Language de"

#: Testing site name configuration during the installer.
msgid "Drupal"
msgstr "Drupal"

Then I click install and it works as expected.

Edit, I then set a breakpoint in setup and confirmed that the simpletest directory has the .po file for german and it's what we expect.

nicxvan’s picture

Looking at this again, the first loop through LocaleProjectRepository getAll it runs build project and saves everything properly.

Then we get to _install_prepare_import and it calls \Drupal::service(LocaleProjectRepository::class)->set($project); with the version found on the file which is 8.0.0.

I suspect the same thing happens on HEAD, but then we probably have an extra clear on HEAD somewhere we don't have anymore.

I think the right fix might be to set the value to the new version, or use the version constant so it's up to date.

nicxvan’s picture

Issue summary: View changes
nicxvan’s picture

Ok, it turns out there was a fair amount of magic happening in _install_prepare_import relying on side effects.

@berdir asked in slack about it and we all settled on keeping the feature that automatically upgrades the version of a core file to the current by explicitly updating the file rather than relying on some indirection.

There is a follow up created here: #3593118: Simplify _install_prepare_import() and other translation-import functions in the installer

As of right now @berdir, @penyaskito, @gabor and myself participated. I'll credit the participants here as well.

I created a first pass of the CR and updated the IS.

I took another deep review of this and I think it's getting pretty close.

berdir’s picture

Issue summary: View changes
Status: Needs work » Needs review

I think there's no pending feedback on the MR. Did some updates to CR and summary to explain a bit more what we're changing and why.

nicxvan’s picture

Status: Needs review » Reviewed & tested by the community

I think this is ready I've been over this a couple dozen times trying to figure out that failure before we uncovered the tricky bits.

Changes to the CR and IS look good too.

  • catch committed fbc204ef on main
    task: #3590050 Deprecate and replace locale_status related functions
    
    By...

catch’s picture

Version: main » 11.4.x-dev
Status: Reviewed & tested by the community » Patch (to be ported)

Seems OK to do the deprecations in a follow-up, as you say there's plenty going on here already.

Committed/pushed to main, thanks!

This will need a backport MR for 11.x/11.4.x

nicxvan’s picture

I think this is ready though there is a phpstan failure we need to fix and confirm tests are green.

I've tested this manually and reviewed all new changes:

I tested the following scenarios through the UI.
Installing English and non english as is.
Adding these config overrides:

$config['locale.settings']['translation']['default_filename'] = '%project.%language.po';
$config['locale.settings']['translation']['default_server_pattern'] = 'translations://%project.%language.po';

Then installing German with the following files in translations:

  • No files (11.4.-dev gets downloaded and used in both the installer and after install)
  • The test drupal-8.0.0.de.po (No downloaded files the test file is used in both the installer and after install)
  • The test drupal.de.po (11.4.-dev gets downloaded and used for the installer the test file is used after install)
  • Renaming the test file drupal-1.4.-dev.de.po (No downloaded files the test file is used in both the installer and after install)
godotislate’s picture

Are there changes that need to back into main?

nicxvan’s picture

Yes, but this isn't ready yet, after fixing phpstan there are a couple of new failures to review first.

nicxvan’s picture

Status: Patch (to be ported) » Reviewed & tested by the community

Ok I think this is ready again.

This ended up being trickier than we all hoped, but I've tested all of the scenarios in 21 again and they all work manually.

All tests are green, (I contributed to one minor test that said it was testing remote downloads but explicitly disabled remote downloads)

There is a new CR explaining the changes: https://www.drupal.org/node/3594457 I've reviewed these as well and tested it.

The changes since the main merge are here: https://git.drupalcode.org/issue/drupal-3590050/-/compare/390885900e150b... These will need forward porting. The primary change is in the early installer, there is one change in the download logic, everything else is just updating tests for the new process.

  • catch committed b3c0d09b on 11.4.x
    task: #3590050 Deprecate and replace locale_status related functions
    
    By...

  • catch committed f240c62e on 11.x
    task: #3590050 Deprecate and replace locale_status related functions
    
    By...
catch’s picture

Version: 11.4.x-dev » main
Status: Reviewed & tested by the community » Needs work

Went ahead and backported this to 11.4 - there's a tiny chance of annoying someone now, vs. a larger chance of annoying someone later + guaranteed more work. Hopefully this turns out to be the right choice.

Moving to main to forward port the 11.x changes there.

nicxvan’s picture

Version: main » 11.4.x-dev
Assigned: Unassigned » nicxvan
Status: Needs work » Reviewed & tested by the community

Thanks! Assigning to myself to forward port.

catch’s picture

Version: 11.4.x-dev » main
Status: Reviewed & tested by the community » Needs work

I think #28 didn't mean to change version/status.

nicxvan’s picture

Status: Needs work » Needs review

Yes thanks, I didn't mean to change that.

I have forward ported, there were two tests that are skipped on 11.x that failed here.

I deleted one that seems to clearly not test anything anymore and updated the other to be consistent with the changes on the backport.

berdir’s picture

Status: Needs review » Reviewed & tested by the community

We discussed those additional test fails in slack. They end up being identical, as all tests now use the exact version string the installer, so we already remove the dev version here.

They didn't fail on 11.x because they are skipped there.

See also #3594389: Remove unecessary core dev version replacement in locale project info which I've already created earlier today before we found that one. We no longer end up with the kind of version strings that this test was using because we changed the structure of Drupal::VERSION in 2024.

I think LocaleNonInteractiveInstallTest also doesn't add anything useful anymore, it just tests that the translation from the .po file is imported and used, that's what InstallerTranslationMultipleLanguageTest and subclasses do as well and we could completely remove that one too, but lets discuss that in a follow-up, this is already doing way too much. We can also backport the removal of the removal of the subclass to 11.x there. Opened #3594914: Remove LocaleNonInteractiveInstallTest

catch’s picture

Version: main » 11.4.x-dev
Status: Reviewed & tested by the community » Fixed

Committed/pushed to main, thanks!

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

  • catch committed f6c6aec7 on main
    task: #3590050 Deprecate and replace locale_status related functions
    
    By...
nicxvan’s picture

Assigned: nicxvan » Unassigned
catch’s picture

Category: Task » Bug report
Priority: Normal » Critical
Status: Fixed » Needs work

This broke 11.4.x installer tests

Time: 00:12.389, Memory: 12.00 MB

There was 1 failure:

1) Drupal\FunctionalTests\Installer\InstallerTranslationTest::testInstaller
Behat\Mink\Exception\ResponseTextException: The text "Save and continue de" was not found anywhere in the text of the current page.

/var/www/html/vendor/behat/mink/src/WebAssert.php:907
/var/www/html/vendor/behat/mink/src/WebAssert.php:293
/var/www/html/core/tests/Drupal/Tests/WebAssert.php:981
/var/www/html/core/tests/Drupal/FunctionalTests/Installer/InstallerTranslationTest.php:127

  • catch committed 25352cd0 on 11.4.x
    Revert "task: #3590050 Deprecate and replace locale_status related...
catch’s picture

I've reverted the 11.4.x commit (left it in 11.x). We can either add everything back + the fix in 11.4, or worst case this becomes 11.5+ only - but if it's reverted it unblocks other bugfixes going into 11.4 etc.

berdir’s picture

Sorry, that's my fault. I thought I tested that.

I wasn't aware that we *do* still use the major.minor.patch-dev format on minor branches. I checked git history, but only on the main branch and there it doesn't show up of course.

This is essentially #3594389: Remove unecessary core dev version replacement in locale project info. The problem is that the regular expression mentioned there matches on 11.4.0-dev and converts the expected filename to 11.4.x, and then that doesn't match Drupal::VERSION anymore.

I verified that drupal.org supports this format and redirects:

curl -I https://ftp.drupal.org/files/translations/all/drupal/drupal-11.4.0-dev.de.po
HTTP/2 302 
server: nginx
content-type: text/html
location: https://ftp.drupal.org/files/translations/all/drupal/drupal-11.4.x.de.po

To fix this, we have two options:

a) We remove the regex in the two places where it appears, so we always use the exact version string of Drupal::VERSION for core, then it's predictable.
b) Or we keep it, then we need a wrapper/helper/something to apply that same version mangling to the ~20 tests that generate the .po filename.

I'm slightly confused about the linked #3445211: Composer tests fail because of invalid Drupal version in my issue. why was 11.0.0-dev a problem but 11.4.0-dev is OK?

nicxvan’s picture

I think a is the better option.

berdir’s picture

Status: Needs work » Needs review

I created a combined MR with the revert of the revert and the removal of the two regular rexpressions for semver versions. Tested manually with standard, umami, local and remote translation files on 11.4.x-dev and InstallerTranslationTest.php.

your call if we're going to backport this. If not, I'll move the regex changes to #3594389: Remove unecessary core dev version replacement in locale project info and then we can close this. Alternatively I can also create a MR for the other issue that we backport and then revert this commit.

berdir’s picture

One test to be updated but otherwise this seems green. Also pushed a corresponding MR with just the changes against HEAD to #3594389: Remove unecessary core dev version replacement in locale project info.

nicxvan’s picture

Status: Needs review » Reviewed & tested by the community

Well this issue certainly has been a bit of a rollercoaster.

I reviewed the changes at #3594389: Remove unecessary core dev version replacement in locale project info and confirmed those are all here.

I think this is the final round.

Let us know if you want mrs for 11.x and main for the regex here or there!

I think this should be the last iteration.

  • catch committed 5be8c8bc on 11.4.x
    fix: #3590050 Deprecate and replace locale_status related functions
    
    By...
catch’s picture

Status: Reviewed & tested by the community » Fixed

Committed/pushed to 11.4.x, thanks!

Probably easier to do the rest of the follow-ups over on #3594389: Remove unecessary core dev version replacement in locale project info.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

berdir’s picture

Category: Bug report » Task
Priority: Critical » Normal
Issue tags: +11.4.0 release highlights

Restoring issue metadata.

Tagging this as a release highlight as representative of all the issues we completed in 11.4 of #3215707: [META] Modernize Locale module.

For sites with many languages, these issues didn't just do OOP refactoring but include considerable performance improvements for locale batch operations to check and import updated translations. It should be noted that this is mostly visible with *local* translation sources, as the HTTP requests still use the bulk of the time for now, but if you have 100 languages, I can imagine that you're using a workflow with local translation sources as a locale check would otherwise trigger 100 * 66 HTTP requests to drupal.org (I feel like we should think about an improvement for that, but that's a different topic) On a test site with 66 projects and 38 languages, on Drupal 11.3, drush locale:check takes 17.7s for me. On 11.4, it's 2.3s. In #3586654: Avoid scanning the file system for local po files, with a partial set of these changes, someone with 38 languages and 200 modules reported 7s instead of 5 minutes.