No ways to configure module-relative path for included module translation .po files

Created on 24 October 2024, 10 months ago

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 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

πŸ› Bug report
Status

Active

Version

11.0 πŸ”₯

Component

locale.module

Created by

πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @murz
  • πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

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

  • Pipeline finished with Canceled
    10 months ago
    Total: 378s
    #319141
  • Pipeline finished with Success
    10 months ago
    Total: 560s
    #319158
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

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

    thanks!

  • πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

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

  • Pipeline finished with Failed
    10 months ago
    Total: 572s
    #337364
  • Pipeline finished with Failed
    10 months ago
    Total: 517s
    #337383
  • πŸ‡ΊπŸ‡ΈUnited States smustgrave

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

  • Status changed to Needs work 3 months ago
  • πŸ‡¨πŸ‡¦Canada Liam Morland Ontario, CA πŸ‡¨πŸ‡¦

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

  • πŸ‡¨πŸ‡¦Canada Liam Morland Ontario, CA πŸ‡¨πŸ‡¦

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

  • Pipeline finished with Success
    3 months ago
    #523616
  • Pipeline finished with Success
    3 months ago
    #523636
  • πŸ‡¨πŸ‡¦Canada Liam Morland Ontario, CA πŸ‡¨πŸ‡¦
  • πŸ‡«πŸ‡·France johnatas

    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!

  • πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

    @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.

    So, I think extension:// would be the best name for this.

  • πŸ‡«πŸ‡·France johnatas

    @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.

  • πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

    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 :(

  • πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

    And seems it's better to switch to the service "extension.path.resolver" and use the method ExtensionPathResolver::getPathname(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 :)

  • Pipeline finished with Canceled
    about 2 months ago
    #549077
  • πŸ‡«πŸ‡·France johnatas

    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 ;)

  • Pipeline finished with Failed
    about 2 months ago
    #549084
  • πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

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

  • πŸ‡¦πŸ‡²Armenia murz Yerevan, Armenia

    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.

  • Pipeline finished with Success
    15 days ago
    Total: 658s
    #575495
  • πŸ‡«πŸ‡·France johnatas

    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.

Production build 0.71.5 2024