gettext implementation for glib
- From: Alex Larsson <alexl redhat com>
- To: <gtk-devel-list gnome org>
- Subject: gettext implementation for glib
- Date: Fri, 3 Aug 2001 01:36:38 -0400 (EDT)
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]