Workaround for a plugin setting before the plugin is initialized

I have a plugin that loads translations by using YAML files. However, I want to provide an option to specify which folder should contain those files.

The problem is - I need to load the translations before the plugin is set up, yet the folder in which those translations reside is determined by a plugin option that’s available after the plugin is initialized:

$folder = kirby()->option('oblik.variables.folder'); // NULL
$translations = loadTranslationsFrom($folder);

Kirby::plugin('oblik/variables', [
    'translations' => $translations,
    'options' => [
        'folder' => kirby()->root('languages')
    ]
]);

Essentially, I want the default folder to be the site/languages folder, but also allow it to be set to the content folder for use cases where content is managed in a separate Git submodule and variables should be stored there.

So by default, YAML files are stored in site/languages but with this config setting:

return [
    'oblik.variables.folder' => kirby()->root('content')
];

…they should be stored in content instead.

Is there a way to make this happen?

Your problem here is using kirby() in the config, right? But to circumvent that problem we now have the ready config option.

Not quite. I need this mechanism, yes, but from a plugin perspective. In other words, I need a ready callback in my plugin config, not in the user’s config.php. Something like this:

plugin/index.php

Kirby::plugin('oblik/variables', [
    'options' => [
        'folder' => kirby()->root('languages')
    ],
    'ready' => function () {
        $folder = kirby()->option('oblik.variables.folder');

        return [
            'translations' => loadTranslationsFrom($folder)
        ];
    }
]);

User’s config.php

return [
    'oblik.variables.folder' => kirby()->root('content')
];

Unfortunately, there seems to be no ready functionality for plugins.

Edit: Perhaps I can somehow register translations from within the plugin after the plugin is loaded via system.loadPlugins:after?

This seems to do the job:

Kirby::plugin('oblik/variables', [
    'options' => [
        'folder' => kirby()->root('languages')
    ],
    'hooks' => [
        'system.loadPlugins:after' => function () {
            kirby()->extendTranslations(Manager::loadTranslations());
        }
    ]
]);

And in loadTranslations(), I call:

$folder = kirby()->option('oblik.variables.folder');

Is this reliable, though? Can I safely use extendTranslations()? Is it an internal method @distantnative?

Does it really work? The method is marked as protected, so I thought you would not be able to call it.

Seems like it:

I’m using my kirby-tester utility, but it still shouldn’t work. I don’t extend the base App class, I simply create a new instance of it. I should still get an error?

Anyways, it seems I should avoid doing that since it’s protected. Are there any alternatives so I can solve my initial problem?

@distantnative what’s even stranger is that if I add private $foo = '42 in the AppPlugins trait, I can access that as well in my closure. How is this possible? Could it be a bug in PHP?

Edit: I removed kirby-tester to make sure it doesn’t affect anything and I still get this behavior. Even if I simply open the site in the browser and run no other shenanigans:

Edit 2: To be super duper sure, I cloned a fresh version of the plainkit and added this plugin:

Kirby::plugin('me/test', [
    'hooks' => [
        'system.loadPlugins:after' => function () {
            kirby()->extendTranslations([
              'en' => ['foo' => 'bar']
            ]);
        }
    ]
]);

It still works. In the default template, I have <?= t('foo') ?> and I get bar. So this is definitely not related to my setup.

Edit 3: Actually, this is not related to the translations logic at all. I can’t do this in a template:

var_dump(kirby()->roots);

Cannot access protected property Kirby\Cms\App::$roots

But I can do it in a plugin:

image

Wow, really creative coding @hdodov :flushed:

All I can add is some background info about the system.loadPlugins:hook: https://github.com/getkirby/ideas/issues/23

1 Like

I think it’s happening because hooks are called through

And in that case the instance still calls the method internally, I guess.

1 Like

So what would you recommend for my initial problem? I think I shouldn’t rely on access to private and protected properties. Is there another solution? How would you implement this functionality?

Maybe I’m completely missing something, why can’t you call the config option and set your default folder as default in the absence of a config setting?

'options' => [
        'folder' => option('oblik.variables.folder', kirby()->root('languages'))
    ],

Then user can set option in config:

    'ready' => function() {
        return [
            'oblik.variables.folder' => kirby()->root('content')
        ];
    }

The problem comes from the fact that I need the final value of the setting before the plugin is loaded. Or - to add translations after it’s loaded.

$finalFolderValue = kirby()->option('oblik.variables.folder');
$data = readTranslations($finalFolderValue);

Kirby::plugin('oblik/variables', [
    'translations' => $data,
    'options' => [
        'folder' => kirby()->root('languages')
    ]
]);

This is a bit paradoxical because I need the value of the folder option on line 1, but set the default value later in Kirby::plugin(). Also, that value should be configurable from config.php by the user, which happens long after Kirby::plugin().

The more logical solution appears to be not reading the option before it’s available, but to delay the functionality that depends on that option after it’s available. That’s what I did with the hook trick above:

Kirby::plugin('oblik/variables', [
    'options' => [
        'folder' => kirby()->root('languages')
    ],
    'hooks' => [
        'system.loadPlugins:after' => function () {
            $finalFolderValue = kirby()->option('oblik.variables.folder');
            $data = readTranslations($finalFolderValue);
            kirby()->extendTranslations($data);
        }
    ]
]);

The loadPlugins hook triggers after all plugins have loaded an therefore after the final value of oblik.variables.folder is available. Then, I use that value to add the corresponding translations.

This works perfectly, but depends on having access to protected properties and using internal Kirby methods. Since I think that’s a bad idea, I’m asking for alternatives.

Edit: What I need, basically, is a similar functionality to ready but for plugins:

Kirby::plugin('oblik/variables', [
    'options' => [
        'folder' => kirby()->root('languages')
    ],
    'ready' => function () {
        $folder = kirby()->option('oblik.variables.folder');
        $data = readTranslations($folder);

        return [
            'translations' => $data
        ];
    }
]);

…or in other words:

Kirby::plugin('oblik/variables', [
    ...
    some plugin config
    ...
    'ready' => function () {
        return [
            ...
            more plugin config
            ...
        ];
    }
]);

@distantnative @texnixe can I have some conclusion on this? Is there a decent way to do it, or should I open an issue in the ideas repo for what I posted above?

@hdodov have you found a solution for this?

I am trying to load translations from a plugin blueprint, but they are not yet registered when the translation registration happens.