Problem/Motivation

#3577671: Modernize locale file handling introduced a new Locale Manager, which Drush 13.7.3 (the most recent as of this writing) does not yet support. There is a deprecated wrapper, but it incorrectly handles the following command:

drush locale:import ja mytranslations.po

This completes (incorrectly!) with "No configuration objects have been updated." when the file is full of translations. Nothing is imported. This works On Drupal 11.3 and below.

Here is the root cause analysis from Claude Opus 4.8 which I believe to be correct:

  Root cause — the chain

  1. Drush still calls the old procedural API and passes a plain stdClass.
  vendor/drush/drush/src/Commands/core/LocaleCommands.php:322

  $poFile = (object) ['filename' => basename($file), 'uri' => $file];   // stdClass, NOT a FileInterface
  $poFile = locale_translate_file_attach_properties($poFile, $translationOptions); // $translationOptions['langcode']
  === 'ja'
  $batch  = locale_translate_batch_build([$poFile->uri => $poFile], $translationOptions);

  2. The 11.4 BC shim forces the langcode to und for non-FileInterface input.
  web/core/modules/locale/locale.bulk.inc:213

  function locale_translate_file_attach_properties($file, array $options = []) {
    @trigger_error(/* deprecated in 11.4.0 … */);
    if ($file instanceof FileInterface) {
      $uri = $file->getFileUri();
      $filename = $file->getFilename();
    }
    else {
      $uri = $file->uri;
      $filename = $file->filename;
      $options['langcode'] = LanguageInterface::LANGCODE_NOT_SPECIFIED;   // ← discards caller's 'ja'
    }
    return LocaleFile::createFromPath($filename, $uri, $options['langcode']);   // override = 'und'
  }

  Pre-11.4, this function preserved $options['langcode']. The new shim overwrites it.

  3. createFromPath() treats the non-null 'und' as an authoritative override.
  web/core/modules/locale/src/File/LocaleFile.php:104

  if (isset($matches[5])) {            // regex extracts 'ja' from 'ja.po' into $matches[5]
    $project  = $matches[2] . $matches[3];
    $version  = $matches[4];
    $langcode = $langcodeOverride ?? $matches[5];   // 'und' ?? 'ja'  →  'und'  (?? does not fall through)
  }

  The correctly-detected ja is thrown away; the resulting LocaleFile->langcode is und.

  4. The import operation skips any file whose langcode is und.
  web/core/modules/locale/src/LocaleImportBatch.php:122

  public function batchImport(LocaleFile $file, array $options, ...&$context): void {
    ...
    if (isset($file->langcode) && $file->langcode != LanguageInterface::LANGCODE_NOT_SPECIFIED) {
      // parse + write to DB happens here — NEVER reached for our file
    }
  }

  Why it surfaces now

  11.4 converted the locale batch callbacks to the LocaleImportBatch / LocaleConfigBatch services and replaced the
  procedural file helpers with the LocaleFile value object (change records node/3589759 and node/3577675). Drush 13.7.3
  still calls the procedural BC layer, so it hits the buggy shim. The bug is the shim's unconditional
  $options['langcode'] = LANGCODE_NOT_SPECIFIED for non-FileInterface input.

Proposed resolution

The shim should reflect the provided langcode, not drop it.

Remaining tasks

API changes

Restores behavior to how it worked before.

Issue fork drupal-3600694

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

ptmkenny created an issue. See original summary.

ptmkenny’s picture

Title: LocaleFile and LocaleFileManager fallback shim breaks drush locale:import » 11.4.0-rc1: LocaleFile and LocaleFileManager fallback shim breaks drush locale:import

berdir’s picture

Title: 11.4.0-rc1: LocaleFile and LocaleFileManager fallback shim breaks drush locale:import » locale_translate_file_attach_properties() BC layer incorrectly always sets override langcode to undefined
Status: Active » Reviewed & tested by the community
Issue tags: +11.4.0 release priority

The fix makes sense and I think it can be summarized into a single sentence which I've used as the new issue title. I'll never understand why LLM's need to use so many words, I guess to use as many tokens as possible.

That slipped in because the old implementation had a similar else case, but it was a completely different if condition back then.

  • catch committed c839944b on 11.4.x
    fix: #3600694 locale_translate_file_attach_properties() BC layer...

  • catch committed ab7d042f on main
    fix: #3600694 locale_translate_file_attach_properties() BC layer...

  • catch committed fdcc05b7 on 11.x
    fix: #3600694 locale_translate_file_attach_properties() BC layer...

catch’s picture

Status: Reviewed & tested by the community » Fixed

I'll never understand why LLM's need to use so many words, I guess to use as many tokens as possible.

I think it's partly that, and also that lengthy responses result in something along the lines of a Gish Gallop https://en.wikipedia.org/wiki/Gish_gallop which makes it harder for people to evaluate and more likely to accept the result.

This looks fine, it's on the borderline of where we should start adding test coverage, but given it's 100% in the deprecated code path I think we should just go ahead here.

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.

dpalicepeio@drupbuilder.org’s picture

Hello All,

If have applied the fix.

But, using drush 13.7.3.0 on Drupal core 11.4.0-rc2

when i launch drush locale:check

I obtain
> [error] TypeError: call_user_func_array(): Argument #1 ($callback) must be a valid callback, function "Drupal\locale\LocaleFetch:batchVersionCheck" not found or invalid function name in _drush_batch_worker() (line 257 of /var/www/D11/vendor/drush/drush/includes/batch.inc) #0 /var/www/D11/vendor/drush/drush/includes/batch.inc(204): _drush_batch_worker()
> #1 /var/www/D11/vendor/drush/drush/includes/batch.inc(75): _drush_batch_command()
> #2 /var/www/D11/vendor/drush/drush/src/Commands/core/BatchCommands.php(25): drush_batch_command()
> #3 [internal function]: Drush\Commands\core\BatchCommands->process()
> #4 /var/www/D11/vendor/consolidation/annotated-command/src/CommandProcessor.php(276): call_user_func_array()
> #5 /var/www/D11/vendor/consolidation/annotated-command/src/CommandProcessor.php(212): Consolidation\AnnotatedCommand\CommandProcessor->runCommandCallback()
> #6 /var/www/D11/vendor/consolidation/annotated-command/src/CommandProcessor.php(175): Consolidation\AnnotatedCommand\CommandProcessor->validateRunAndAlter()
> #7 /var/www/D11/vendor/consolidation/annotated-command/src/AnnotatedCommand.php(389): Consolidation\AnnotatedCommand\CommandProcessor->process()
> #8 /var/www/D11/vendor/symfony/console/Command/Command.php(341): Consolidation\AnnotatedCommand\AnnotatedCommand->execute()
> #9 /var/www/D11/vendor/symfony/console/Application.php(1117): Symfony\Component\Console\Command\Command->run()
> #10 /var/www/D11/vendor/drush/drush/src/Application.php(201): Symfony\Component\Console\Application->doRunCommand()
> #11 /var/www/D11/vendor/symfony/console/Application.php(356): Drush\Application->doRunCommand()
> #12 /var/www/D11/vendor/symfony/console/Application.php(195): Symfony\Component\Console\Application->doRun()
> #13 /var/www/D11/vendor/drush/drush/src/Runtime/Runtime.php(113): Symfony\Component\Console\Application->run()
> #14 /var/www/D11/vendor/drush/drush/src/Runtime/Runtime.php(40): Drush\Runtime\Runtime->doRun()
> #15 /var/www/D11/vendor/drush/drush/drush.php(140): Drush\Runtime\Runtime->run()
> #16 /var/www/D11/vendor/bin/drush.php(119): include('...')
> #17 {main}.
> TypeError: call_user_func_array(): Argument #1 ($callback) must be a valid callback, function "Drupal\locale\LocaleFetch:batchVersionCheck" not found or invalid function name in /var/www/D11/vendor/drush/drush/includes/batch.inc on line 257 #0 /var/www/D11/vendor/drush/drush/includes/batch.inc(204): _drush_batch_worker()
> #1 /var/www/D11/vendor/drush/drush/includes/batch.inc(75): _drush_batch_command()
> #2 /var/www/D11/vendor/drush/drush/src/Commands/core/BatchCommands.php(25): drush_batch_command()
> #3 [internal function]: Drush\Commands\core\BatchCommands->process()
> #4 /var/www/D11/vendor/consolidation/annotated-command/src/CommandProcessor.php(276): call_user_func_array()
> #5 /var/www/D11/vendor/consolidation/annotated-command/src/CommandProcessor.php(212): Consolidation\AnnotatedCommand\CommandProcessor->runCommandCallback()
> #6 /var/www/D11/vendor/consolidation/annotated-command/src/CommandProcessor.php(175): Consolidation\AnnotatedCommand\CommandProcessor->validateRunAndAlter()
> #7 /var/www/D11/vendor/consolidation/annotated-command/src/AnnotatedCommand.php(389): Consolidation\AnnotatedCommand\CommandProcessor->process()
> #8 /var/www/D11/vendor/symfony/console/Command/Command.php(341): Consolidation\AnnotatedCommand\AnnotatedCommand->execute()
> #9 /var/www/D11/vendor/symfony/console/Application.php(1117): Symfony\Component\Console\Command\Command->run()
> #10 /var/www/D11/vendor/drush/drush/src/Application.php(201): Symfony\Component\Console\Application->doRunCommand()
> #11 /var/www/D11/vendor/symfony/console/Application.php(356): Drush\Application->doRunCommand()
> #12 /var/www/D11/vendor/symfony/console/Application.php(195): Symfony\Component\Console\Application->doRun()
> #13 /var/www/D11/vendor/drush/drush/src/Runtime/Runtime.php(113): Symfony\Component\Console\Application->run()
> #14 /var/www/D11/vendor/drush/drush/src/Runtime/Runtime.php(40): Drush\Runtime\Runtime->doRun()
> #15 /var/www/D11/vendor/drush/drush/drush.php(140): Drush\Runtime\Runtime->run()
> #16 /var/www/D11/vendor/bin/drush.php(119): include('...')
> #17 {main}
> [warning] Drush command terminated abnormally.

In ProcessBase.php line 171:

Unable to decode output into JSON: Syntax error

TypeError: call_user_func_array(): Argument #1 ($callback) must be a valid callback, function "Drupal\locale\LocaleFetch:batchVersionCheck" not found or invalid function name in _drush_batch_
worker() (line 257 of /var/www/D11/vendor/drush/drush/includes/batch.inc).

dpalicepeio@drupbuilder.org’s picture

An now when i try to check translation using Ui Checking translations , i obtain a red message "
An AJAX HTTP error occurred.
HTTP Result Code: 500
Debugging information follows.
Path: /batch?id=2280&op=do_nojs&op=do
StatusText: 500 Service unavailable (with message)
ResponseText: The website encountered an unexpected error. Try again later.TypeError: Drupal\locale\LocaleSource::loadSource(): Return value must be of type Drupal\locale\LocaleTranslationSource, stdClass returned in Drupal\locale\LocaleSource->loadSource() (line 85 of core/modules/locale/src/LocaleSource.php). Drupal\locale\LocaleFetch->batchVersionCheck() (Line: 300)
_batch_process() (Line: 139)
_batch_do() (Line: 96)
_batch_page() (Line: 52)
Drupal\system\Controller\BatchController->batchPage()
call_user_func_array() (Line: 123)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::wrapControllerExecutionInRenderContext():121}() (Line: 638)
Drupal\Core\Render\Renderer::{closure:Drupal\Core\Render\Renderer::executeInRenderContext():638}()
Fiber->start() (Line: 639)
Drupal\Core\Render\Renderer->executeInRenderContext() (Line: 121)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->wrapControllerExecutionInRenderContext() (Line: 97)
Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber->{closure:Drupal\Core\EventSubscriber\EarlyRenderingControllerWrapperSubscriber::onController():96}() (Line: 183)
Symfony\Component\HttpKernel\HttpKernel->handleRaw() (Line: 76)
Symfony\Component\HttpKernel\HttpKernel->handle() (Line: 53)
Drupal\Core\StackMiddleware\Session->handle() (Line: 30)
Drupal\Core\StackMiddleware\KernelPreHandle->handle() (Line: 28)
Drupal\Core\StackMiddleware\ContentLength->handle() (Line: 32)
Drupal\big_pipe\StackMiddleware\ContentLength->handle() (Line: 118)
Drupal\page_cache\StackMiddleware\PageCache->pass() (Line: 92)
Drupal\page_cache\StackMiddleware\PageCache->handle() (Line: 34)
Drupal\counter\StackMiddleware\CounterMiddleware->handle() (Line: 48)
Drupal\Core\StackMiddleware\ReverseProxyMiddleware->handle() (Line: 51)
Drupal\Core\StackMiddleware\NegotiationMiddleware->handle() (Line: 61)
Drupal\Core\StackMiddleware\AjaxPageState->handle() (Line: 54)
Drupal\Core\StackMiddleware\StackedHttpKernel->handle() (Line: 753)
Drupal\Core\DrupalKernel->handle() (Line: 19)"

and

Oops, something went wrong. Check your browser's developer console for more details.

berdir’s picture

The drush error is https://github.com/drush-ops/drush/issues/6566, merged but not yet released I believe. The second error is #3601433: Fix local_status deprecation by clearing source in a post update, will hopefully still land before 11.4.0. Instead of the patch, you can also run that one line in the post update manually with drush ev or so.

dpalicepeio@drupbuilder.org’s picture

Thank you berdir : I'have just reported the two fixes, batch and UI . All is working

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.