[gimp] libgimp: add a new set_i18n() method to GimpPlugIn class.



commit 4b9d8a56cb8aa08913b8eb4848a3b18405073a6b
Author: Jehan <jehan girinstud io>
Date:   Thu May 26 00:39:32 2022 +0200

    libgimp: add a new set_i18n() method to GimpPlugIn class.
    
    Clearly the old logic for localizing plug-ins was "core plug-ins first":
    
    * In particular, we were defaulting to the "gimp*-std-plugins" domain,
      asking plug-ins to override it with libgimp function
      gimp_plug_in_set_translation_domain(). Obviously any third-party
      plug-in would have to call this function since their strings are most
      likely not the same as GIMP core plug-ins.
    * Moreover this was only for the localization of menu items, which means
      we had to duplicate work anyway (simplified for core C plug-ins only
      with the INIT_I18N macro).
    * Also plug-ins had to manage the location of the Gettext catalogs,
      which is simpler for core plug-ins with gimp_locale_directory(), but
      more annoying for third-party plug-ins which can be installed
      basically anywhere (assuming their message catalogs are in their
      directory, not centrally installed on the system, especially for
      non-UNIX-like packages, with relocatable GIMP, no central package
      system and so on).
    * Finally in this logic of centrally installed catalogs, we were
      requesting that the domain name is unique, which makes sense in a
      Linux world with well maintained packages to avoid name clashes, not
      in a world with people making plug-ins without knowing too much what's
      done by their neighbour.
    
    So now the new logic is:
    
    - By default, GIMP will search for a folder called locale/ on the same
      level as the plug-in executable. The Gettext domain will be the name
      of the executable folder (and it doesn't matter too much if it's not
      unique in such configuration).
    - This can be disabled by overriding set_i18n() to NULL or
      reimplementing it. All our core plug-ins will do this since they will
      continue to use the centrally installed "gimp*-std-plugins" domain (or
      "gimp*-python" for Python plug-ins).
    - When not disabled while the folder is not available, warning messages
      will be outputted to stderr, so that plug-in developers can easily
      detect what is missing and how to handle it (and how to easily support
      internationalization of their plug-in).
    - gimp_plug_in_set_translation_domain() is removed and a future commit
      is going to get rid of _gimp_plug_in_domain_register() because we are
      going to get rid of the core-side localization of menu items (with
      logic explained in the upcoming commit).

 libgimp/gimp.def             |   1 -
 libgimp/gimpplugin-private.h |   5 +
 libgimp/gimpplugin.c         | 225 ++++++++++++++++++++++++++++++++++---------
 libgimp/gimpplugin.h         |  54 ++++++++++-
 4 files changed, 238 insertions(+), 47 deletions(-)
---
diff --git a/libgimp/gimp.def b/libgimp/gimp.def
index bab2fe2e4b..b05044bb11 100644
--- a/libgimp/gimp.def
+++ b/libgimp/gimp.def
@@ -704,7 +704,6 @@ EXPORTS
        gimp_plug_in_remove_temp_procedure
        gimp_plug_in_set_help_domain
        gimp_plug_in_set_pdb_error_handler
-       gimp_plug_in_set_translation_domain
        gimp_procedure_add_argument
        gimp_procedure_add_argument_from_property
        gimp_procedure_add_aux_argument
diff --git a/libgimp/gimpplugin-private.h b/libgimp/gimpplugin-private.h
index 9c45fe7f81..cda2238501 100644
--- a/libgimp/gimpplugin-private.h
+++ b/libgimp/gimpplugin-private.h
@@ -37,6 +37,11 @@ void            _gimp_plug_in_read_expect_msg   (GimpPlugIn      *plug_in,
                                                  GimpWireMessage *msg,
                                                  gint             type);
 
+gboolean        _gimp_plug_in_set_i18n          (GimpPlugIn      *plug_in,
+                                                 const gchar     *procedure_name,
+                                                 gchar          **gettext_domain,
+                                                 gchar          **catalog_dir);
+
 GimpProcedure * _gimp_plug_in_create_procedure  (GimpPlugIn      *plug_in,
                                                  const gchar     *procedure_name);
 
diff --git a/libgimp/gimpplugin.c b/libgimp/gimpplugin.c
index 7e3db21358..35e89f14bc 100644
--- a/libgimp/gimpplugin.c
+++ b/libgimp/gimpplugin.c
@@ -22,6 +22,7 @@
 #include "config.h"
 
 #include <errno.h>
+#include <libintl.h>
 #include <string.h>
 
 #include "gimp.h"
@@ -166,6 +167,11 @@ static void       gimp_plug_in_get_property      (GObject         *object,
                                                   GValue          *value,
                                                   GParamSpec      *pspec);
 
+static gboolean   gimp_plug_in_real_set_i18n     (GimpPlugIn      *plug_in,
+                                                  const gchar     *procedure_name,
+                                                  gchar          **gettext_domain,
+                                                  gchar          **catalog_dir);
+
 static void       gimp_plug_in_register          (GimpPlugIn      *plug_in,
                                                   GList           *procedures);
 
@@ -203,6 +209,8 @@ static void       gimp_plug_in_destroy_hashes    (GimpPlugIn      *plug_in);
 static void       gimp_plug_in_destroy_proxies   (GHashTable      *hash_table,
                                                   gboolean         destroy_all);
 
+static void       gimp_plug_in_init_i18n         (GimpPlugIn      *plug_in);
+
 
 G_DEFINE_TYPE_WITH_PRIVATE (GimpPlugIn, gimp_plug_in, G_TYPE_OBJECT)
 
@@ -222,6 +230,8 @@ gimp_plug_in_class_init (GimpPlugInClass *klass)
   object_class->set_property = gimp_plug_in_set_property;
   object_class->get_property = gimp_plug_in_get_property;
 
+  klass->set_i18n            = gimp_plug_in_real_set_i18n;
+
   /**
    * GimpPlugIn:read-channel:
    *
@@ -373,45 +383,21 @@ gimp_plug_in_get_property (GObject    *object,
     }
 }
 
-
-/*  public functions  */
-
-/**
- * gimp_plug_in_set_translation_domain:
- * @plug_in:     A #GimpPlugIn.
- * @domain_name: The name of the textdomain (must be unique).
- * @domain_path: (nullable): A file pointing to the compiled message catalog
- *               (may be %NULL).
- *
- * Sets a textdomain for localisation for the @plug_in.
- *
- * This function adds a textdomain to the list of domains Gimp
- * searches for strings when translating its menu entries. There is no
- * need to call this function for plug-ins that have their strings
- * included in the 'gimp-std-plugins' domain as that is used by
- * default. If the compiled message catalog is not in the standard
- * location, you may specify an absolute path to another
- * location.
- *
- * This function can only be called in the
- * [vfunc@PlugIn.query_procedures] function of a plug-in.
- *
- * Since: 3.0
- **/
-void
-gimp_plug_in_set_translation_domain (GimpPlugIn  *plug_in,
-                                     const gchar *domain_name,
-                                     GFile       *domain_path)
+static gboolean
+gimp_plug_in_real_set_i18n (GimpPlugIn   *plug_in,
+                            const gchar  *procedure_name,
+                            gchar       **gettext_domain,
+                            gchar       **catalog_dir)
 {
-  g_return_if_fail (GIMP_IS_PLUG_IN (plug_in));
-  g_return_if_fail (domain_name != NULL);
-  g_return_if_fail (domain_path == NULL || G_IS_FILE (domain_path));
+  /* Default to enabling localization by gettext. It will have the good
+   * side-effect of warning plug-in developers of the existence of the
+   * ability through stderr if the catalog directory is missing.
+   */
+  return TRUE;
+}
 
-  g_free (plug_in->priv->translation_domain_name);
-  plug_in->priv->translation_domain_name = g_strdup (domain_name);
 
-  g_set_object (&plug_in->priv->translation_domain_path, domain_path);
-}
+/*  public functions  */
 
 /**
  * gimp_plug_in_set_help_domain:
@@ -890,13 +876,133 @@ _gimp_plug_in_read_expect_msg (GimpPlugIn      *plug_in,
     }
 }
 
+gboolean
+_gimp_plug_in_set_i18n (GimpPlugIn   *plug_in,
+                        const gchar  *procedure_name,
+                        gchar       **gettext_domain,
+                        gchar       **catalog_dir)
+{
+  gboolean use_gettext;
+
+  g_return_val_if_fail (GIMP_IS_PLUG_IN (plug_in), FALSE);
+  g_return_val_if_fail (gettext_domain && *gettext_domain == NULL, FALSE);
+  g_return_val_if_fail (catalog_dir && *catalog_dir == NULL, FALSE);
+
+  if (! plug_in->priv->translation_domain_path ||
+      ! plug_in->priv->translation_domain_name)
+    gimp_plug_in_init_i18n (plug_in);
+
+  if (! GIMP_PLUG_IN_GET_CLASS (plug_in)->set_i18n)
+    {
+      use_gettext = FALSE;
+    }
+  else
+    {
+      use_gettext = GIMP_PLUG_IN_GET_CLASS (plug_in)->set_i18n (plug_in,
+                                                                procedure_name,
+                                                                gettext_domain,
+                                                                catalog_dir);
+      if (use_gettext)
+        {
+          if (! (*gettext_domain))
+            *gettext_domain = g_strdup (plug_in->priv->translation_domain_name);
+
+          if (*catalog_dir)
+            {
+              if (g_path_is_absolute (*catalog_dir))
+                {
+                  g_printerr ("[%s] The catalog directory set by set_i18n() is not relative: %s\n",
+                              procedure_name, *catalog_dir);
+                  g_printerr ("[%s] Localization disabled\n", procedure_name);
+
+                  use_gettext = FALSE;
+                }
+              else
+                {
+                  gchar *rootdir   = g_path_get_dirname (gimp_get_progname ());
+                  GFile *root_file = g_file_new_for_path (rootdir);
+                  GFile *catalog_file;
+                  GFile *parent;
+
+                  catalog_file = g_file_resolve_relative_path (root_file, *catalog_dir);
+
+                  /* Verify that the catalog is a subdir of the plug-in folder.
+                   * We do not want to allow plug-ins to look outside their own
+                   * realm.
+                   */
+                  parent = g_file_dup (catalog_file);
+                  do
+                    {
+                      if (g_file_equal (parent, root_file))
+                        break;
+                      g_clear_object (&parent);
+                    }
+                  while ((parent = g_file_get_parent (parent)));
+
+                  if (parent == NULL)
+                    {
+                      g_printerr ("[%s] The catalog directory set by set_i18n() is not a subdirectory: %s\n",
+                                  procedure_name, *catalog_dir);
+                      g_printerr ("[%s] Localization disabled\n", procedure_name);
+
+                      use_gettext = FALSE;
+                    }
+
+                  g_free (rootdir);
+                  g_object_unref (root_file);
+                  g_clear_object (&parent);
+                  g_object_unref (catalog_file);
+                }
+            }
+          else
+            {
+              *catalog_dir = g_file_get_path (plug_in->priv->translation_domain_path);
+            }
+        }
+    }
+
+  if (use_gettext && ! g_file_test (*catalog_dir, G_FILE_TEST_IS_DIR))
+    {
+      g_printerr ("[%s] The catalog directory does not exist: %s\n",
+                  procedure_name, *catalog_dir);
+      g_printerr ("[%s] Override method set_i18n() for the plug-in to customize or disable localization.\n",
+                  procedure_name);
+      g_printerr ("[%s] Localization disabled\n", procedure_name);
+
+      use_gettext = FALSE;
+    }
+
+  if (! use_gettext)
+    {
+      g_clear_pointer (gettext_domain, g_free);
+      g_clear_pointer (catalog_dir, g_free);
+    }
+
+  return use_gettext;
+}
+
 GimpProcedure *
 _gimp_plug_in_create_procedure (GimpPlugIn  *plug_in,
                                 const gchar *procedure_name)
 {
+  gchar *gettext_domain = NULL;
+  gchar *catalog_dir    = NULL;
+
   g_return_val_if_fail (GIMP_IS_PLUG_IN (plug_in), NULL);
   g_return_val_if_fail (gimp_is_canonical_identifier (procedure_name), NULL);
 
+  if (_gimp_plug_in_set_i18n (plug_in, procedure_name, &gettext_domain, &catalog_dir))
+    {
+      bindtextdomain (gettext_domain, catalog_dir);
+#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
+      bind_textdomain_codeset (gettext_domain, "UTF-8");
+#endif
+      textdomain (gettext_domain);
+
+      g_free (gettext_domain);
+      g_free (catalog_dir);
+    }
+
   if (GIMP_PLUG_IN_GET_CLASS (plug_in)->create_procedure)
     return GIMP_PLUG_IN_GET_CLASS (plug_in)->create_procedure (plug_in,
                                                                procedure_name);
@@ -932,12 +1038,6 @@ gimp_plug_in_register (GimpPlugIn *plug_in,
 
   g_list_free_full (procedures, g_free);
 
-  if (plug_in->priv->translation_domain_name)
-    {
-      _gimp_plug_in_domain_register (plug_in->priv->translation_domain_name,
-                                     plug_in->priv->translation_domain_path);
-    }
-
   if (plug_in->priv->help_domain_name)
     {
       _gimp_plug_in_help_register (plug_in->priv->help_domain_name,
@@ -1216,7 +1316,22 @@ gimp_plug_in_proc_run_internal (GimpPlugIn    *plug_in,
                                 GPProcReturn  *proc_return)
 {
   GimpValueArray *arguments;
-  GimpValueArray *return_values = NULL;
+  GimpValueArray *return_values  = NULL;
+  gchar          *gettext_domain = NULL;
+  gchar          *catalog_dir    = NULL;
+
+  if (_gimp_plug_in_set_i18n (plug_in, gimp_procedure_get_name (procedure),
+                              &gettext_domain, &catalog_dir))
+    {
+      bindtextdomain (gettext_domain, catalog_dir);
+#ifdef HAVE_BIND_TEXTDOMAIN_CODESET
+      bind_textdomain_codeset (gettext_domain, "UTF-8");
+#endif
+      textdomain (gettext_domain);
+
+      g_free (gettext_domain);
+      g_free (catalog_dir);
+    }
 
   gimp_plug_in_push_procedure (plug_in, procedure);
 
@@ -1493,3 +1608,27 @@ gimp_plug_in_destroy_proxies (GHashTable *hash_table,
         }
     }
 }
+
+static void
+gimp_plug_in_init_i18n (GimpPlugIn *plug_in)
+{
+  gchar *rootdir      = g_path_get_dirname (gimp_get_progname ());
+  GFile *root_file    = g_file_new_for_path (rootdir);
+  GFile *catalog_file = NULL;
+
+  g_return_if_fail (GIMP_IS_PLUG_IN (plug_in));
+
+  /* Default domain name is the program directory name. */
+  g_free (plug_in->priv->translation_domain_name);
+  plug_in->priv->translation_domain_name = g_path_get_basename (rootdir);
+
+  /* Default catalog path is the locale/ directory under the root
+   * directory.
+   */
+  catalog_file = g_file_resolve_relative_path (root_file, "locale");
+  g_set_object (&plug_in->priv->translation_domain_path, catalog_file);
+
+  g_free (rootdir);
+  g_object_unref (root_file);
+  g_object_unref (catalog_file);
+}
diff --git a/libgimp/gimpplugin.h b/libgimp/gimpplugin.h
index f8161b8b3d..a966e3e94d 100644
--- a/libgimp/gimpplugin.h
+++ b/libgimp/gimpplugin.h
@@ -127,6 +127,57 @@ struct _GimpPlugInClass
    */
   void             (* quit)             (GimpPlugIn  *plug_in);
 
+  /**
+   * GimpPlugInClass::set_i18n:
+   * @plug_in:        a #GimpPlugIn.
+   * @procedure_name: procedure name.
+   * @gettext_domain: (out) (nullable): Gettext domain. If %NULL, it
+   *                  defaults to the plug-in name as determined by the
+   *                  directory the binary is called from.
+   * @catalog_dir:    (out) (nullable): relative path to a subdirectory
+   *                  of the plug-in folder containing the compiled
+   *                  Gettext message catalogs. If %NULL, it defaults to
+   *                  "locale/".
+   *
+   * This method can be overridden by all plug-ins to customize
+   * internationalization of the plug-in.
+   *
+   * This method will be called before initializing, querying or running
+   * @procedure_name (respectively with [vfunc@PlugIn.init_procedures],
+   * [vfunc@PlugIn.query_procedures] or with the `run()` function set in
+   * `gimp_image_procedure_new()`).
+   *
+   * By default, GIMP plug-ins look up gettext compiled message catalogs
+   * in the subdirectory `locale/` under the plug-in folder (same folder
+   * as `gimp_get_progname()`) with a text domain equal to the plug-in
+   * name (regardless @procedure_name). It is unneeded to override this
+   * method if you follow this localization scheme.
+   *
+   * If you wish to disable localization or localize with another system,
+   * simply set the method to %NULL, or possibly implement this method
+   * to do something useful for your usage while returning %FALSE.
+   *
+   * If you wish to tweak the @gettext_domain or the @localedir, return
+   * %TRUE and allocate appropriate @gettext_domain and/or @localedir
+   * (these use the default if set %NULL).
+   *
+   * Note that @localedir must be a relative path, subdirectory of the
+   * directory of `gimp_get_progname()`.
+   *
+   * When localizing your plug-in this way, GIMP also binds
+   * @gettext_domain to the UTF-8 encoding.
+   *
+   * Returns: %TRUE if this plug-in will use Gettext localization. You
+   *          may return %FALSE if you wish to disable localization or
+   *          set it up differently.
+   *
+   * Since: 3.0
+   */
+  gboolean         (* set_i18n)         (GimpPlugIn   *plug_in,
+                                         const gchar  *procedure_name,
+                                         gchar       **gettext_domain,
+                                         gchar       **catalog_dir);
+
   /* Padding for future expansion */
   /*< private >*/
   void (* _gimp_reserved1) (void);
@@ -144,9 +195,6 @@ GQuark          gimp_plug_in_error_quark            (void);
 
 GType           gimp_plug_in_get_type               (void) G_GNUC_CONST;
 
-void            gimp_plug_in_set_translation_domain (GimpPlugIn    *plug_in,
-                                                     const gchar   *domain_name,
-                                                     GFile         *domain_path);
 void            gimp_plug_in_set_help_domain        (GimpPlugIn    *plug_in,
                                                      const gchar   *domain_name,
                                                      GFile         *domain_uri);


[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]