Problem/Motivation

Some modules want to include the built-in translations as .po files, especially those that aren't published on Drupal.org and don't have a project in localize.drupal.org.

Usually, they place them here [module_root]/translations/[langcode].po.

And we have special configuration keys in the my_module.info.yml file to automatically import those translations during the module install:

'interface translation project': my_module
'interface translation server pattern': translations/%language.po

But suddenly, they don't work as expected because the configured path translations/%language.po is considered as the path from the Drupal root, not from the module root!

Steps to reproduce

1. Create a custom module my_module and put the translation info into the my_module.info.yml file:

'interface translation project': my_module
'interface translation server pattern': translations/%language.po

2. Place some translation files there, for example - translations/de.po with some translation strings.

3. Install the module.

4. See that no translation strings were imported during the installation.

Proposed resolution

To not break the previous behavior, I'd propose to introduce the module-relative path placeholder module:// and use it like this:

'interface translation project': my_module
'interface translation server pattern': module://translations/%language.po

And extend the current Drupal Core logic to replace it with the actual module path.

For now, we can use a workaround with a custom hook like this:

function my_module_locale_translation_projects_alter(&$projects) {
  $moduleName = 'my_module';
  $modulePath = \Drupal::service('module_handler')->getModule($moduleName)->getPath();
  $projects[$moduleName]['info']['interface translation server pattern'] =
  str_replace(
    'module://',
    $modulePath . '/',
    $projects[$moduleName]['info']['interface translation server pattern']
  );

Remaining tasks

User interface changes

Introduced terminology

API changes

Data model changes

Release notes snippet

Issue fork drupal-3483087

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

murz created an issue. See original summary.

murz’s picture

Issue summary: View changes
Status: Active » Needs review

I proposed a fix for this in the MR, please review. If it's okay, I can extend the tests to check this feature.

smustgrave’s picture

Version: 11.0.x-dev » 11.x-dev
Status: Needs review » Needs work

Have not reviewed yet but MR should be pointed at 11.x

thanks!

murz’s picture

Status: Needs work » Needs review

Reworked MR to the 11.x branch, please review.

smustgrave’s picture

Status: Needs review » Needs work
Issue tags: +Needs Review Queue Initiative, +Needs tests

Thanks for reporting, think next step would be to get test coverage added.

liam morland made their first commit to this issue’s fork.

liam morland’s picture

I have added commits that improve the documentation comments.

Existing tests are passing. Tests need to be added for the new functionality.

Another approach would be to make a %project_root token available for use in interface translation server pattern.

liam morland’s picture

Title: No ways to configure module-relative path for included module translation .po files » Allow project-relative path for included translation .po files
johnatas’s picture

Hi,
I'm in the same situation, and MR !10167 works perfectly for my modules.

Note that — though I'm not sure if it's considered best practice — I'm experiencing the same issue with my themes as well.
Would it be possible to extend this fix to themes too?
Just for reference, I tested the following code on my project (based on the MR), and it works as expected:

    // Check for the `theme://` prefix in the translation server pattern and
    // replace it to the current module path.
    if (
      isset($data['info']['interface translation server pattern'])
      && str_starts_with($data['info']['interface translation server pattern'], 'theme://')
    ) {
      $themePath = \Drupal::service('theme_handler')->getTheme($name)->getPath();
      $data['info']['interface translation server pattern'] =
        str_replace(
          'theme://',
          $themePath . '/',
          $data['info']['interface translation server pattern']
        );
    }

Thanks!

murz’s picture

@johnatas, good point about themes! So, maybe let's use a more general term extension:// instead of module/theme then? Because, as I see in the Drupal Core, both modules and themes are named with a general term "extension" in functions, that work with both types.

Another approach would be to make a %project_root token available for use in interface translation server pattern.

In the context of the Drupal installation with many modules, the %project_root usually means the Drupal installation root, not the module/theme root directory. And for that, we already have the default behavior, which searches for files from the Drupal root.

Also, token means that it can be used in any place of the string, but for that specific case, we usually need to generate the prefix of the path, not the middle part, so it's better to go with the protocol prefix approach, than with the token, I believe.

So, I think extension:// would be the best name for this. What do you think?

johnatas’s picture

@murz,
Yeah, that approach sounds good to me.
Now that I think about it, would this also make it possible to extend the functionality to profiles as well?
If needed, I can work on a patch with this approach in the next few days.

murz’s picture

Yes, profiles, as I suppose, are a subset of extensions too. @johnatas, and yeah, would be great if you can update the MR, cuz I want too, but too busy with other urgent tasks :(

murz’s picture

And seems it's better to switch to the service "extension.path.resolver" and use the method ExtensionPathResolver::getPath(string $type, string $name) but there we have to know the extension type again, it looks like the first idea with having "module://", "theme://" and "profile://" was good :)

Here are all supported extension types:

    $this->extensionLists['module'] = $module_extension_list;
    $this->extensionLists['profile'] = $profile_extension_list;
    $this->extensionLists['theme'] = $theme_extension_list;
    $this->extensionLists['theme_engine'] = $theme_engine_extension_list;

But that splitting is strange, cuz we can't install modules, themes, and profiles with the same name together to one Drupal installation - see #371375: Do not allow a module and theme to use the same name.

johnatas’s picture

I’ve updated the merge request with the "extension" approach.
It works for both modules and themes.

However, it’s much more complex for profiles and theme engines, since using ProjectInfo()->processInfoList() isn’t possible in those cases.

Having never created a custom profile or theme engine myself, I’m not sure if they would even be concerned by custom translation handling?
It would be a shame to invest a lot of effort unnecessarily ;)

murz’s picture

Looks good to me, thank you! So, the last step seems is to write tests.

murz’s picture

About theme engines and profiles, I don't see cases when they can need translations, and profiles seems to be replaced by recepies, so I believe we can go as you've done now.

johnatas’s picture

Hi @murz,

Sorry for the delay — I’ve just rebased the branch, which fixed the failing test jobs.
Is that what you were referring to? Or were you talking about writing new tests for the new code?

Thanks.

Version: 11.x-dev » main

Drupal core is now using the main branch as the primary development branch. New developments and disruptive changes should now be targeted to the main branch.

Read more in the announcement.