Internationalizing GIMP Scheme Plugins
About
Read this when you are a plugin author who wants to distribute a GIMP Scheme plugin to many countries.
Internationalizing makes a plugin’s GUI appear in the native language of the user.
Internationalizing is also known as localizing, or i18n.
Internationalizing refers to a process or system. Internally GIMP uses the i18n system called “gettext.”
Quickstart
A third-party can only internationalize “new style” Scheme plugins installed to /plugins.
The process is:
- Mark user-facing GUI strings in the source like: ’ _“Foo” '
- Call e.g. ‘(script-fu-register-i18n “plug-in-my” “Standard”) at the bottom of your script.
- Coordinate with translators to create one or more translation data files (.po “portable”)
- Build and install translation data (.mo “machine”) in a “locale” subdirectory of your plugin’s main directory.
More details and examples follow.
Example for Use Case: Single Plugin
At the bottom of your script:
(script-fu-register-i18n "plugin-in-my" "Standard")
where “plug-in-my” is the name of your plugin’s PDB procedure and run function (in Scheme.)
Install .mo files alongside your script, in a “locale” subdirectory.
.../plug-ins/my/my.scm
/locale/es/LC_MESSAGES/my.mo
/de/LC_MESSAGES/my.mo
To do that install, use this meson command in the meson.build file inside the source directory holding your translation data (es.po and de.po files, and the LINGUAS file):
i18n.gettext(
"my",
preset: 'glib',
install_dir:
get_option('prefix') / get_option('libdir') /
'plug-ins' / 'my' / 'locale')
The “install_dir” argument builds a path to the plugin’s installed directory, starting with the platform’s path to GIMP’s installed “plug-ins” directory. (GIMP’s meson.build files have that path in the “gimpplugindir” variable.)
In this use case, because you passed the keyword “Standard”, the name of the i18n domain is standard i.e. canonical: the same as the name of the plugin’s file name, “my”. Everywhere in the command, substitute your plugin’s filename for “my”.
Note the name of the PDB procedure is “plug-in-my” but it is defined in a file named “my.scm”. You could instead use “plug-in-my.scm” as the name of the file. In other words, you can use the same name for the i18n domain, the PDB procedure, and the source file.
Example for Use Case: Suite of Plugins
In this use case, a suite of plugins shares .po/.mo files. You do this when the suite of plugins use a same set of phrases, to simplify the work of translators.
Suppose you name the suite of plugins “mySuite”. The file named “mySuite.scm” defines two PDB procedures in the suite.
At the bottom of mySuite.scm, declare intent to translate:
(script-fu-register-i18n "plugin-in-my1" "mySuite")
(script-fu-register-i18n "plugin-in-my2" "mySuite")
where “plug-in-my1” and “plug-in-my2” are the names of the suite’s PDB procedures and run functions (in Scheme.)
Install .mo files alongside your script, in a “locale” subdirectory.
.../plug-ins/mySuite/mySuite.scm
/locale/es/LC_MESSAGES/mySuite.mo
/de/LC_MESSAGES/mySuite.mo
To do that install, use this meson command in the meson.build file inside the source directory:
i18n.gettext(
"mySuite",
preset: 'glib',
install_dir:
get_option('prefix') / get_option('libdir') /
'plug-ins' / 'mySuite' / 'locale')
The “install_dir” argument builds a path to the plugin suite’s installed directory, starting with the platform’s path to GIMP’s installed “plug-ins” directory. (In the meson build system, this construct is platform independent.)
In this use case, your source directory is like:
mySuite/ mySuite.scm (defines two PDB procedures)
/ meson.build
/ es.po (from translators)
/ de.po
/ LINGUAS (a text file that lists "es" and "de")
Example for Use Case: Suite of Plugins with Separate Plugin Files
This use case expands on the previous use case.
In this use case, there is a suite of PDB procedures, but one is defined in a separate source file. The file named “mySuite.scm” defines two PDB procedures in the suite. The file named “mySuitMore.scm” defines another PDB procedure in the suite. Again, all the PDB procedures use same phrases.
At the bottom of mySuiteMore.scm, declare intent to translate:
(script-fu-register-i18n "plugin-in-my3" "mySuiteMore")
Note that the domain name is different, “mySuiteMore”, but the translations will be shared, and the same.
In this use case, the translation data is duplicated and installed to two places. (It may seem wasteful to duplicate a file, but for now, there is no other way to do this.)
The installed files:
.../plug-ins/mySuite/mySuite.scm
/locale/es/LC_MESSAGES/mySuite.mo
/de/LC_MESSAGES/mySuite.mo
.../plug-ins/mySuiteMore/mySuiteMore.scm
/locale/es/LC_MESSAGES/mySuiteMore.mo
/de/LC_MESSAGES/mySuiteMore.mo
Note that the same translation data is named by two domain names.
To do that install, you must duplicate the meson command in the meson.build file where the translation data is (alongside mySuite.scm):
i18n.gettext(
"mySuite", # first domain name
preset: 'glib',
install_dir:
get_option('prefix') / get_option('libdir') /
'plug-ins' / 'mySuite' / 'locale')
i18n.gettext(
"mySuiteMore", # second domain name
preset: 'glib',
install_dir: # second, duplicate location
get_option('prefix') / get_option('libdir') /
'plug-ins' / 'mySuiteMore' / 'locale')
In this use case, your source directory could be like:
mySuite/ mySuite.scm (defines two PDB procedures)
/ mySuiteMore.scm (defines another PDB procedure)
/ meson.build
/ es.po (from translators)
/ de.po
/ LINGUAS (a text file listing "es" and "de")
Example for Use Case: Plugins not Internationalized
When you don’t want to internationalize your plugin, just forego the instructions for the other use cases.
You don’t need to call “script-fu-register-i18n”. You can call it to document that your plugin is not internationalized:
(script-fu-register-i18n "plugin-in-my" "None")
Your plugin will only appear in the language of the user-facing strings of the source code (which need not be English.)
Programmers Reference
The ScriptFu language has
- the script-fu-register-i18n function
- the ‘_’ special form.
script-fu-register-i18n
You should only call this function in a new-style plugin installed to /plugins.
(script-fu-register-i18n "PDB name"
"domainName" | "Standard" | "None"
["relative path"]) => void
This function is optional, needed only for internationalization.
When used, it must appear at the outermost lexical level of a script. That is, in the global environment.
When used, it must appear AFTER a call to script-fu-register- that defines the PDB procedure named in the first argument.
The function takes two or three arguments and returns nothing.
First argument
- type string.
- name of a PDB procedure. The same name as used in a prior call to script-fu-register-procedure or -filter. The PDB procedure which is declaring it’s i18n.
Second argument
- type string
- a domain name in the i18n system or “Standard” or “None”
When the second argument is “Standard”, the domain name in the i18n system is the same as the file name where the PDB procedure is defined.
When the second argument is a provided domain name, it should match a name of a translation file installed with the plugin, e.g. a file named “domainName.mo” under a directory named “locale” somewhere in the installation.
Third argument
Optional path to a “catalog” directory. You should rarely need it. The reasons to use it are not documented here.
When the third argument is not provided, the i18n system uses as “catalog” a directory named “locale” in the directory where the plugin is installed. That is the canonical, i.e. suggested catalog location.
When provided, the third argument must be:
- type string.
- a relative path to a catalog directory, almost always named “locale”. The path must be below the parent directory of the file where the PDB procedure is defined. Note that can’t use a path that is absolute or above the parent directory, else you get a run time error message.
The Marker “_”
Use this in the Scheme source of a plugin. Use it to mark user-facing GUI strings that should be internationalized, i.e. translated into native language.
Typically, you mark these strings:
- The label of the menu item of the plugin
- Labels on widgets in a dialog for the plugin
- Error and information messages to the user
The marker _ is a so-called “special form” in the ScriptFu language. It is not part of the Scheme language. It is not a function. The _ marker character must precede a double quote character.
Example, to mark a pop-up menu widget:
SF-OPTION _"Orientation" '(_"Horizontal" _"Vertical")
This means both the label of the pop-up widget, and the strings for the choices, should be translated.
Do not confuse _ used to mark internationalizable strings with _ used to designate the character for a keyboard shortcut. When used for a keyboard shortcut, it appears inside a string.
Internationalizing Third-party Scheme Plugins in /scripts
Third party plugins installed to /scripts are “old style” plugins, served by another plugin “extension-script-fu.” You can not internationalize them, or at least not easily.
If you want to internationalize your plugin, you should instead create a “new-style” plugin installed to /plug-ins.
The plugins shipped with GIMP and installed to /scripts use shared translation data, for example at /usr/local/share/locale/es/LC_MESSAGES/gimp30-script-fu.mo
(A technical explanation. Only GIMP developers need read this. Old-style scripts are “served” by another plugin, extension-script-fu. The extension-script-fu plugin has its own i18n declaration that is used for all served scripts. So served scripts all use the domain gimp30-script-fu and the corresponding translation data installed with GIMP. See the use of the macro DEFINE_STD_SET_I18N in the source files script-fu.c and script-fu-intl.h. )
The Role of Translators
This briefly explains how translators work together with script authors.
You mark strings in the source.
A translator is usually fluent in English and one target language. You may need many translators for many target languages.
A person in the role of translator first uses software tools to scan source scripts to produce .po files.
The .po files are text files. A .po file essentially has many pairs of English phrases with their translations. The “p” means “portable.” The .po files are usually kept as “generated” source in the source code repository.
Then the translator uses software tools to edit .po files, changing the translated second half of translation string pairs. (The second half was initially untranslated.)
When you change user-facing strings in the source code, translators can update the .po files, without starting the whole process over again.
At build time, another tool compiles the .po files into binary .mo files. The “m” means machine. The .mo files are more compact than the .mo files, but are not human readable. The .mo files are installed with a plugin.