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
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
Comment #2
berdirJust 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)
Comment #3
nicxvan commentedPostponed on batch callbacks
Comment #4
nicxvan commentedWe can probably get rid of buildSources here and call sourceBuild directly.
Comment #6
berdirThe blocker is in, I've already started on the MR but it's very much WIP.
Comment #7
nicxvan commentedComment #8
nicxvan commentedGot some questions here, but this is a long way towards what I think we need here!
Test failure is probably real.
Comment #9
berdirI haven't managed to reproduce that one fail locally, but it does seem persistent. Can you reproduce that one?
Comment #10
nicxvan commentedYes, I get this error:
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")Nothing else looks suspicious, but we've had issues with this tests before.
Comment #11
nicxvan commentedI manually followed the steps:
1. Open the installer
2. Choose German
It fails with:
I then replicated the test set up and added drupal-8.0.0.de.po to sites/default/files/translations and pasted:
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.
Comment #12
nicxvan commentedLooking 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.
Comment #13
nicxvan commentedComment #14
nicxvan commentedOk, it turns out there was a fair amount of magic happening in
_install_prepare_importrelying 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.
Comment #15
berdirI 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.
Comment #16
nicxvan commentedI 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.
Comment #19
catchSeems 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
Comment #21
nicxvan commentedI 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:
Then installing German with the following files in translations:
Comment #22
godotislateAre there changes that need to back into main?
Comment #23
nicxvan commentedYes, but this isn't ready yet, after fixing phpstan there are a couple of new failures to review first.
Comment #24
nicxvan commentedOk 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.
Comment #27
catchWent 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.
Comment #29
nicxvan commentedThanks! Assigning to myself to forward port.
Comment #30
catchI think #28 didn't mean to change version/status.
Comment #32
nicxvan commentedYes 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.
Comment #33
berdirWe 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
Comment #35
catchCommitted/pushed to main, thanks!
Comment #38
nicxvan commentedComment #39
catchThis broke 11.4.x installer tests
Comment #41
catchI'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.
Comment #42
berdirSorry, 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:
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?
Comment #43
nicxvan commentedI think a is the better option.
Comment #45
berdirI 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.
Comment #46
berdirOne 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.
Comment #47
nicxvan commentedWell 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.
Comment #49
catchCommitted/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.
Comment #52
berdirRestoring 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.