Bug 39564 - Enable runtime translations for plugins (Koha::I18N)
Summary: Enable runtime translations for plugins (Koha::I18N)
Status: Needs Signoff
Alias: None
Product: Koha
Classification: Unclassified
Component: I18N/L10N (show other bugs)
Version: Main
Hardware: All All
: P5 - low enhancement
Assignee: Julian Maurice
QA Contact: Testopia
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2025-04-04 13:21 UTC by Julian Maurice
Modified: 2025-04-07 09:34 UTC (History)
8 users (show)

See Also:
GIT URL:
Change sponsored?: ---
Patch complexity: ---
Documentation contact:
Documentation submission:
Text to go in the release notes:
Version(s) released in:
Circulation function:


Attachments
Bug 39564: Enable runtime translations for plugins (Koha::I18N) (15.20 KB, patch)
2025-04-04 13:24 UTC, Julian Maurice
Details | Diff | Splinter Review
Bug 39564: Enable runtime translations for plugins (Koha::I18N) (15.20 KB, patch)
2025-04-04 13:26 UTC, Julian Maurice
Details | Diff | Splinter Review

Note You need to log in before you can comment on or make changes to this bug.
Description Julian Maurice 2025-04-04 13:21:38 UTC
This is an attempt to have translatable plugins (like bug 37472) but using Koha::I18N (runtime translations)

See also bug 37472 comment 11 and bug 37472 comment 8
Comment 1 Julian Maurice 2025-04-04 13:24:47 UTC
Created attachment 180642 [details] [review]
Bug 39564: Enable runtime translations for plugins (Koha::I18N)
Comment 2 Julian Maurice 2025-04-04 13:26:27 UTC
Created attachment 180643 [details] [review]
Bug 39564: Enable runtime translations for plugins (Koha::I18N)

This solution uses gettext's textdomain feature to allow plugins to use
Koha::I18N for their own translations.

Basically we have one textdomain per plugin and we bind the directory
where a plugin store its translations (.mo files) to its textdomain.

Plugins have to specify the textdomain like this:

    __d('MyPlugin', 'string to translate')

or, if within a template:

    [% I18N.td('MyPlugin', 'string to translate') %]

This patches adds several functions to Koha::I18N:
__d, __dn, __dp, __dnp, __dx, __dnx, __dpx, and __dnpx

and their template equivalents in Koha::Template::Plugin::I18N

It also moves some code for string extraction from gulpfile.js to
misc/translator/xgettext-perl and misc/translator/xgettext-js for easier
use by plugins authors.

(Note that, even if there is a misc/translator/xgettext-js script, this
patch does not add support for JS translations in plugins)

By default a plugin named Koha::Plugin::Foo will have a textdomain
'Koha-Plugin-Foo'. It can be changed by overriding method
locale_textdomain.

By default the directory bound to the textdomain will be
Koha/Plugin/Foo/locale. It can be changed by overriding method
locale_dir.
This directory should contain one subdirectory for each language, and
each language subdirectory should contain an LC_MESSAGES subdirectory
which should contain a <textdomain>.mo file.
It should look like this:

Koha/
    Plugin/
        Foo/
            locale/
                fr_FR/
                    LC_MESSAGES/
                        Koha-Plugin-Foo.mo
                de_DE/
                    LC_MESSAGES/
                        Koha-Plugin-Foo.mo
                ...
Comment 3 Julian Maurice 2025-04-04 13:33:29 UTC
Example plugin here : https://gitlab.com/jajm/koha-plugin-i18n-example

Test plan:
1. Apply the patch
2. Install the example plugin and restart koha
3. Enable fr-FR language for the staff interface.
4. Go to Admin > Plugins and run the example plugin's tool.
5. Switch between en and fr-FR to see the changes.
Comment 4 Jonathan Druart 2025-04-07 08:21:59 UTC
Did you investigate a way to prevent the addition of the new methods?

It would be nice to use the existing ones and deal with the domain in Koha::Plugins or Koha::I18N.

We could guess the caller and set the domain depending on it.

Not totally sure how we would make that work, but I’m curious, did you already ask yourself that question? :)
Comment 5 Julian Maurice 2025-04-07 08:27:24 UTC
(In reply to Jonathan Druart from comment #4)
> Did you investigate a way to prevent the addition of the new methods?

Actually I started without the new methods but I figured it would be nice to make these two things customizable.
So yes, it's totally doable.
Comment 6 Julian Maurice 2025-04-07 08:30:21 UTC
(In reply to Julian Maurice from comment #5)
> (In reply to Jonathan Druart from comment #4)
> > Did you investigate a way to prevent the addition of the new methods?
> 
> Actually I started without the new methods but I figured it would be nice to
> make these two things customizable.
> So yes, it's totally doable.

I may have read your comment too quickly. Are you talking about the __d* functions ?
Comment 7 Julian Maurice 2025-04-07 09:34:50 UTC
(In reply to Julian Maurice from comment #6)
> (In reply to Julian Maurice from comment #5)
> > (In reply to Jonathan Druart from comment #4)
> > > Did you investigate a way to prevent the addition of the new methods?
> > 
> > [...]
> 
> I may have read your comment too quickly. Are you talking about the __d*
> functions ?
For templates, the new methods can be avoided by writing something like this:

  [% USE i18n = I18N('Example') %]
  [% i18n.t('string to translate') %]

All that is needed is a `new` method in Koha::Template::Plugin::I18N that stores its textdomain parameter (within $self) so it can be used in t* methods.

On the Perl side, it might not be that simple, as it would probably require keeping a "wrapper object" around (the wrapper object would change the default textdomain, call the appropriate function __*, then revert the default textdomain to its original value).

> We could guess the caller and set the domain depending on it.
I really don't like subroutines that change their behaviour depending on the caller. It makes them more difficult to debug and test.