Problem/Motivation

In some situations the translations directory (where downloaded translation files get stored; e.g. sites/default/files/translations) is missing. This may happen for example in a development process, where a site is re-created on a dev machine from git and a live DB but without populating the files directory (as you use StageFileProxy).

Simplified steps to reproduce in a normally working site (translations directory = sites/default/files/translations)

$ rm -r sites/default/files/translations
$ drush locale-update

or on browser at admin/config/regional/translate/import and trying to import the .po file.

Example error messages:

 [notice] File temporary://filejmiMlj could not be moved/copied because the destination directory translations:// is not configured correctly.
 [error]  Unable to download translation file https://ftp.drupal.org/files/translations/8.x/admin_toolbar/admin_toolbar-8.x-1.24.de.po. 
 [warning] fopen(translations://admin_toolbar-8.x-1.24.de.po): failed to open stream: "Drupal\locale\StreamWrapper\TranslationsStream::stream_open" call failed PoStreamReader.php:154
 [warning] fgets() expects parameter 1 to be resource, boolean given PoStreamReader.php:248
 [notice] Unable to import translations file: translations://admin_toolbar-8.x-1.24.de.po

Screenshot of error message on translation import form:
Error message

Processes that will profit from a fix:

  • Update translation via user interface
  • Upload a translation via the Translation import page
  • Adding a language

Proposed resolution

  • Auto-create the directory if at the start of the batch check/update process.
  • Auto-create the directory when importing a translation via the Translation import page.

Remaining tasks

Find other other processes where the translation directory is yet not auto-created.

User interface changes

none

API changes

none

Data model changes

none

Issue fork drupal-3013435

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

axel.rutz created an issue. See original summary.

Version: 8.7.x-dev » 8.8.x-dev

Drupal 8.7.0-alpha1 will be released the week of March 11, 2019, which means new developments and disruptive changes should now be targeted against the 8.8.x-dev branch. For more information see the Drupal 8 minor version schedule and the Allowed changes during the Drupal 8 release cycle.

Version: 8.8.x-dev » 8.9.x-dev

Drupal 8.8.0-alpha1 will be released the week of October 14th, 2019, which means new developments and disruptive changes should now be targeted against the 8.9.x-dev branch. (Any changes to 8.9.x will also be committed to 9.0.x in preparation for Drupal 9’s release, but some changes like significant feature additions will be deferred to 9.1.x.). For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

sophie.sk’s picture

I've seen this lately. We had the module enabled all the way up to prod, then new developers (me!) joined the company. The module is enabled in config, and we were provided with a database dump (to get content and users), so the module was enabled in the database dump.

The locale module therefore never got installed on my local machine and the folder never got created. I've had to create a new update hook to ensure it got created on all environments, including local and remote, as we were moving hosts as well.

Perhaps there could be a requirements check for this module that makes sure the translations directory exists and is writeable?

sutharsan’s picture

@Sophie.SK, Can you please clarify "The locale module therefore never got installed on my local machine" How can you explain this? If you combine code and a database, any module enabled in the database should be enabled. Did you run config import after combining code and DB on your local environment?

The locale module does check for and create the directory at various points. For example when enabling the module and when writing po files.

I'm not sure if the module can be and should be made responsible for re-creating the environment. If your system relies on a translation directory within the public files directory, you can enforce this by placing a gitkeep file inside the translations directory and exclude that from gitignore. That way an empty directory is made available via the VCS.

sophie.sk’s picture

@Sutharsan

Re-reading the comment, I think I mean "I didn't enable the module locally, therefore the install hooks never ran".

The module was enabled, but I didn't enable it. The directory creation happens on install.

This was further exacerbated when we switched hosts recently. We had the database and the config, so the module was enabled, but it wasn't being installed and the translations directory didn't get created when we deployed. I had to write an additional update hook to create the directory on each environment.

If your system relies on a translation directory within the public files directory, you can enforce this by placing a gitkeep file inside the translations directory and exclude that from gitignore.

That could work. Should it be documented that a directory will be created on install but if it's required otherwise then a gitkeep would be needed?

geek-merlin’s picture

Issue summary: View changes

I see a way how to reliably reproduce this, see IS.

sutharsan’s picture

Issue summary: View changes
sutharsan’s picture

Title: Translations directory not autocreated, leading to obscure errors » Translations directory not autocreated

Restructured the issue summary with same content in an attempt to make the case easier to understand.

I can think of the following cases that should be considered:
- Update translation via user interface
- Upload a translation via the Translation Import page
- Adding a language
- Does configuration import trigger interface translation update?
- Update translation via drush
- Update translation via drupal console

Drush and Drupal Console are not in scope of this issue, but are important to consider when implementing a solution.

Some technical notes: Check translation status and Update translations are two independent processes. During 'check' the translation directory may be used for reading (only if the translation source is local or local+remote). During 'update' the translation directory is used for reading and writing (writing only in case of remote translation source).

geek-merlin’s picture

Hmm, we could extend \Drupal\Core\StreamWrapper\LocalStream with an "autocreate" option and set that for TranslationStream in locale.services.yml. This could benefit all others too and we're done. Thoughts?

sutharsan’s picture

Issue tags: +Needs tests
StatusFileSize
new1.99 KB

Interesting idea, but it feels like cutting corners. When I look at https://www.php.net/manual/en/class.streamwrapper.php I think the streamwrapper must knows about the 'how' and not about the 'when'. It has an mkdir method, but looking at core implementations, it does not use it itself.

I have made a first patch that adds a batch task that creates the directory right before the download/import tasks. It also handles creating the directory when importing a translation via the interface.

sophie.sk’s picture

I'm not sure about this approach, as it seems to rely on uploading a translation manually through the UI. That means we wouldn't get the benefits when doing things elsewhere (eg enabling a new module, which fetches new translations).

I think this is useful for one particular use case... maybe it's worth adding a requirements check (shown on the Status Report) for the directory existing?

When performing another action that tries to create translations (such as enabling a module), it'd be useful to check whether the directory exists before trying to create them, to avoid the error message.

sutharsan’s picture

@Sophie.SK, Mabe I was not clear about what the patch does. Right before the batch task that imports translations the translation directory is created (when needed). Additionally the translation directory is created before manual translation import.

sutharsan’s picture

(updated the #11 text accordingly)

sutharsan’s picture

Component: language system » locale.module
Status: Active » Needs review
Issue tags: -Needs tests
StatusFileSize
new2.81 KB
new4.66 KB

This patch add a test for the new function that creates the directory. It updates an existing test for the added cron task.

Status: Needs review » Needs work

The last submitted patch, 15: drupal-locale-prepare-directory-3013435-15.patch, failed testing. View results

sutharsan’s picture

Issue summary: View changes
Status: Needs work » Needs review
geek-merlin’s picture

@Sutharsan: I rolled the other approach and to keep this focused posted it on #3105515: Local stream wrappers break when directory is missing. Thoughts?

Version: 8.9.x-dev » 9.1.x-dev

Drupal 8.9.0-beta1 was released on March 20, 2020. 8.9.x is the final, long-term support (LTS) minor release of Drupal 8, which means new developments and disruptive changes should now be targeted against the 9.1.x-dev branch. For more information see the Drupal 8 and 9 minor version schedule and the Allowed changes during the Drupal 8 and 9 release cycles.

Version: 9.1.x-dev » 9.2.x-dev

Drupal 9.1.0-alpha1 will be released the week of October 19, 2020, which means new developments and disruptive changes should now be targeted for the 9.2.x-dev branch. For more information see the Drupal 9 minor version schedule and the Allowed changes during the Drupal 9 release cycle.

dieterholvoet’s picture

I re-rolled the patch against 9.1.5.

Status: Needs review » Needs work

The last submitted patch, 21: drupal-locale-prepare-directory-3013435-21.patch, failed testing. View results

benjifisher’s picture

Please do not ask the testbot to try again until #3207086: [HEAD BROKEN] Consistent failure in MonthDatePluginTest is fixed.

alexpott’s picture

Status: Needs work » Reviewed & tested by the community
alexpott’s picture

Status: Reviewed & tested by the community » Needs work
  1. +++ b/core/modules/locale/locale.module
    @@ -764,6 +764,15 @@ function locale_system_file_system_settings_submit(&$form, FormStateInterface $f
    +function locale_prepare_translations_directory() {
    

    I wish locale code was a bit more like the rest of core. Adding more functions to the global namespace is a shame.

  2. +++ b/core/modules/locale/locale.module
    @@ -764,6 +764,15 @@ function locale_system_file_system_settings_submit(&$form, FormStateInterface $f
    +  $directory = \Drupal::config('locale.settings')->get('translation.path');
    ...
    +  \Drupal::service('file_system')->prepareDirectory($directory, $options);
    

    Code in \Drupal\locale\Form\LocaleSettingsForm::buildForm

        if ($directory = $config->get('translation.path')) {
          $description = $this->t('Translation files are stored locally in the  %path directory. You can change this directory on the <a href=":url">File system</a> configuration page.', ['%path' => $directory, ':url' => Url::fromRoute('system.file_system_settings')->toString()]);
        }
        else {
          $description = $this->t('Translation files will not be stored locally. Change the Interface translation directory on the <a href=":url">File system configuration</a> page.', [':url' => Url::fromRoute('system.file_system_settings')->toString()]);
        }
    

    implies that is acceptable for this setting to be empty.

    Also I'm not sure that the extra complexity around the permissions is really necessary. We don't do this in system_check_directory() so if you go to the file system form and press save the permissions will change.

  3. +++ b/core/modules/locale/tests/src/Kernel/LocalePrepareDirectoryTest.php
    @@ -0,0 +1,36 @@
    +    $directory = Settings::get('file_public_path') . '/' . strtolower($this->randomMachineName(8));
    

    There's no need for the name to be random - a consistent name would be better here - something like 'test_translations'...

  4. One thing that odd is that if you go to the file system form and press save you'll end up with an htaccess file that'll deny access to all files in the folder via your web server - which feels correct. I think if we are going to introduce locale_prepare_translations_directory() then we should call it from locale.install and also call FileSecurity::writeHtaccess($directory); in it - and then things are more consistent.
  5. The test is testing locale_prepare_translations_directory() - it's not testing the import form or the batch - ie. the bug that has caused the need to make this change.

Version: 9.2.x-dev » 9.3.x-dev

Drupal 9.2.0-alpha1 will be released the week of May 3, 2021, which means new developments and disruptive changes should now be targeted for the 9.3.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 9.3.x-dev » 9.4.x-dev

Drupal 9.3.0-rc1 was released on November 26, 2021, which means new developments and disruptive changes should now be targeted for the 9.4.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

sjerdo’s picture

StatusFileSize
new4.65 KB

Re-roll patch #21

Still needs work, see #25

Version: 9.4.x-dev » 9.5.x-dev

Drupal 9.4.0-alpha1 was released on May 6, 2022, which means new developments and disruptive changes should now be targeted for the 9.5.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

ravi.shankar’s picture

StatusFileSize
new4.67 KB
new991 bytes

Fixed failed tests of patch #28.

nico972’s picture

If you go to the admin/config/media/file-system page, the "translations" directory will be created and will contain a .htaccess but I don't know what code does this.

Version: 9.5.x-dev » 10.1.x-dev

Drupal 9.5.0-beta2 and Drupal 10.0.0-beta2 were released on September 29, 2022, which means new developments and disruptive changes should now be targeted for the 10.1.x-dev branch. For more information see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

Version: 10.1.x-dev » 11.x-dev

Drupal core is moving towards using a “main” branch. As an interim step, a new 11.x branch has been opened, as Drupal.org infrastructure cannot currently fully support a branch named main. New developments and disruptive changes should now be targeted for the 11.x branch, which currently accepts only minor-version allowed changes. For more information, see the Drupal core minor version schedule and the Allowed changes during the Drupal core release cycle.

luksak’s picture

Just ran into this issue once again. It would be great to fix this, since it can be pretty annoying such as breaking CI pipelines when installing modules.

Could we move this issue forward? I guess the comments in #25 are still valid?

adam-delaney’s picture

StatusFileSize
new4.25 KB

Re rolling patch 30 for latest Drupal 10.2.x

adam-delaney’s picture

StatusFileSize
new3.19 KB

I made a mistake re-rolling 35, so I've re-rolled a new patch to resolve the issue which should apply cleanly against latest 10.2.x

majorrobot made their first commit to this issue’s fork.

sokru made their first commit to this issue’s fork.

sokru’s picture

Status: Needs work » Needs review

The MR !8320 should address the feedback from #25.3 and #25.5, but I wonder what action (if any) should be taken for other comments on #25, so marking this "Needs review".

I feel the priority should be raised, because this issue is typical for causing frustration, since the the UI doesn't give a proper indication of what is wrong with settings.

ivnish’s picture

Status: Needs review » Reviewed & tested by the community

MR works fine. Tested drush locale-update and translations update via UI

gábor hojtsy’s picture

Status: Reviewed & tested by the community » Needs review

I have mixed feelings about this issue.

I think the missing translation directory in your dev situation for example is a lack of syncing files in your development workflow. Other critical files may also be missing. Eg. the JS translation files may get regenerated locally and that may end up leading to a mismatch in your live site when the updates get pushed (depending on what you push).

At the same time I understand the developer experience improvement that instead of an error, you get an empty directory at least. I am not sure though if that does not lead to unwanted side effects.

How are other files missing handled by Drupal? Eg. if your site logo is missing or other key files that you don't have on the dev environment?

One MR specific comment: How is the locale/src/Plugin/QueueWorker/LocaleTranslation.php change related?

smustgrave’s picture

Should this be in PNMI ?

smustgrave’s picture

Status: Needs review » Needs work

To address some of the questions in #42

sokru’s picture

Issue summary: View changes
Status: Needs work » Needs review
StatusFileSize
new338.73 KB

How are other files missing handled by Drupal? Eg. if your site logo is missing or other key files that you don't have on the dev environment?

I don't think its recommended to include files from sites/default/files to version control. If the theme build assets are not included in version control and the build process fails, at least there's visual cue that the files are missing. When translation directory missing, the error message on UI/watchdog doesn't give guidance how to resolve the issue. I added the screenshot of the error message on UI to issue summary.

For code specific discussion I think the comments on merge request would be preferred, but locale/src/Plugin/QueueWorker/LocaleTranslation.php change is needed because LocaleTranslation::processItem() provides zero $args. The tests where failing without this change.

gábor hojtsy’s picture

Indeed that error message is not helpful. Could the error be more specific and tell the user the directory does not exist? What can the user then do to get the directory created? (Save the file settings config form?) At least if we let the user know this, they have a chance to sync their translation files if they wish so without silently getting them into a different situation.

smustgrave’s picture

Status: Needs review » Needs work

Thanks so much for taking a look @gábor hojtsy moving to NW to address the feedback.

sokru’s picture

Status: Needs work » Needs review

re: #46
Asking users to go to separate form to resolve this issue does not help the command-line use cases (CI pipelines, syncing database from different environment, etc.).

IMHO automatically creating the missing translation directory gives the best DX/UX. We could add some message to inform about autocreation.

gordonio’s picture

I have run into this a few times with CI pipelines and it is a bit of an annoyance. I agree with sokru that we should handle it automatically if it doesn't exist. I can't see a downside to having it there, even if it is empty.

needs-review-queue-bot’s picture

Status: Needs review » Needs work
StatusFileSize
new3.39 KB

The Needs Review Queue Bot tested this issue. It fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".

This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.

Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.

tobiasb made their first commit to this issue’s fork.

tobiasb’s picture

Status: Needs work » Needs review

MR updated + missing typehints.

smustgrave’s picture

Status: Needs review » Needs work

We could add some message to inform about autocreation.

Think would be a good compromise

nicrodgers made their first commit to this issue’s fork.

nicrodgers’s picture

Whoops - tried to update the MR to work with 11.2 but it seems to have gone wrong somewhere.. Sorry!

penyaskito’s picture

Development happens on 11.x, not 11.2.x.

penyaskito’s picture

Fixed MR by rebasing back with 11.x. Didn't really review, just git magic.

jelle_s’s picture

StatusFileSize
new5.98 KB

Patch file of the MR for those that want to use it in composer.patches.json. Applies on 11.2.1.

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.