• Resolved r4nd99y

    (@r4nd99y)


    For this experiment I took the Twenty Twenty-Five theme and added to its’ functions.php:

    add_action('init', function() {
    load_theme_textdomain('twentytwentyfive', __DIR__ . '/');
    });

    add_shortcode('test', function() {
    $content = '<ul>';
    $content .= sprintf('<li>String 1: %s</li>', esc_html__('String 1', 'twentytwentyfive'));
    $content .= sprintf('<li>String 2: %s</li>', esc_html__('String 2', 'twentytwentyfive'));
    $content .= '</ul>';
    return $content;
    });

    Then I used Loco to create the .pot as well as a Language that translates String 1. I chose the “Author” location themes/twentytwentyfive/{locale}.po so as to simulate translations bundled with a non-wp.org theme.

    Loco created: twentytwentyfive.pot, {locale}.l10n.php, {locale}.mo and {locale}.po, the last 3 I renamed to twentytwentyfive-{locale}.l10n.php, twentytwentyfive-{locale}.mo and twentytwentyfive-{locale}.po because it didn’t work without it.

    Now on a page where I use the test shortcode the String 1 is translated and String 2 is not, as expected.

    Then as a “user of the theme” I use Loco to add a translation to the String 2 – I go to Loco -> Themes -> Twenty Twenty-Five -> New language and create it at the “Other” location languages/loco/themes/twentytwentyfive-{locale}.po, I translate String 2 and save.

    Now the String 2 is translated, but String 1 isn’t – the translations didn’t waterfall.

    Seems like when a custom Loco translation is added, it loads that one and disregards the original “author” one.

    A manual fix:

    add_filter('load_translation_file', function($file, $domain, $locale) {

    static $fixed = false;
    if($fixed) {
    return $file;
    }

    if($domain === 'twentytwentyfive' && $locale === '{my_locale}') {
    $file = wp_normalize_path($file);
    if(strpos($file, 'languages/loco/themes') !== false) {
    $fixed = true;
    add_action('init', function() use ($file) {
    $author_file = str_replace('languages/loco/themes', 'themes/twentytwentyfive', $file);
    $author_file = str_replace('.l10n.php', '.mo', $file);
    load_textdomain('twentytwentyfive', $author_file);
    }, 15);
    return str_replace('languages/loco/themes', 'themes/twentytwentyfive', $file);
    }
    }

    return $file;
    }, 15, 3);

    So when Loco goes to load languages/loco/themes/twentytwentyfive-{locale}.l10n.php I intercept and change it to the author path instead, and also need to do load_textdomain again with the .mo file, for some reason. Then the author file gets loaded correctly and Loco ends up loading the custom file again at some point, resulting in both strings translated.

    Can you reproduce it? Am I doing something wrong?

Viewing 6 replies - 1 through 6 (of 6 total)
  • Plugin Author Tim W

    (@timwhitlock)

    Thanks for this. I am able to reproduce the problem of author files not being merged with custom files for themes. I’m not familiar with your term “waterfall”.

    Firstly though, the naming {locale}.mo etc.. (without text domain) is correct for the author location. This may be a related factor to the merging problem, but must remain in place because WordPress expects this for any translation file path under a theme directory. Renaming files is not a good way to debug this issue.

    I’ll have a look at fixing the root cause here. The fact that merging (author + custom) works fine for plugins makes me optimistic. It seems likely the issue is as related to the file naming exception for theme files.

    As a side note. WordPress have made more than one change over the years to deprecate author-provided translations. They’re fundamentally against the concept, but I get that commercial themes don’t have much choice. You can however simply copy the author file to the system location, and merging will work.

    Thread Starter r4nd99y

    (@r4nd99y)

    I’m not familiar with your term “waterfall”.

    On second thought, me neither, not sure why I thought that was the term for it.

    Firstly though, the naming {locale}.mo etc.. (without text domain) is correct for the author location.

    Interesting, so what is the reason that it’s not working when I don’t have Loco active and I just do (wp-content/themes/twentytwentyfive/functions.php):

    add_action('init', function() {
    load_theme_textdomain('twentytwentyfive', __DIR__ . '/');
    });

    while having the files:

    • wp-content/themes/twentytwentyfive/functions.php
    • wp-content/themes/twentytwentyfive/twentytwentyfive.pot
    • wp-content/themes/twentytwentyfive/{locale}.mo
    • wp-content/themes/twentytwentyfive/{locale}.po
    • wp-content/themes/twentytwentyfive/{locale}.l10n.php

    The style.css has Text Domain: twentytwentyfive.
    Does the fact that it’s a wp.org theme that I’m testing with have any impact? I temporarily removed wp-content/languages in case it had any downloaded tt5 translations, but no difference.
    Would shipping both naming formats just in case be a bad idea?

    The fact that merging (author + custom) works fine for plugins makes me optimistic.

    You have confirmed that? I thought I had the same issue with WC – when I used Loco to override 1 string then the other translations from wp.org stopped working. Haven’t tested it in isolation though, so it could have been something else.

    Plugin Author Tim W

    (@timwhitlock)

    what is the reason that it’s not working when I don’t have Loco active[….]

    I can’t say, but I will point out that load_theme_textdomain won’t work if the translations have already been loaded automatically without your custom path. This is for you to debug.

    Does the fact that it’s a wp.org theme that I’m testing with have any impact?

    I don’t know why it would. You can see the behaviour here in the WordPress core. There’s even a comment stating explicitly that author-provided theme translations follow a different naming.

    Would shipping both naming formats just in case be a bad idea?

    Yes.

    Fix the actual problem, rather than implementing workarounds with the potential to create more confusion.

    I’m going to look at author+custom merging for themes today. I’m pretty sure it works for plugins, but will double check in my tests as I do this.

    Plugin Author Tim W

    (@timwhitlock)

    I’ve made a discovery, but I don’t know if it’s fixable at my end.

    Short version: Use the after_setup_theme hook instead of init.

    As for the general problem of author files not merging with custom ones. It does actually work (for both themes and plugins), but not if the text domain is loaded before you can set your path.

    Here’s what happens when you call load_theme_textdomain on the init hook when you have custom and author translations, but no system translations installed.

    You’re in race with the theme setup which also hooks onto init to register block patterns. This results in WordPress automatically loading translations BEFORE you’ve been able set your author path. The result of this is that Loco Translate looks for its own custom files and these get stuck in the WordPress registry before it can be told about your author files. Unloading the text domain won’t reset this value and I can’t see a way to clear it.

    Thread Starter r4nd99y

    (@r4nd99y)

    after_setup_theme did the trick, thanks!

    Also found out that the reason the {locale}.mo name format didn’t work was because I used __DIR__ and not get_template_directory() and because I’m on Windows the directory separators were different and the str_starts_with in l10n.php didn’t match.

    Did a test with WC and it seems to be working as well, I guess when there’s an issue it’s probably going to be something using a string too early somewhere and nothing to do with Loco.

    Thank you very much for your help, I’ll leave you with this unrelated error that you maybe want to look at:

    Warning: Undefined array key "" in C:\...\wp-content\plugins\loco-translate\src\fs\FileFinder.php on line 413

    Fatal error: Uncaught Error: Call to a member function add() on null in C:\...\wp-content\plugins\loco-translate\src\fs\FileFinder.php:413
    Stack trace:
    #0 C:\...\wp-content\plugins\loco-translate\src\fs\FileFinder.php(383): Loco_fs_FileFinder->add(Object(Loco_fs_File))
    #1 C:\...\wp-content\plugins\loco-translate\src\fs\FileFinder.php(458): Loco_fs_FileFinder->read()
    #2 C:\...\wp-content\plugins\loco-translate\src\fs\FileFinder.php(179): Loco_fs_FileFinder->next()
    #3 C:\...\wp-content\plugins\loco-translate\src\fs\FileFinder.php(190): Loco_fs_FileFinder->export()
    #4 C:\...\wp-content\plugins\loco-translate\src\package\Project.php(714): Loco_fs_FileFinder->exportGroups()
    #5 C:\...\wp-content\plugins\loco-translate\src\package\Project.php(843): Loco_package_Project->findLocaleFiles('po')
    #6 C:\...\wp-content\plugins\loco-translate\src\package\Bundle.php(711): Loco_package_Project->getLastUpdated()
    #7 C:\...\wp-content\plugins\loco-translate\src\admin\list\BaseController.php(26): Loco_package_Bundle->getLastUpdated()
    #8 C:\...\wp-content\plugins\loco-translate\src\admin\list\BaseController.php(36): Loco_admin_list_BaseController->bundleParam(Object(Loco_package_Plugin))
    #9 C:\...\wp-content\plugins\loco-translate\src\admin\list\PluginsController.php(17): Loco_admin_list_BaseController->addBundle(Object(Loco_package_Plugin))
    #10 C:\...\wp-content\plugins\loco-translate\src\mvc\AdminRouter.php(246): Loco_admin_list_PluginsController->render()
    #11 C:\...\wp-includes\class-wp-hook.php(324): Loco_mvc_AdminRouter->renderPage('')
    #12 C:\...\wp-includes\class-wp-hook.php(348): WP_Hook->apply_filters('', Array)
    #13 C:\...\wp-includes\plugin.php(517): WP_Hook->do_action(Array) #14 C:\...\wp-admin\admin.php(259): do_action('loco-translate_...')
    #15 {main} thrown in C:\...\wp-content\plugins\loco-translate\src\fs\FileFinder.php on line 413

    This happens when I go to Loco Translate -> Plugins while I have a symlinked plugin. When I uncomment this then it works:

    /*if( '' === $ext || ! array_key_exists($ext,$this->exts) ){
    throw new LogicException('Should have filtered out '.$file->basename().' when grouping by *.{'.implode(',',array_keys($this->exts)).'}' );
    }*/

    I use this command as admin in cmd to symlink some plugins from a central location to various WP installations:

    mklink /j "C:\...\wp-content\plugins\my-plugin" "C:\...\my-plugin"

    The plugin folders themselves also typically contain symlinks by pnpm and composer, which may also contribute to it.

    Plugin Author Tim W

    (@timwhitlock)

    Thanks for reporting your symlink issue. I do test the plugin with symlinks, although I don’t test on Windows.

    The only scenario I can reproduce for your error is if a symlinked file had a valid extension being filtered on (like .po), but the original (target file) had a different file extension (or no file extension, as in your case).

    I’ve added some code to avoid that error and debug it, but I can’t see how your example of a symlinked directory would create the same situation. (The original and linked paths would all have the same file names). Is it possible you mistakenly linked a directory with a file extension.

    Feel free to post more detail on how this happens.

Viewing 6 replies - 1 through 6 (of 6 total)
  • You must be logged in to reply to this topic.