gettext implementation for glib



We have a problem with the win32 port. Windows (and other OSes) doesn't 
support gettext, and gettext is GPL, so we can't use it. There is actually 
a copy of the gettext sources in glibc that are LGPL, but RMS doesn't like 
people using it at other places.

Therefore, may i present: ... <glib/gint.h>, with g_gettext() and friends.

Here is an inital patch. I've tested it and it seems to work. It is 
lacking some M4 magic to find libintl.h and if found add 
GLIB_HAS_LIBINTL_H to glibconfig.h. I'm far to scared of M4 to dare attack 
that.

/ Alex

Index: glib/Makefile.am
===================================================================
RCS file: /cvs/gnome/glib/glib/Makefile.am,v
retrieving revision 1.89
diff -u -p -r1.89 Makefile.am
--- glib/Makefile.am	2001/07/23 11:44:04	1.89
+++ glib/Makefile.am	2001/08/03 05:26:26
@@ -22,6 +22,7 @@ libglib_1_3_la_SOURCES = 	\
 	gfileutils.c		\
 	ghash.c			\
 	ghook.c			\
+	gintl.c			\
 	giochannel.c    	\
 	glibintl.h		\
 	glist.c			\
@@ -83,6 +84,7 @@ glibsubinclude_HEADERS =   \
 	gfileutils.h	\
 	ghash.h		\
 	ghook.h		\
+	gintl.h		\
 	giochannel.h	\
 	glist.h		\
 	gmacros.h	\
Index: glib/glibintl.h
===================================================================
RCS file: /cvs/gnome/glib/glib/glibintl.h,v
retrieving revision 1.2
diff -u -p -r1.2 glibintl.h
--- glib/glibintl.h	2001/01/17 04:31:20	1.2
+++ glib/glibintl.h	2001/08/03 05:26:26
@@ -7,7 +7,7 @@
 
 gchar *_glib_gettext (const gchar *str);
 
-#include <libintl.h>
+#include "gintl.h"
 #define _(String) _glib_gettext(String)
 
 #ifdef gettext_noop
Index: glib/gutils.c
===================================================================
RCS file: /cvs/gnome/glib/glib/gutils.c,v
retrieving revision 1.97
diff -u -p -r1.97 gutils.c
--- glib/gutils.c	2001/06/30 16:54:32	1.97
+++ glib/gutils.c	2001/08/03 05:26:34
@@ -1095,11 +1095,11 @@ _glib_gettext (const gchar *str)
 
   if (!_glib_gettext_initialized)
     {
-      bindtextdomain(GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
+      g_bindtextdomain(GETTEXT_PACKAGE, GLIB_LOCALE_DIR);
       _glib_gettext_initialized = TRUE;
     }
   
-  return dgettext (GETTEXT_PACKAGE, str);
+  return g_dgettext (GETTEXT_PACKAGE, str);
 }
 
 #endif /* ENABLE_NLS */
Index: glib/gutils.h
===================================================================
RCS file: /cvs/gnome/glib/glib/gutils.h,v
retrieving revision 1.10
diff -u -p -r1.10 gutils.h
--- glib/gutils.h	2001/07/10 22:37:07	1.10
+++ glib/gutils.h	2001/08/03 05:26:34
@@ -166,7 +166,7 @@ gchar*                g_path_get_basenam
 gchar*                g_path_get_dirname   (const gchar *file_name);
 
 /* Get the codeset for the current locale */
-/* gchar * g_get_codeset    (void); */
+gchar * g_get_codeset    (void);
 
 /* return the environment string for the variable. The returned memory
  * must not be freed. */
--- /dev/null	Thu Aug 24 11:00:32 2000
+++ glib/gintl.c	Fri Aug  3 01:24:31 2001
@@ -0,0 +1,642 @@
+/* gintl.c - i18n functions
+ *
+ *  Copyright 2001 Alexander Larsson <alexl redhat com>.
+ *
+ * GLib is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * GLib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with GLib; see the file COPYING.LIB.  If not,
+ * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ *   Boston, MA 02111-1307, USA.
+ */
+
+#include "config.h"
+
+#include <string.h>
+#include <errno.h>
+
+#include "glib.h"
+#include "gintl.h"
+
+/* FIXME: fix this on win32 and maybe use LOCALEDIR */
+#define DEFAULT_LOCALE_DIR "/usr/share/locale"
+
+#define SWAB(_val, _do_swab) ((_do_swab)?(GUINT32_SWAP_LE_BE (_val)):(_val))
+
+static char *current_domain = NULL;
+
+static GHashTable *domains = NULL;
+
+typedef struct {
+  guint32 length;
+  guint32 offset;
+} string_data;
+
+typedef struct {
+  char *base;
+
+  gboolean need_swab;
+  int num_strings;
+  string_data *original;
+  string_data *translated;
+  
+  guint32 *hash;
+  guint32 hash_len;
+
+  gchar *codeset;
+} GettextCatalog;
+
+typedef struct {
+  char *name;
+  char *directory;
+  GettextCatalog *cat;
+  gchar *out_codeset;
+  GIConv iconv;
+
+  GHashTable *cache;
+} Domain;
+
+static const char     *get_locale        (void);
+static Domain         *get_domain        (const char     *domain_name,
+					  gboolean        create);
+static GettextCatalog *load_catalog      (Domain         *domain);
+static GettextCatalog *get_catalog       (Domain         *domain);
+static string_data *   lookup_message    (GettextCatalog *cat,
+					  const char     *message);
+static char           *translate_message (Domain         *domain,
+					  const char     *message);
+static void            free_catalog      (GettextCatalog *catalog);
+static void            free_domain       (Domain         *domain);
+
+
+/* The hash function was taken from glibc.
+ *  Copyright (C) 1995, 1997, 1998, 2000 Free Software Foundation, Inc.
+ */
+#define HASHWORDBITS 32
+static inline guint32
+hash_string (const char *str)
+{
+  guint hval, g;
+
+  /* Compute the hash value for the given string.  */
+  hval = 0;
+  while (*str != '\0')
+    {
+      hval <<= 4;
+      hval += (guint32) *str++;
+      g = hval & ((guint32) 0xf << (HASHWORDBITS - 4));
+      if (g != 0)
+	{
+	  hval ^= g >> (HASHWORDBITS - 8);
+	  hval ^= g;
+	}
+    }
+  return hval;
+}
+
+char *
+g_textdomain (const char *domain_name)
+{
+  if (domain_name)
+    {
+      g_free (current_domain);
+      current_domain = g_strdup (domain_name);
+    }
+  else if (!current_domain)
+    current_domain = g_strdup ("messages"); /* Default domain */
+  
+  return current_domain;
+}
+
+char *
+g_gettext (const char *msgid)
+{
+  return g_dgettext (g_textdomain (NULL), msgid);
+}
+
+char *
+g_dgettext (const char *domain_name,
+	    const char *msgid)
+{
+  Domain *domain;
+  char *translation;
+  
+  g_return_val_if_fail (msgid != NULL, NULL);
+  
+  if (domain_name == NULL)
+    return (char *)msgid;
+  
+  domain = get_domain (domain_name, TRUE);
+
+  translation = translate_message (domain, msgid);
+
+  if (translation == NULL)
+    return (char *)msgid;
+
+  
+  
+  return translation;
+}
+
+char *
+g_bindtextdomain (const char *domain_name,
+		  const char *dir_name)
+{
+  Domain *domain;
+  
+  g_assert (domain_name != NULL);
+  
+  domain = get_domain (domain_name, TRUE);
+
+  if (dir_name)
+    {
+      g_free (domain->directory);
+      domain->directory = g_strdup (dir_name);
+    }
+  
+  return domain->directory;
+}
+
+char *
+g_bind_textdomain_codeset (const char *domain_name,
+			   const char *codeset)
+{
+  Domain *domain;
+  
+  g_assert (domain_name != NULL);
+  
+  domain = get_domain (domain_name, TRUE);
+
+  if (codeset)
+    {
+      g_free (domain->out_codeset);
+      domain->out_codeset = g_strdup (codeset);
+
+      if (domain->iconv &&
+	  domain->iconv != (GIConv)-1)
+	g_iconv_close (domain->iconv);
+      domain->iconv = 0;
+    }
+  
+  return domain->out_codeset;
+}
+
+static GettextCatalog *
+get_catalog (Domain *domain)
+{
+  if (domain->cat)
+    return domain->cat;
+
+  domain->cat = load_catalog (domain);
+  
+  return domain->cat;
+}
+
+static const char *
+get_locale (void)
+{
+  const char *locale;
+  
+  locale = g_getenv ("LANG");
+  if (locale)
+    return locale;
+
+  return "en_US";
+}
+
+static Domain *
+get_domain (const char *domain_name, gboolean create)
+{
+  Domain *domain;
+  const char *codeset;
+
+  if (domains == NULL)
+    domains = g_hash_table_new_full (g_str_hash,
+				     g_str_equal,
+				     NULL,
+				     (GDestroyNotify)free_domain);
+
+  domain = g_hash_table_lookup (domains, domain_name);
+
+  if (domain)
+    return domain;
+
+  if (create)
+    {
+      domain = g_new0 (Domain, 1);
+      domain->name = g_strdup (domain_name);
+      domain->directory = g_strdup (DEFAULT_LOCALE_DIR);
+      g_get_charset (&codeset);
+      domain->out_codeset = g_strdup (codeset);
+      g_hash_table_insert (domains, domain->name, domain);
+      
+      domain->cache = g_hash_table_new_full (g_str_hash,
+					     g_str_equal,
+					     NULL,
+					     g_free);
+
+      return domain;
+    }
+
+  return NULL;
+}
+
+static GettextCatalog *
+load_mo_file (char *file)
+{
+  char *contents;
+  guint32 *words;
+  guint32 offs;
+  gsize len;
+  GettextCatalog *cat;
+  string_data *str;
+  char *charset;
+  size_t length;
+  
+  if (!g_file_get_contents (file,
+			    &contents,
+			    &len, NULL))
+    return NULL;
+
+  if (len < 28)
+    {
+      /* Can't be a .mo file. To small */
+      g_free (contents);
+      return NULL;
+    }
+  
+  cat = g_new (GettextCatalog, 1);
+  cat->base = contents;
+
+  words = (guint32 *)contents;
+  
+  if (words[0] == 0x950412de)
+    cat->need_swab = FALSE;
+  else if (words[0] == 0xde120495)
+    cat->need_swab = FALSE;
+  else
+    {
+      /* Wrong magic number */
+      g_free (contents);
+      g_free (cat);
+      return NULL;
+    }
+
+  if ( (words[1] != 0) /* Wrong version number */ ||
+       (words[6] == 0) /* No hashtable */)
+    {
+      g_free (contents);
+      g_free (cat);
+      return NULL;
+    }
+
+  cat->num_strings = SWAB (words[2], cat->need_swab);
+  
+  offs = SWAB (words[3], cat->need_swab);
+  cat->original = (string_data *)(contents + offs);
+  
+  offs = SWAB (words[4], cat->need_swab);
+  cat->translated = (string_data *)(contents + offs);
+  
+  cat->hash_len = SWAB (words[5], cat->need_swab);
+
+  offs = SWAB (words[6], cat->need_swab);
+  cat->hash = (guint32 *)(contents + offs);
+
+  str = lookup_message (cat, "");
+
+  charset = cat->base + SWAB (str->offset, cat->need_swab);
+
+  charset = strstr (charset, "charset=");
+  if (charset)
+    {
+      charset += strlen ("charset=");
+      length = strcspn (charset, " \t\n");
+
+      cat->codeset = g_malloc (length + 1);
+      memcpy (cat->codeset, charset, length);
+      cat->codeset[length] = 0;
+    }
+  else
+    {
+      const char *codeset;
+      g_get_charset (&codeset);
+      cat->codeset = g_strdup (codeset);
+    }
+
+  return cat;
+}
+
+static GList *
+generalize_locale (const char *locale)
+{
+  GList *list = NULL;
+  char *str;
+  char *modifier;
+  char *codeset;
+  char *territory;
+  char *language;
+
+  /* LOCALE can consist of up to four recognized parts for the XPG syntax:
+
+		language[_territory[ codeset]][ modifier]
+
+     Beside the first part all of them are allowed to be missing.  If
+     the full specified locale is not found, the less specific one are
+     looked for.  The various parts will be stripped off according to
+     the following order:
+		(1) codeset
+		(2) territory
+		(3) modifier
+  */
+
+  language = g_strdup (locale);
+
+  modifier = strrchr (language, '@');
+  if (modifier)
+    {
+      *modifier++ = 0;
+    }
+  
+  codeset = strrchr (language, '.');
+  if (codeset)
+    {
+      *codeset++ = 0;
+    }
+  
+  territory = strrchr (language, '_');
+  if (territory)
+    {
+      *territory++ = 0;
+    }
+
+  if (codeset)
+    {
+      str = g_strconcat (language,
+			 (territory)?"_":"",
+			 (territory)?territory:"",
+			 ".",
+			 codeset,
+			 (modifier)?"@":"",
+			 (modifier)?modifier:"",
+			 NULL);
+      list = g_list_append (list, str);
+    }
+  
+  if (territory)
+    {
+      str = g_strconcat (language,
+			 "_",
+			 territory,
+			 (modifier)?"@":"",
+			 (modifier)?modifier:"",
+			 NULL);
+      list = g_list_append (list, str);
+    }
+  
+  if (modifier)
+    {
+      str = g_strconcat (language,
+			 "@",
+			 modifier,
+			 NULL);
+      list = g_list_append (list, str);
+    }
+
+  list = g_list_append (list, g_strdup (language));
+
+  g_free (language);
+
+  return list;
+}
+
+
+static GettextCatalog *
+load_catalog (Domain *domain)
+{
+  char *dir;
+  const char *locale;
+  char *file;
+  GettextCatalog *cat;
+  GList *langs, *l;
+
+  dir = domain->directory;
+  locale = get_locale ();
+  langs = generalize_locale (locale);
+  
+  l = langs;
+  while (l)
+    {
+      char *lang = l->data;;
+      
+      file = g_strconcat (domain->directory, "/", lang, "/LC_MESSAGES/", domain->name, ".mo", NULL);
+
+      if (g_file_test (file, G_FILE_TEST_IS_REGULAR | G_FILE_TEST_EXISTS))
+	break;
+      
+      g_free (file);
+      
+      file = NULL;
+
+      l = l->next;
+    }
+
+  if (file == NULL)
+    return NULL;
+
+  cat = load_mo_file (file);
+  
+  g_free (file);
+  
+  return cat;
+}
+
+static void
+free_domain (Domain *domain)
+{
+  g_free (domain->name);
+  g_free (domain->directory);
+  if (domain->cat)
+    free_catalog (domain->cat);
+  g_free (domain->out_codeset);
+  if (domain->iconv)
+    g_iconv_close (domain->iconv);
+  g_hash_table_destroy (domain->cache);
+  g_free (domain);
+}
+     
+static void
+free_catalog (GettextCatalog *catalog)
+{
+  g_free (catalog->base);
+  g_free (catalog);
+}
+
+static string_data *
+lookup_message (GettextCatalog *cat,
+		const char     *message)
+{
+  guint32 hash;
+  guint32 offs;
+  guint32 len;
+  int i;
+  guint32 entry;
+  int entry_len;
+  char *entry_msg;
+  int increment;
+
+  hash = hash_string (message);
+  i = hash % cat->hash_len;
+  len = strlen (message);
+  increment = 1 + (hash % (cat->hash_len - 2));
+    
+  while (1)
+    {
+      entry = SWAB (cat->hash[i], cat->need_swab);
+
+      if (entry == 0)
+	return NULL;
+      
+      entry_len = SWAB (cat->original[entry-1].length, cat->need_swab);
+      if (entry_len >= len) /* Compare with >= because of plural entries with embedded zeros */
+	{
+	  offs = SWAB (cat->original[entry-1].offset, cat->need_swab);
+	  entry_msg = cat->base + offs;
+	  
+	  if (strcmp (message, entry_msg) == 0)
+	    break;
+	}
+
+      i += increment;
+      if (i >= cat->hash_len)
+	i -= cat->hash_len;
+    }
+  
+  return &cat->translated[entry-1];
+}
+
+
+gchar*
+convert (const gchar *str,
+	 gssize       len,
+	 GIConv       cd)
+{
+  gchar *dest;
+  gchar *outp;
+  const gchar *p;
+  gsize inbytes_remaining;
+  gsize outbytes_remaining;
+  gsize err;
+  gsize outbuf_size;
+  gboolean have_error = FALSE;
+  
+  g_return_val_if_fail (str != NULL, NULL);
+
+  if (len < 0)
+    len = strlen (str);
+
+  p = str;
+  inbytes_remaining = len;
+  outbuf_size = len + 1; /* + 1 for nul in case len == 1 */
+  
+  outbytes_remaining = outbuf_size - 1; /* -1 for nul */
+  outp = dest = g_malloc (outbuf_size);
+
+ again:
+  err = g_iconv (cd, (char **)&p, &inbytes_remaining, &outp, &outbytes_remaining);
+  
+  if (err == (size_t) -1)
+    {
+      switch (errno)
+	{
+	case EINVAL:
+	  /* Incomplete text, do not report an error */
+	  break;
+	case E2BIG:
+	  {
+	    size_t used = outp - dest;
+
+	    outbuf_size *= 2;
+	    dest = g_realloc (dest, outbuf_size);
+		
+	    outp = dest + used;
+	    outbytes_remaining = outbuf_size - used - 1; /* -1 for nul */
+
+	    goto again;
+	  }
+	case EILSEQ:
+	  have_error = TRUE;
+	  break;
+	default:
+	  have_error = TRUE;
+	  break;
+	}
+    }
+
+  *outp = '\0';
+  
+  if ((p - str) != len) 
+      have_error = TRUE;
+
+  if (have_error)
+    {
+      g_free (dest);
+      return NULL;
+    }
+  else
+    return dest;
+}
+
+
+static char *
+translate_message (Domain     *domain,
+		   const char *message)
+{
+  string_data *str;
+  GettextCatalog *cat;
+  char *in, *out;
+
+  out = g_hash_table_lookup (domain->cache, message);
+  if (out)
+    return out;
+  
+  cat = get_catalog (domain);
+  
+  if (cat == NULL)
+    return NULL;
+  
+  str = lookup_message (cat, message);
+
+  if (str == NULL)
+    return NULL;
+
+  in = cat->base + SWAB (str->offset, cat->need_swab);
+
+  if (domain->iconv == 0)
+    {
+      domain->iconv = g_iconv_open (domain->out_codeset,
+				    cat->codeset);
+      
+      if (domain->iconv == (GIConv)-1)
+	g_warning ("Unable to convert between codesets '%s' and '%s'\n",
+		   cat->codeset, domain->out_codeset);
+    }
+
+  if (domain->iconv != (GIConv)-1)
+    out = convert (in, -1, domain->iconv);
+  else
+    out = in;
+
+  g_hash_table_replace (domain->cache,
+			in, out);
+  
+  return out;
+}
--- /dev/null	Thu Aug 24 11:00:32 2000
+++ glib/gintl.h	Thu Aug  2 23:45:46 2001
@@ -0,0 +1,49 @@
+/* gintl.h - gettext functions
+ *
+ * Copyright (C) 1999 Tom Tromey
+ * Copyright (C) 2000 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ */
+#ifndef __G_INTL_H__
+#define __G_INTL_H__
+
+#ifdef GLIB_HAS_LIBINTL_H
+
+#include <libintl.h>
+#define g_textdomain textdomain
+#define g_gettext gettext
+#define g_dgettext dgettext
+#define g_bindtextdomain bindtextdomain
+#define g_bind_textdomain_codeset bind_textdomain_codeset
+
+#else
+
+#define gettext_noop(String) (String)
+
+char *g_textdomain              (const char *domain_name);
+char *g_gettext                 (const char *msgid);
+char *g_dgettext                (const char *domain_name,
+				 const char *msgid);
+char *g_bindtextdomain          (const char *domain_name,
+				 const char *dir_name);
+char *g_bind_textdomain_codeset (const char *domain_name,
+				 const char *codeset);
+
+
+#endif /* GLIB_HAS_LIBINTL_H */
+	
+#endif /* __G_INTL_H__ */





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