From: Zbigniew Chyla <cyba@piast.t19.ds.pwr.wroc.pl>
Subject: Re: [PATCH] improving on i18n
To: Sven Neumann <sven@gimp.org>
Cc: gimp-developer@xcf.berkeley.edu
Date: Thu, 07 Dec 2000 15:12:20 +0100
Gnus-Warning: This is a duplicate of message <20001207151220.A530@t19.ds.pwr.wroc.pl>

On Wed, 2000-12-06 at 15:24:58, Zbigniew Chyla wrote:

> (...)
> Thanks tor this! It should be no problem for translators - I have another
> patch, that doesn't _require_ modified translations. If you replace
> _("File") with Q_("!menu!File") in your code, old translations (containing
> only "File") will still work. I'll send improved patch tomorrow.

Attached patch implements new version of the Q_() macro. As I said before,
adding new prefixes to strings doesn't break existing translations.

This time I don't include whole po/Makefile.in.in but only small patch
adding "--keyword=Q_" to the list of xgettext options. Patch is being
applied by autogen.sh after running gettextize. Many GNOME apps
(eg. gnumeric) modify po/Makefile.in.in this way.


Zbigniew
diff -ruN /home/cyba/gcvs/gimp/ChangeLog gimp/ChangeLog
--- /home/cyba/gcvs/gimp/ChangeLog	Sun Dec  3 20:16:03 2000
+++ gimp/ChangeLog	Wed Dec  6 19:32:58 2000
@@ -1,3 +1,33 @@
+2000-12-06  Zbigniew Chyla  <cyba@gnome.pl>
+
+	I18n improvement - added support for Q_() macro (replacement for _()
+	allowing to add a prefix of the form "!some_prefix!" to the
+	string and thus making it possible to translate it differently in
+	different contexts).
+
+	* libgimp/gimpintl.h, libgimp/libgimp-intl.h: included "gimputils.h",
+	defined Q_() macro.
+
+	* libgimp/gimputils.h: added functions declarations:
+	gimp_i18n_qualifier_prefix_gettext(),
+	gimp_i18n_qualifier_prefix_dgettext(),
+	gimp_i18n_qualifier_prefix_noop().
+
+	* libgimp/gimputils.c: included "gimpintl.h", added functions:
+	gimp_i18n_qualifier_prefix_gettext(),
+	gimp_i18n_qualifier_prefix_dgettext(),
+	gimp_i18n_qualifier_prefix_noop().
+
+	* autogen.sh: patching po/Makefile.in.in (with
+	po/Makefile.in.in.i18npatch) after running gettextize.
+
+	* po/Makefile.in.in.i18n: new file - patch for po/Makefile.in.in (file
+	provided by gettextize), adds "--keyword=Q_" to the list of xgettext
+	options.
+
+	* po-libgimp/Makefile.in.in, po-plug-ins/Makefile.in.in: added
+	"--keyword=Q_" to the list of xgettext options.
+
 2000-11-30  Andy Thomas <alt@gimp.org>
 
 	* app/curves.c
diff -ruN /home/cyba/gcvs/gimp/libgimp/gimputils.h gimp/libgimp/gimputils.h
--- /home/cyba/gcvs/gimp/libgimp/gimputils.h	Thu Nov 23 12:38:50 2000
+++ gimp/libgimp/gimputils.h	Wed Dec  6 19:32:58 2000
@@ -45,13 +45,12 @@
 			  const gchar *exceptions);
 gchar * gimp_strcompress (const gchar *source);
 #endif  /* GLIB <= 1.3 */
-
+gchar *gimp_i18n_qualifier_prefix_gettext (const gchar *string);
+gchar *gimp_i18n_qualifier_prefix_dgettext (const gchar *domain, const gchar *string);
+gchar *gimp_i18n_qualifier_prefix_noop (const gchar *string);
 
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
 
 #endif /* __GIMPUTILS_H__ */
-
-
-
diff -ruN /home/cyba/gcvs/gimp/libgimp/libgimp-intl.h gimp/libgimp/libgimp-intl.h
--- /home/cyba/gcvs/gimp/libgimp/libgimp-intl.h	Thu Nov 23 12:38:50 2000
+++ gimp/libgimp/libgimp-intl.h	Wed Dec  6 19:32:58 2000
@@ -22,10 +22,12 @@
 #ifndef __LIBGIMP_INTL_H__
 #define __LIBGIMP_INTL_H__
 
+#include "gimputils.h"
 
 #ifdef ENABLE_NLS
 #    include <libintl.h>
 #    define _(String) dgettext ("gimp-libgimp", String)
+#    define Q_(String) gimp_i18n_qualifier_prefix_dgettext ("gimp-libgimp", String)
 #    undef gettext
 #    define gettext(String) dgettext ("gimp-libgimp", String)
 #    ifdef gettext_noop
@@ -37,6 +39,7 @@
 /* Stubs that do something close enough.  */
 #    define gettext(String) (String)
 #    define _(String) (String)
+#    define Q_(String) gimp_i18n_qualifier_prefix_noop (String)
 #    define N_(String) (String)
 #endif
 
diff -ruN /home/cyba/gcvs/gimp/libgimp/gimpintl.h gimp/libgimp/gimpintl.h
--- /home/cyba/gcvs/gimp/libgimp/gimpintl.h	Thu Nov 23 12:38:48 2000
+++ gimp/libgimp/gimpintl.h	Wed Dec  6 19:32:58 2000
@@ -25,6 +25,8 @@
 #include <glib.h>
 #include <locale.h>
 
+#include "gimputils.h"
+
 /* Copied from gnome-i18n.h by Tom Tromey <tromey@creche.cygnus.com>
  * Heavily modified by Daniel Egger <Daniel.Egger@t-online.de>
  * So be sure to hit me instead of him if something is wrong here
@@ -40,6 +42,7 @@
 #ifdef ENABLE_NLS
 #    include <libintl.h>
 #    define _(String) gettext (String)
+#    define Q_(String) gimp_i18n_qualifier_prefix_gettext (String)
 #    ifdef gettext_noop
 #        define N_(String) gettext_noop (String)
 #    else
@@ -53,6 +56,7 @@
 #    define dcgettext(Domain,Message,Type) (Message)
 #    define bindtextdomain(Domain,Directory) (Domain)
 #    define _(String) (String)
+#    define Q_(String) gimp_i18n_qualifier_prefix_noop (String)
 #    define N_(String) (String)
 #endif
 
diff -ruN /home/cyba/gcvs/gimp/libgimp/gimputils.c gimp/libgimp/gimputils.c
--- /home/cyba/gcvs/gimp/libgimp/gimputils.c	Thu Nov 23 12:38:50 2000
+++ gimp/libgimp/gimputils.c	Wed Dec  6 19:32:58 2000
@@ -23,8 +23,12 @@
 #include <string.h>
 #include <glib.h>
 
+#include "gimpintl.h"
 #include "gimputils.h"
 
+#define Q_PREFIX_START '!'
+#define Q_PREFIX_END   '!'
+
 /**
  * gimp_strescape:
  * @source: A string to escape special characters in.
@@ -197,3 +201,113 @@
   return dest;
 }
 #endif  /* GLIB <= 1.3 */
+
+/***
+ * gimp_i18n_qualifier_prefix_gettext
+ *
+ **/
+gchar *
+gimp_i18n_qualifier_prefix_gettext (const gchar *string)
+{
+	g_assert (string != NULL);
+
+	if (*string != Q_PREFIX_START) {
+		return gettext (string);
+	} else {
+		gchar *translation;
+
+		translation = gettext (string);
+		if (translation != string) {
+			if (*translation != Q_PREFIX_START) {
+				return translation;
+			} else {
+				gchar *real_translation;
+
+				real_translation = strchr (translation + 1, Q_PREFIX_END);
+				if (real_translation != NULL) {
+					return real_translation + 1;
+				} else {
+					g_warning ("Ivalid Q_() translation: \"%s\"", translation);
+					return translation;
+				}
+			}
+		} else {
+			gchar *real_string;
+
+			real_string = strchr (string + 1, Q_PREFIX_END);
+			if (real_string != NULL) {
+				return gettext (real_string + 1);
+			} else {
+				g_warning ("Ivalid Q_() string: \"%s\"", string);
+				return (gchar *) string;
+			}
+		}
+	}
+}
+
+/***
+ * gimp_i18n_qualifier_prefix_dgettext
+ *
+ **/
+gchar *
+gimp_i18n_qualifier_prefix_dgettext (const gchar *domain, const gchar *string)
+{
+	g_assert (string != NULL);
+
+	if (*string != Q_PREFIX_START) {
+		return dgettext (domain, string);
+	} else {
+		gchar *translation;
+
+		translation = dgettext (domain, string);
+		if (translation != string) {
+			if (*translation != Q_PREFIX_START) {
+				return translation;
+			} else {
+				gchar *real_translation;
+
+				real_translation = strchr (translation + 1, Q_PREFIX_END);
+				if (real_translation != NULL) {
+					return real_translation + 1;
+				} else {
+					g_warning ("Ivalid Q_() translation: \"%s\"", translation);
+					return translation;
+				}
+			}
+		} else {
+			gchar *real_string;
+
+			real_string = strchr (string + 1, Q_PREFIX_END);
+			if (real_string != NULL) {
+				return dgettext (domain, real_string + 1);
+			} else {
+				g_warning ("Ivalid Q_() string: \"%s\"", string);
+				return (gchar *) string;
+			}
+		}
+	}
+}
+
+/***
+ * gimp_i18n_qualifier_prefix_noop
+ *
+ **/
+gchar *
+gimp_i18n_qualifier_prefix_noop (const gchar *string)
+{
+	g_assert (string != NULL);
+
+	if (*string != Q_PREFIX_START) {
+		return (gchar *) string;
+	} else {
+		gchar *real_string;
+
+		real_string = strchr (string + 1, Q_PREFIX_END);
+		if (real_string != NULL) {
+			return real_string + 1;
+		} else {
+			g_warning ("Ivalid Q_() string: \"%s\"", string);
+			return (gchar *) string;
+		}
+	}
+}
diff -ruN /home/cyba/gcvs/gimp/autogen.sh gimp/autogen.sh
--- /home/cyba/gcvs/gimp/autogen.sh	Sun Dec  3 20:18:06 2000
+++ gimp/autogen.sh	Wed Dec  6 19:42:30 2000
@@ -105,6 +105,14 @@
 # while making dist.
 echo "no" | gettextize --copy --force
 
+if [ -r po/Makefile.in.in.i18npatch ]; then
+	if grep 'keyword=Q_' po/Makefile.in.in >/dev/null; then
+		echo "no need for patching file \"Makefile.in.in\""
+	else
+		patch po/Makefile.in.in < po/Makefile.in.in.i18npatch
+	fi
+fi
+
 autogen_dirs="."
 #if test -z "$NO_GCG"; then
 #	autogen_dirs="$autogen_dirs tools/gcg"
diff -ruN /home/cyba/gcvs/gimp/po/Makefile.in.in.i18npatch gimp/po/Makefile.in.in.i18npatch
--- /home/cyba/gcvs/gimp/po/Makefile.in.in.i18npatch	Thu Jan  1 01:00:00 1970
+++ gimp/po/Makefile.in.in.i18npatch	Wed Dec  6 19:35:34 2000
@@ -0,0 +1,9 @@
+--- Makefile.in.in.clean	Wed Dec  6 19:33:55 2000
++++ Makefile.in.in	Wed Dec  6 19:32:58 2000
+@@ -87,5 +87,5 @@
+ $(srcdir)/$(PACKAGE).pot: $(POTFILES)
+ 	$(XGETTEXT) --default-domain=$(PACKAGE) --directory=$(top_srcdir) \
+-	  --add-comments --keyword=_ --keyword=N_ \
++	  --add-comments --keyword=_ --keyword=N_ --keyword=Q_ \
+ 	  --files-from=$(srcdir)/POTFILES.in \
+ 	&& test ! -f $(PACKAGE).po \
diff -ruN /home/cyba/gcvs/gimp/po-libgimp/Makefile.in.in gimp/po-libgimp/Makefile.in.in
--- /home/cyba/gcvs/gimp/po-libgimp/Makefile.in.in	Thu Nov 23 12:39:17 2000
+++ gimp/po-libgimp/Makefile.in.in	Wed Dec  6 19:32:58 2000
@@ -84,7 +84,7 @@
 
 $(srcdir)/$(PACKAGE).pot: $(POTFILES)
 	$(XGETTEXT) --default-domain=$(PACKAGE) --directory=$(top_srcdir) \
-	  --add-comments --keyword=_ --keyword=N_ \
+	  --add-comments --keyword=_ --keyword=N_ --keyword=Q_ \
 	  --files-from=$(srcdir)/POTFILES.in \
 	&& test ! -f $(PACKAGE).po \
 	   || ( rm -f $(srcdir)/$(PACKAGE).pot \
diff -ruN /home/cyba/gcvs/gimp/po-plug-ins/Makefile.in.in gimp/po-plug-ins/Makefile.in.in
--- /home/cyba/gcvs/gimp/po-plug-ins/Makefile.in.in	Thu Nov 23 12:39:17 2000
+++ gimp/po-plug-ins/Makefile.in.in	Wed Dec  6 19:32:58 2000
@@ -84,7 +84,7 @@
 
 $(srcdir)/$(PACKAGE).pot: $(POTFILES)
 	$(XGETTEXT) --default-domain=$(PACKAGE) --directory=$(top_srcdir) \
-	  --add-comments --keyword=_ --keyword=N_ \
+	  --add-comments --keyword=_ --keyword=N_ --keyword=Q_ \
 	  --files-from=$(srcdir)/POTFILES.in \
 	&& test ! -f $(PACKAGE).po \
 	   || ( rm -f $(srcdir)/$(PACKAGE).pot \
diff -ru /home/cyba/gcvs/gimp/app/blend.c gimp/app/blend.c
--- /home/cyba/gcvs/gimp/app/blend.c	Sun Dec  3 20:16:03 2000
+++ gimp/app/blend.c	Wed Dec  6 21:39:01 2000
@@ -317,7 +317,7 @@
     (FALSE, gradient_type_callback,
      &options->gradient_type, (gpointer) options->gradient_type_d,
 
-     _("Linear"),                 (gpointer) LINEAR, NULL,
+     Q_("!gradient_type!Linear"), (gpointer) LINEAR, NULL,
      _("Bi-Linear"),              (gpointer) BILINEAR, NULL,
      _("Radial"),                 (gpointer) RADIAL, NULL,
      _("Square"),                 (gpointer) SQUARE, NULL,
diff -ru /home/cyba/gcvs/gimp/app/brush_select.c gimp/app/brush_select.c
--- /home/cyba/gcvs/gimp/app/brush_select.c	Sun Dec  3 20:16:03 2000
+++ gimp/app/brush_select.c	Wed Dec  6 21:39:01 2000
@@ -464,7 +464,7 @@
   util_box = gtk_hbox_new (FALSE, 0);
   gtk_box_pack_end (GTK_BOX (bsp->options_box), util_box, FALSE, FALSE, 4);
 
-  button =  gtk_button_new_with_label (_("New"));
+  button =  gtk_button_new_with_label (Q_("!brush-button!New"));
   gtk_signal_connect (GTK_OBJECT (button), "clicked",
 		      GTK_SIGNAL_FUNC (brush_select_new_brush_callback),
 		      bsp);
diff -ru /home/cyba/gcvs/gimp/app/gradient.c gimp/app/gradient.c
--- /home/cyba/gcvs/gimp/app/gradient.c	Sun Dec  3 20:16:19 2000
+++ gimp/app/gradient.c	Wed Dec  6 22:46:31 2000
@@ -529,7 +529,7 @@
 
 static const gchar *blending_types[] =
 {
-  N_("Linear"),
+  N_("!blending_type!Linear"),
   N_("Curved"),
   N_("Sinusoidal"),
   N_("Spherical (increasing)"),
@@ -906,7 +906,7 @@
   gtk_box_pack_start (GTK_BOX (gvbox), button, FALSE, FALSE, 0);
   gtk_widget_show (button);
 
-  button = ed_create_button (_("Save as POV-Ray"),
+  button = ed_create_button (Q_("!gradient-button!Save as POV-Ray"),
 			     "dialogs/gradient_editor/save_as_pov_ray.html",
 			     GTK_SIGNAL_FUNC (ed_save_pov_callback),
 			     NULL);
@@ -1845,7 +1845,7 @@
   if (curr_gradient == NULL)
     return;
 
-  window = gtk_file_selection_new (_("Save as POV-Ray"));
+  window = gtk_file_selection_new (Q_("!gradient-dialog_title!Save as POV-Ray"));
   gtk_window_set_wmclass (GTK_WINDOW (window), "save_gradient", "Gimp");
   gtk_window_set_position (GTK_WINDOW (window), GTK_WIN_POS_MOUSE);
 
@@ -4617,7 +4617,7 @@
       else
 	menuitem =
 	  gtk_radio_menu_item_new_with_label (group,
-					      gettext (blending_types[i]));
+					      Q_(blending_types[i]));
 
       group = gtk_radio_menu_item_group (GTK_RADIO_MENU_ITEM (menuitem));
 
diff -ru /home/cyba/gcvs/gimp/app/palette.c gimp/app/palette.c
--- /home/cyba/gcvs/gimp/app/palette.c	Sun Dec  3 20:16:54 2000
+++ gimp/app/palette.c	Wed Dec  6 21:39:01 2000
@@ -1161,7 +1161,7 @@
 
   palette->popup_menu = menu = gtk_menu_new ();
 
-  menu_item = gtk_menu_item_new_with_label (_("New"));
+  menu_item = gtk_menu_item_new_with_label (Q_("!palette_color-menuitem!New"));
   gtk_menu_append (GTK_MENU (menu), menu_item);
   gtk_signal_connect (GTK_OBJECT (menu_item), "activate", 
 		      GTK_SIGNAL_FUNC (palette_dialog_new_entry_callback),
@@ -2176,7 +2176,7 @@
       gtk_notebook_append_page (GTK_NOTEBOOK (hbox), vbox,
 				gtk_label_new (_("Palette")));
       gtk_notebook_append_page (GTK_NOTEBOOK (hbox), scrolledwindow,
-				gtk_label_new (_("Select")));
+				gtk_label_new (Q_("!palette-notebook_tab!Select")));
     }
   else
     {
@@ -2216,7 +2216,7 @@
       gtk_container_add (GTK_CONTAINER (frame), vbox);
       gtk_widget_show (vbox);
       
-      button = gtk_button_new_with_label (_("New"));
+      button = gtk_button_new_with_label (Q_("!palette-button!New"));
       GTK_WIDGET_UNSET_FLAGS (button, GTK_RECEIVES_DEFAULT);
       gtk_misc_set_padding (GTK_MISC (GTK_BIN (button)->child), 2, 0);
       gtk_signal_connect (GTK_OBJECT (button), "clicked",
@@ -2238,7 +2238,7 @@
 			       "dialogs/palette_editor/delete_palette.html");
       gtk_widget_show (button);
       
-      button = gtk_button_new_with_label (_("Import"));
+      button = gtk_button_new_with_label (Q_("!palette-button!Import"));
       GTK_WIDGET_UNSET_FLAGS (button, GTK_RECEIVES_DEFAULT);
       gtk_misc_set_padding (GTK_MISC (GTK_BIN (button)->child), 2, 0);
       gtk_signal_connect (GTK_OBJECT (button), "clicked",
@@ -3093,7 +3093,7 @@
 		     GTK_WIN_POS_NONE,
 		     FALSE, TRUE, FALSE,
 
-		     _("Import"), palette_import_import_callback,
+		     Q_("!palette_import_dialog-button!Import"), palette_import_import_callback,
 		     palette, NULL, NULL, FALSE, FALSE,
 		     _("Close"), palette_import_close_callback,
 		     palette, NULL, NULL, TRUE, TRUE,
@@ -3107,7 +3107,7 @@
   gtk_widget_show (hbox);
 
   /*  The "Import" frame  */
-  frame = gtk_frame_new (_("Import"));
+  frame = gtk_frame_new (Q_("!palette_import_dialog-frame!Import"));
   gtk_box_pack_start (GTK_BOX (hbox), frame, TRUE, TRUE, 0);
   gtk_widget_show (frame);
 
@@ -3230,7 +3230,7 @@
   gtk_box_pack_start (GTK_BOX (vbox), image, FALSE, FALSE, 0);
   gtk_widget_show (image);
 
-  button = import_dialog->select = gtk_button_new_with_label (_("Select"));
+  button = import_dialog->select = gtk_button_new_with_label (Q_("!palette_import-button!Select"));
   GTK_WIDGET_UNSET_FLAGS (button, GTK_RECEIVES_DEFAULT);
   gtk_signal_connect (GTK_OBJECT (button), "clicked", 
 		      GTK_SIGNAL_FUNC (palette_import_select_grad_callback),
diff -ru /home/cyba/gcvs/gimp/app/preferences_dialog.c gimp/app/preferences_dialog.c
--- /home/cyba/gcvs/gimp/app/preferences_dialog.c	Sun Dec  3 20:16:59 2000
+++ gimp/app/preferences_dialog.c	Wed Dec  6 21:39:01 2000
@@ -2109,9 +2109,9 @@
 
   /* Interface / Tool Options */
   vbox = prefs_notebook_append_page (GTK_NOTEBOOK (notebook),
-					  _("Tool Options"),
+					  Q_("!preferences_interface!Tool Options"),
 					  GTK_CTREE (ctree),
-					  _("Tool Options"),
+					  Q_("!preferences_interface!Tool Options"),
 					  "dialogs/preferences/interface.html#tool_options",
 					  top_insert,
 					  &child_insert,
@@ -2246,7 +2246,7 @@
 
 			   _("Nearest Neighbor (Fast)"),
 			   (gpointer) NEAREST_NEIGHBOR_INTERPOLATION, NULL,
-			   _("Linear"),
+			   Q_("!interpolation_type!Linear"),
 			   (gpointer) LINEAR_INTERPOLATION, NULL,
 			   _("Cubic (Slow)"),
 			   (gpointer) CUBIC_INTERPOLATION, NULL,
diff -ru /home/cyba/gcvs/gimp/app/tools.c gimp/app/tools.c
--- /home/cyba/gcvs/gimp/app/tools.c	Sun Dec  3 20:17:06 2000
+++ gimp/app/tools.c	Wed Dec  6 21:39:01 2000
@@ -1534,7 +1534,7 @@
 
   /*  The shell and main vbox  */
   options_shell =
-    gimp_dialog_new (_("Tool Options"), "tool_options",
+    gimp_dialog_new (Q_("!dialog_title!Tool Options"), "tool_options",
 		     tools_help_func,
 		     "dialogs/tool_options.html",
 		     GTK_WIN_POS_NONE,
diff -ru /home/cyba/gcvs/gimp/modules/colorsel_water.c gimp/modules/colorsel_water.c
--- /home/cyba/gcvs/gimp/modules/colorsel_water.c	Thu Nov 23 12:38:51 2000
+++ gimp/modules/colorsel_water.c	Wed Dec  6 21:39:01 2000
@@ -626,7 +626,7 @@
   bbox = gtk_vbutton_box_new ();
   gtk_box_pack_end (GTK_BOX (hbox2), bbox, FALSE, FALSE, 0);
 
-  button = gtk_button_new_with_label (_("New"));
+  button = gtk_button_new_with_label (Q_("!color-button!New"));
   gtk_container_add (GTK_CONTAINER (bbox), button);
   gtk_signal_connect (GTK_OBJECT (button), "clicked",
 		      (GtkSignalFunc) new_color_callback,
