Internationalizing Filters
[Theory] Introduction
Just like plug-ins, filters can show their user interface in the native language of the user, using the very same “gettext” system.
If you haven’t yet, read Internationalizing Plug-Ins
first: the _() and N_() macros, the role of translators and the
.po/.mo files are pretty much all the same for filters, so this
tutorial only covers the filter-specific bits.
What differs comes from how filters are loaded:
a plug-in runs in its own process, where libgimp picks a gettext domain
and a catalog directory for you. A filter is a module which libgegl
loads into the GIMP process, and there is no special i18n function to
pick the domain or catalog: you have to do it yourself.
The process is:
- Declare your domain and mark the user-facing strings in your source
- Bind your domain to your catalog directory
- Coordinate with translators, exactly as for plug-ins
- Build and install the compiled translations next to your filter
More details and examples follow.
[Code] Declaring the Domain and Marking Strings
GEGL’s own/core operations include <glib/gi18n-lib.h> (do
not confuse with <glib/gi18n.h>, without -lib), which works to
call gettext to look up the string in the domain you name, instead of
the default domain of the process (which belongs to GIMP).
GEGL operations are compiled with GETTEXT_PACKAGE set to "gegl-*",
so yours needs its custom domain name, defined before including the header.
Now let’s take the operation we wrote in the point filter tutorial and mark the strings which a user will actually read:
#define GETTEXT_PACKAGE "zemarmot-gegl-demo"
#include <glib/gi18n-lib.h>
#ifdef GEGL_PROPERTIES
enum_start (zemarmot_hello_world_letter)
enum_value (ZEMARMOT_HELLO_WORLD_LETTER_H, "h", N_("Letter H"))
enum_value (ZEMARMOT_HELLO_WORLD_LETTER_W, "w", N_("Letter W"))
enum_end (ZemarmotHelloWorldLetter)
property_enum (letter, _("Letter"),
ZemarmotHelloWorldLetter, zemarmot_hello_world_letter,
ZEMARMOT_HELLO_WORLD_LETTER_H)
description (_("The letter to draw"))
property_double (thickness, _("Thickness"), 1.0)
description (_("Thickness of the letter strokes, in percent of the image size"))
value_range (0.1, 10.0)
#else
#define GEGL_OP_POINT_FILTER
#define GEGL_OP_NAME gimp_tutorial_point_filter_op
#define GEGL_OP_C_SOURCE gimp-tutorial-point-filter-op.c
#include "gegl-op.h"
/* […] */
static void
gegl_op_class_init (GeglOpClass *klass)
{
GeglOperationClass *operation_class = GEGL_OPERATION_CLASS (klass);
GeglOperationPointFilterClass *point_filter_class = GEGL_OPERATION_POINT_FILTER_CLASS (klass);
point_filter_class->process = process;
gegl_operation_class_set_keys (operation_class,
"title", _("Hello World Point Filter"),
"name", "zemarmot:hello-world-point-filter",
"categories", "Artistic",
"description", _("Hello World as a Point filter for official GIMP tutorial"),
"gimp:menu-path", "<Image>/Filters/Hello Worlds/",
"gimp:menu-label", _("Point Filter"),
NULL);
}
#endifI added 2 properties to this demo filter (letter and thickness)
so that we have some labels to mark; also, the title, description and
menu label were translated.
Note the enum values marked with N_(): since they are a static table,
_() would not even compile there. You don’t have to translate them
later yourself: when GETTEXT_PACKAGE is defined, gegl-op.h looks up
the enum value names in your domain when it registers the enum type.
On the other hand, name and categories are identifiers, not labels,
so they must never be marked.
The gimp:menu-path (like gimp_procedure_add_menu_path on plug-ins) is
not marked either, since GIMP finds the menus by their English names,
so this path must not be translated.
[Code] Binding the Domain
Your strings are translated very early: the _() calls run when
your operation class is initialized, which GIMP triggers at startup,
right after GEGL loaded your module. So, to bind the domain, you
need some code which runs as soon as your module is loaded.
This is what the g_module_check_init() function is for.
gegl-op.h doesn’t define it, so you can:
G_MODULE_EXPORT const gchar * g_module_check_init (GModule *module);
G_MODULE_EXPORT const gchar *
g_module_check_init (GModule *module)
{
gchar *filter_dir = g_path_get_dirname (g_module_name (module));
gchar *catalog_dir = g_build_filename (filter_dir, "locale", NULL); /* relative to the filter dir */
#ifndef _WIN32
bindtextdomain (GETTEXT_PACKAGE, catalog_dir);
#else
{
wchar_t *catalog_dir_utf16 = g_utf8_to_utf16 (catalog_dir, -1, NULL, NULL, NULL);
if (catalog_dir_utf16)
wbindtextdomain (GETTEXT_PACKAGE, catalog_dir_utf16);
g_free (catalog_dir_utf16);
}
#endif
bind_textdomain_codeset (GETTEXT_PACKAGE, "UTF-8");
g_free (catalog_dir);
g_free (filter_dir);
return NULL;
}Put it after the #include "gegl-op.h" line, in the #else part of
your source.
[Theory] The Role of Translators
As said, the .pot and .po files are created and updated
exactly as for a C plug-in,
with xgettext and msgmerge. There is no difference in parsing etc, all is the same.
[Code] Building and Installing the Translations
The only difference regarding translation files is the .mo installing.
With msgfmt, the commands are the same.
With meson, the i18n.gettext() call is the same as for plug-ins,
only the install_dir changes:
i18n = import('i18n')
i18n.gettext('zemarmot-gegl-demo',
preset: 'glib',
install_dir: filter_dir / 'locale')filter_dir is the directory where you install your module itself.
filter_dir, like on
GIMP plug-ins, so your locale won’t get mixed with other filters’ ones.[Theory] Suites of Filters
Say you ship several related filters. So, since GEGL supports loading
filters on the same path, as said on the tip above, nothing has to be
duplicated:
just install all your modules in the same directory, use the same GETTEXT_PACKAGE for
all of them and there is a single locale directory, with a single set of .mo files.
Since each module is a separate library, each one still needs its own
g_module_check_init(). Binding the same domain several times is
harmless, so you may simply put this function in a small header which
all your sources include.
Conclusion
As you see, apart from binding the domain yourself, internationalizing a filter is the same affair as for a plug-in.
- Back to “How to write a filter” tutorial index
- Previous tutorial: Integrate with GIMP
- Next tutorial: Helper Tools for Developers