[gnome-initial-setup] Add liblanguage, pulled from gnome-control-center



commit 7f5b7470b80a62ea8624526fb6bcb55cda38962f
Author: Jasper St. Pierre <jstpierre mecheye net>
Date:   Thu Jul 12 15:59:17 2012 -0400

    Add liblanguage, pulled from gnome-control-center

 .gitignore                                         |    2 +
 configure.ac                                       |    8 +
 gnome-initial-setup/Makefile.am                    |    2 +
 gnome-initial-setup/languages/Makefile.am          |   27 +
 gnome-initial-setup/languages/cc-common-language.c |  589 +++++++++
 gnome-initial-setup/languages/cc-common-language.h |   58 +
 .../languages/cc-language-chooser.c                |  343 +++++
 .../languages/cc-language-chooser.h                |   38 +
 gnome-initial-setup/languages/gdm-languages.c      | 1326 ++++++++++++++++++++
 gnome-initial-setup/languages/gdm-languages.h      |   44 +
 gnome-initial-setup/languages/language-chooser.ui  |  114 ++
 gnome-initial-setup/languages/list-languages.c     |   36 +
 gnome-initial-setup/languages/locarchive.h         |   97 ++
 13 files changed, 2684 insertions(+), 0 deletions(-)
---
diff --git a/.gitignore b/.gitignore
index 3a5e806..4bb6108 100644
--- a/.gitignore
+++ b/.gitignore
@@ -54,4 +54,6 @@ gnome-initial-setup/timedated.h
 gnome-initial-setup/setup_resources.c
 gnome-initial-setup/gnome-initial-setup
 
+gnome-initial-setup/languages/list-languages
+
 gnome-initial-setup.desktop
diff --git a/configure.ac b/configure.ac
index fbead81..a48db2a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -31,6 +31,7 @@ PKG_CHECK_MODULES(INITIAL_SETUP,
                   cheese-gtk >= 3.3.5
                   clutter-gtk-1.0
                   clutter-1.0 >= $CLUTTER_REQUIRED_VERSION
+                  fontconfig
                   geoclue
                   gweather-3.0
                   goa-1.0
@@ -47,6 +48,12 @@ if test x$have_pwquality = xyes ; then
   INITIAL_SETUP_LIBS="$INITIAL_SETUP_LIBS -lpwquality"
 fi
 
+# This is a hard-dependency for the region and user-accounts panels
+PKG_CHECK_MODULES(ISOCODES, iso-codes)
+
+AC_DEFINE_UNQUOTED([ISO_CODES_PREFIX],["`$PKG_CONFIG --variable=prefix iso-codes`"],[ISO codes prefix])
+ISO_CODES=iso-codes
+
 AC_SUBST(INITIAL_SETUP_CFLAGS)
 AC_SUBST(INITIAL_SETUP_LIBS)
 
@@ -67,6 +74,7 @@ AC_CONFIG_FILES([
 Makefile
 data/Makefile
 gnome-initial-setup/Makefile
+gnome-initial-setup/languages/Makefile
 po/Makefile.in
 ])
 AC_OUTPUT
diff --git a/gnome-initial-setup/Makefile.am b/gnome-initial-setup/Makefile.am
index f78ddc3..7db2c47 100644
--- a/gnome-initial-setup/Makefile.am
+++ b/gnome-initial-setup/Makefile.am
@@ -1,3 +1,5 @@
+SUBDIRS = languages
+
 uidir = $(datadir)/gnome-initial-setup
 
 # GNOMECC_DATA_DIR is used in tz.c
diff --git a/gnome-initial-setup/languages/Makefile.am b/gnome-initial-setup/languages/Makefile.am
new file mode 100644
index 0000000..0aa166e
--- /dev/null
+++ b/gnome-initial-setup/languages/Makefile.am
@@ -0,0 +1,27 @@
+noinst_LTLIBRARIES = liblanguage.la
+noinst_PROGRAMS = list-languages
+
+AM_CPPFLAGS =						\
+	$(INITIAL_SETUP_CFLAGS)				\
+	-DDATADIR=\""$(datadir)"\"			\
+	-DUIDIR=\""$(pkgdatadir)/ui"\"			\
+	-DLIBLOCALEDIR=\""$(prefix)/lib/locale"\"       \
+	-DGNOMELOCALEDIR=\""$(datadir)/locale"\"        \
+	-DUM_PIXMAP_DIR=\""$(pkgdatadir)/pixmaps"\"
+
+liblanguage_la_SOURCES =		\
+	gdm-languages.h 		\
+	gdm-languages.c 		\
+	locarchive.h			\
+	cc-common-language.c		\
+	cc-common-language.h		\
+	cc-language-chooser.c		\
+	cc-language-chooser.h
+
+liblanguage_la_LIBADD = 		\
+	$(INITIAL_SETUP_LIBS)
+
+liblanguage_la_LDFLAGS = -export_dynamic -avoid-version -module -no-undefined
+
+list_languages_SOURCES = list-languages.c
+list_languages_LDADD = liblanguage.la
diff --git a/gnome-initial-setup/languages/cc-common-language.c b/gnome-initial-setup/languages/cc-common-language.c
new file mode 100644
index 0000000..28973a8
--- /dev/null
+++ b/gnome-initial-setup/languages/cc-common-language.c
@@ -0,0 +1,589 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gtk/gtk.h>
+
+#include <fontconfig/fontconfig.h>
+
+#include "cc-common-language.h"
+
+#include "gdm-languages.h"
+
+static gint
+cc_common_language_sort_languages (GtkTreeModel *model,
+				   GtkTreeIter  *a,
+				   GtkTreeIter  *b,
+				   gpointer      data)
+{
+        char *ca, *cb;
+        char *la, *lb;
+        gboolean sa, ula;
+        gboolean sb, ulb;
+        gint result;
+
+	gtk_tree_model_get (model, a,
+			    LOCALE_COL, &ca,
+			    DISPLAY_LOCALE_COL, &la,
+			    SEPARATOR_COL, &sa,
+			    USER_LANGUAGE, &ula,
+			    -1);
+	gtk_tree_model_get (model, b,
+			    LOCALE_COL, &cb,
+			    DISPLAY_LOCALE_COL, &lb,
+			    SEPARATOR_COL, &sb,
+			    USER_LANGUAGE, &ulb,
+			    -1);
+
+	/* Sort before and after separator first */
+	if (sa && sb)
+		return 0;
+	if (sa)
+		return ulb ? 1 : -1;
+	if (sb)
+		return ula ? -1 : 1;
+
+	/* Sort user-languages first */
+	if (ula != ulb) {
+		if (ula)
+			return -1;
+		else
+			return 1;
+	}
+
+        if (!ca)
+                result = 1;
+        else if (!cb)
+                result = -1;
+        else
+                result = strcmp (la, lb);
+
+        g_free (ca);
+        g_free (cb);
+        g_free (la);
+        g_free (lb);
+
+        return result;
+}
+
+static gboolean
+iter_for_language (GtkTreeModel *model,
+                   const gchar  *lang,
+                   GtkTreeIter  *iter,
+                   gboolean      region)
+{
+        char *l;
+        char *name;
+        char *language;
+
+        gtk_tree_model_get_iter_first (model, iter);
+        do {
+                gtk_tree_model_get (model, iter, LOCALE_COL, &l, -1);
+                if (g_strcmp0 (l, lang) == 0) {
+                        g_free (l);
+                        return TRUE;
+                }
+                g_free (l);
+        } while (gtk_tree_model_iter_next (model, iter));
+
+        name = gdm_normalize_language_name (lang);
+        if (name != NULL) {
+                if (region) {
+                        language = gdm_get_region_from_name (name, NULL);
+                }
+                else {
+                        language = gdm_get_language_from_name (name, NULL);
+                }
+
+                gtk_list_store_insert_with_values (GTK_LIST_STORE (model),
+                                                   iter,
+                                                   -1,
+                                                   LOCALE_COL, name,
+                                                   DISPLAY_LOCALE_COL, language,
+                                                   -1);
+                g_free (name);
+                g_free (language);
+                return TRUE;
+        }
+
+        return FALSE;
+}
+
+gboolean
+cc_common_language_get_iter_for_language (GtkTreeModel *model,
+                                          const gchar  *lang,
+                                          GtkTreeIter  *iter)
+{
+  return iter_for_language (model, lang, iter, FALSE);
+}
+
+gboolean
+cc_common_language_get_iter_for_region (GtkTreeModel *model,
+                                        const gchar  *lang,
+                                        GtkTreeIter  *iter)
+{
+  return iter_for_language (model, lang, iter, TRUE);
+}
+
+gboolean
+cc_common_language_has_font (const gchar *locale)
+{
+        const FcCharSet *charset;
+        FcPattern       *pattern;
+        FcObjectSet     *object_set;
+        FcFontSet       *font_set;
+        gchar           *language_code;
+        gboolean         is_displayable;
+
+        is_displayable = FALSE;
+        pattern = NULL;
+        object_set = NULL;
+        font_set = NULL;
+
+        if (!gdm_parse_language_name (locale, &language_code, NULL, NULL, NULL))
+                return FALSE;
+
+        charset = FcLangGetCharSet ((FcChar8 *) language_code);
+        if (!charset) {
+                /* fontconfig does not know about this language */
+                is_displayable = TRUE;
+        }
+        else {
+                /* see if any fonts support rendering it */
+                pattern = FcPatternBuild (NULL, FC_LANG, FcTypeString, language_code, NULL);
+
+                if (pattern == NULL)
+                        goto done;
+
+                object_set = FcObjectSetCreate ();
+
+                if (object_set == NULL)
+                        goto done;
+
+                font_set = FcFontList (NULL, pattern, object_set);
+
+                if (font_set == NULL)
+                        goto done;
+
+                is_displayable = (font_set->nfont > 0);
+        }
+
+ done:
+        if (font_set != NULL)
+                FcFontSetDestroy (font_set);
+
+        if (object_set != NULL)
+                FcObjectSetDestroy (object_set);
+
+        if (pattern != NULL)
+                FcPatternDestroy (pattern);
+
+        g_free (language_code);
+
+        return is_displayable;
+}
+
+typedef struct
+{
+  GtkListStore  *store;
+  GHashTable    *user_langs;
+  gchar        **languages;
+  gboolean       regions;
+  gint           position;
+} AsyncLangData;
+
+static void
+async_lang_data_free (AsyncLangData *data)
+{
+  g_object_unref (data->store);
+  g_hash_table_unref (data->user_langs);
+  g_strfreev (data->languages);
+  g_free (data);
+}
+
+static gboolean
+add_one_language (gpointer d)
+{
+  AsyncLangData *data = d;
+  char *name;
+  char *language;
+  GtkTreeIter iter;
+
+  if (data->languages[data->position] == NULL) {
+    /* we are done */
+    async_lang_data_free (data);
+    return FALSE;
+  }
+
+  name = gdm_normalize_language_name (data->languages[data->position]);
+  if (g_hash_table_lookup (data->user_langs, name) != NULL) {
+    g_free (name);
+    goto next;
+  }
+
+  if (!cc_common_language_has_font (data->languages[data->position])) {
+    g_free (name);
+    goto next;
+  }
+
+  if (data->regions) {
+    language = gdm_get_region_from_name (name, NULL);
+  }
+  else {
+    language = gdm_get_language_from_name (name, NULL);
+  }
+  if (!language) {
+    g_debug ("Ignoring '%s' as a locale, because we couldn't figure the language name", name);
+    g_free (name);
+    goto next;
+  }
+
+  /* Add separator between initial languages and new additions */
+  if (g_object_get_data (G_OBJECT (data->store), "needs-separator")) {
+    GtkTreeIter iter;
+
+    gtk_list_store_insert_with_values (GTK_LIST_STORE (data->store),
+                                       &iter,
+                                       -1,
+                                       LOCALE_COL, NULL,
+                                       DISPLAY_LOCALE_COL, "Don't show",
+                                       SEPARATOR_COL, TRUE,
+                                       USER_LANGUAGE, FALSE,
+                                       -1);
+    g_object_set_data (G_OBJECT (data->store), "needs-separator", NULL);
+  }
+
+  gtk_list_store_insert_with_values (data->store,
+                                     &iter,
+                                     -1,
+                                     LOCALE_COL, name,
+                                     DISPLAY_LOCALE_COL, language,
+                                     -1);
+
+  g_free (name);
+  g_free (language);
+
+ next:
+  data->position++;
+
+  return TRUE;
+}
+
+guint
+cc_common_language_add_available_languages (GtkListStore *store,
+                                            gboolean      regions,
+                                            GHashTable   *user_langs)
+{
+  AsyncLangData *data;
+
+  data = g_new0 (AsyncLangData, 1);
+
+  data->store = g_object_ref (store);
+  data->user_langs = g_hash_table_ref (user_langs);
+  data->languages = gdm_get_all_language_names ();
+  data->regions = regions;
+  data->position = 0;
+
+  return gdk_threads_add_idle (add_one_language, data);
+}
+
+gchar *
+cc_common_language_get_current_language (void)
+{
+        gchar *language;
+        const gchar *locale;
+
+        locale = (const gchar *) setlocale (LC_MESSAGES, NULL);
+        if (locale)
+                language = gdm_normalize_language_name (locale);
+        else
+                language = NULL;
+
+        return language;
+}
+
+static void
+languages_foreach_cb (gpointer key,
+		      gpointer value,
+		      gpointer user_data)
+{
+	GtkListStore *store = (GtkListStore *) user_data;
+	const char *locale = (const char *) key;
+	const char *display_locale = (const char *) value;
+	GtkTreeIter iter;
+
+        gtk_list_store_insert_with_values (store,
+                                           &iter,
+                                           -1,
+                                           LOCALE_COL, locale,
+                                           DISPLAY_LOCALE_COL, display_locale,
+                                           SEPARATOR_COL, FALSE,
+                                           USER_LANGUAGE, TRUE,
+                                           -1);
+}
+
+static gboolean
+separator_func (GtkTreeModel *model,
+		GtkTreeIter  *iter,
+		gpointer      data)
+{
+	gboolean is_sep;
+
+	gtk_tree_model_get (model, iter,
+			    SEPARATOR_COL, &is_sep,
+			    -1);
+
+	return is_sep;
+}
+
+void
+cc_common_language_setup_list (GtkWidget    *treeview,
+			       GHashTable   *initial)
+{
+	GtkCellRenderer *cell;
+	GtkTreeViewColumn *column;
+	GtkListStore *store;
+
+        cell = gtk_cell_renderer_text_new ();
+	g_object_set (cell,
+		      "width-chars", 40,
+		      "ellipsize", PANGO_ELLIPSIZE_END,
+		      NULL);
+        column = gtk_tree_view_column_new_with_attributes (NULL, cell, "text", DISPLAY_LOCALE_COL, NULL);
+        gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+        store = gtk_list_store_new (NUM_COLS, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_BOOLEAN, G_TYPE_BOOLEAN);
+        gtk_tree_sortable_set_default_sort_func (GTK_TREE_SORTABLE (store),
+                                                 cc_common_language_sort_languages, NULL, NULL);
+        gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (store),
+                                              GTK_TREE_SORTABLE_DEFAULT_SORT_COLUMN_ID,
+                                              GTK_SORT_ASCENDING);
+        gtk_tree_view_set_row_separator_func (GTK_TREE_VIEW (treeview),
+					      separator_func,
+					      NULL, NULL);
+
+        gtk_tree_view_set_model (GTK_TREE_VIEW (treeview), GTK_TREE_MODEL (store));
+
+
+        /* Add languages from the initial hashtable */
+        g_hash_table_foreach (initial, (GHFunc) languages_foreach_cb, store);
+
+        /* Mark the need for a separator if we had any languages added */
+        if (initial != NULL &&
+            g_hash_table_size (initial) > 0) {
+		g_object_set_data (G_OBJECT (store), "needs-separator", GINT_TO_POINTER (TRUE));
+	}
+}
+
+void
+cc_common_language_select_current_language (GtkTreeView *treeview)
+{
+	GtkTreeModel *model;
+	GtkTreeIter iter;
+	gboolean cont;
+	char *lang;
+
+	lang = cc_common_language_get_current_language ();
+	model = gtk_tree_view_get_model (treeview);
+	cont = gtk_tree_model_get_iter_first (model, &iter);
+	while (cont) {
+		char *locale;
+
+		gtk_tree_model_get (model, &iter,
+				    LOCALE_COL, &locale,
+				    -1);
+		if (locale != NULL &&
+		    g_str_equal (locale, lang)) {
+			GtkTreeSelection *selection;
+			selection = gtk_tree_view_get_selection (treeview);
+			gtk_tree_selection_select_iter (selection, &iter);
+			g_free (locale);
+			break;
+		}
+		g_free (locale);
+
+		cont = gtk_tree_model_iter_next (model, &iter);
+	}
+	g_free (lang);
+}
+
+static void
+add_other_users_language (GHashTable *ht)
+{
+        GVariant *variant;
+        GVariantIter *vi;
+        GError *error = NULL;
+        const char *str;
+        GDBusProxy *proxy;
+
+        proxy = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                               G_DBUS_PROXY_FLAGS_NONE,
+                                               NULL,
+                                               "org.freedesktop.Accounts",
+                                               "/org/freedesktop/Accounts",
+                                               "org.freedesktop.Accounts",
+                                               NULL,
+                                               NULL);
+
+        if (proxy == NULL)
+                return;
+
+        variant = g_dbus_proxy_call_sync (proxy,
+                                          "ListCachedUsers",
+                                          NULL,
+                                          G_DBUS_CALL_FLAGS_NONE,
+                                          -1,
+                                          NULL,
+                                          &error);
+        if (variant == NULL) {
+                g_warning ("Failed to list existing users: %s", error->message);
+                g_error_free (error);
+                g_object_unref (proxy);
+                return;
+        }
+        g_variant_get (variant, "(ao)", &vi);
+        while (g_variant_iter_loop (vi, "o", &str)) {
+                GDBusProxy *user;
+                GVariant *props;
+                const char *lang;
+                char *name;
+                char *language;
+
+                user = g_dbus_proxy_new_for_bus_sync (G_BUS_TYPE_SYSTEM,
+                                                      G_DBUS_PROXY_FLAGS_NONE,
+                                                      NULL,
+                                                      "org.freedesktop.Accounts",
+                                                      str,
+                                                      "org.freedesktop.Accounts.User",
+                                                      NULL,
+                                                      &error);
+                if (user == NULL) {
+                        g_warning ("Failed to get proxy for user '%s': %s",
+                                   str, error->message);
+                        g_error_free (error);
+                        error = NULL;
+                        continue;
+                }
+                props = g_dbus_proxy_get_cached_property (user, "Language");
+                lang = g_variant_get_string (props, NULL);
+                if (lang != NULL && *lang != '\0' &&
+                    cc_common_language_has_font (lang) &&
+                    gdm_language_has_translations (lang)) {
+                        name = gdm_normalize_language_name (lang);
+                        if (!g_hash_table_lookup (ht, name)) {
+                                language = gdm_get_language_from_name (name, NULL);
+                                g_hash_table_insert (ht, name, language);
+                        }
+                        else {
+                                g_free (name);
+                        }
+                }
+                g_variant_unref (props);
+                g_object_unref (user);
+        }
+        g_variant_iter_free (vi);
+        g_variant_unref (variant);
+
+        g_object_unref (proxy);
+}
+
+GHashTable *
+cc_common_language_get_initial_languages (void)
+{
+        GHashTable *ht;
+        char *name;
+        char *language;
+
+        ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+        /* Add some common languages first */
+        g_hash_table_insert (ht, g_strdup ("en_US.utf8"), g_strdup (_("English")));
+        if (gdm_language_has_translations ("en_GB"))
+                g_hash_table_insert (ht, g_strdup ("en_GB.utf8"), g_strdup (_("British English")));
+        if (gdm_language_has_translations ("de") ||
+            gdm_language_has_translations ("de_DE"))
+                g_hash_table_insert (ht, g_strdup ("de_DE.utf8"), g_strdup (_("German")));
+        if (gdm_language_has_translations ("fr") ||
+            gdm_language_has_translations ("fr_FR"))
+                g_hash_table_insert (ht, g_strdup ("fr_FR.utf8"), g_strdup (_("French")));
+        if (gdm_language_has_translations ("es") ||
+            gdm_language_has_translations ("es_ES"))
+                g_hash_table_insert (ht, g_strdup ("es_ES.utf8"), g_strdup (_("Spanish")));
+        if (gdm_language_has_translations ("zh_CN"))
+                g_hash_table_insert (ht, g_strdup ("zh_CN.utf8"), g_strdup (_("Chinese (simplified)")));
+
+        /* Add the languages used by other users on the system */
+        add_other_users_language (ht);
+
+        /* Add current locale */
+        name = cc_common_language_get_current_language ();
+        if (g_hash_table_lookup (ht, name) == NULL) {
+                language = gdm_get_language_from_name (name, NULL);
+                g_hash_table_insert (ht, name, language);
+        } else {
+                g_free (name);
+        }
+
+        return ht;
+}
+
+GHashTable *
+cc_common_language_get_initial_regions (const gchar *lang)
+{
+        GHashTable *ht;
+        char *language;
+        gchar **langs;
+        gint i;
+
+        ht = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+#if 0
+        /* Add some common regions */
+        g_hash_table_insert (ht, g_strdup ("en_US.utf8"), g_strdup (_("United States")));
+        g_hash_table_insert (ht, g_strdup ("de_DE.utf8"), g_strdup (_("Germany")));
+        g_hash_table_insert (ht, g_strdup ("fr_FR.utf8"), g_strdup (_("France")));
+        g_hash_table_insert (ht, g_strdup ("es_ES.utf8"), g_strdup (_("Spain")));
+        g_hash_table_insert (ht, g_strdup ("zh_CN.utf8"), g_strdup (_("China")));
+#endif
+
+        gdm_parse_language_name (lang, &language, NULL, NULL, NULL);
+        langs = gdm_get_all_language_names ();
+        for (i = 0; langs[i]; i++) {
+                gchar *l, *s;
+                gdm_parse_language_name (langs[i], &l, NULL, NULL, NULL);
+                if (g_strcmp0 (language, l) == 0) {
+                        if (!g_hash_table_lookup (ht, langs[i])) {
+                                s = gdm_get_region_from_name (langs[i], NULL);
+                                g_hash_table_insert (ht, g_strdup (langs[i]), s);
+                        }
+                }
+                g_free (l);
+        }
+        g_strfreev (langs);
+        g_free (language);
+
+        return ht;
+}
diff --git a/gnome-initial-setup/languages/cc-common-language.h b/gnome-initial-setup/languages/cc-common-language.h
new file mode 100644
index 0000000..169eee4
--- /dev/null
+++ b/gnome-initial-setup/languages/cc-common-language.h
@@ -0,0 +1,58 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __CC_COMMON_LANGUAGE_H__
+#define __CC_COMMON_LANGUAGE_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+enum {
+        LOCALE_COL,
+        DISPLAY_LOCALE_COL,
+        SEPARATOR_COL,
+        USER_LANGUAGE,
+        NUM_COLS
+};
+
+gboolean cc_common_language_get_iter_for_language   (GtkTreeModel     *model,
+						     const gchar      *lang,
+						     GtkTreeIter      *iter);
+gboolean cc_common_language_get_iter_for_region     (GtkTreeModel     *model,
+						     const gchar      *lang,
+						     GtkTreeIter      *iter);
+guint    cc_common_language_add_available_languages (GtkListStore     *store,
+                                                     gboolean          regions,
+                                                     GHashTable       *user_langs);
+gboolean cc_common_language_has_font                (const gchar  *locale);
+gchar   *cc_common_language_get_current_language    (void);
+
+GHashTable *cc_common_language_get_initial_languages   (void);
+GHashTable *cc_common_language_get_initial_regions     (const gchar *lang);
+
+void     cc_common_language_setup_list              (GtkWidget    *treeview,
+						     GHashTable   *initial);
+void     cc_common_language_select_current_language (GtkTreeView  *treeview);
+
+G_END_DECLS
+
+#endif
diff --git a/gnome-initial-setup/languages/cc-language-chooser.c b/gnome-initial-setup/languages/cc-language-chooser.c
new file mode 100644
index 0000000..b2f692d
--- /dev/null
+++ b/gnome-initial-setup/languages/cc-language-chooser.c
@@ -0,0 +1,343 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <locale.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+
+#include <fontconfig/fontconfig.h>
+
+#include "cc-language-chooser.h"
+#include "cc-common-language.h"
+#include "gdm-languages.h"
+
+gchar *
+cc_language_chooser_get_language (GtkWidget *chooser)
+{
+        GtkTreeView *tv;
+        GtkTreeSelection *selection;
+        GtkTreeModel *model;
+        GtkTreeIter iter;
+        gchar *lang;
+
+        tv = (GtkTreeView *) g_object_get_data (G_OBJECT (chooser), "list");
+        selection = gtk_tree_view_get_selection (tv);
+
+        gdk_threads_enter ();
+        if (gtk_tree_selection_get_selected (selection, &model, &iter))
+                gtk_tree_model_get (model, &iter, LOCALE_COL, &lang, -1);
+        else
+                lang = NULL;
+        gdk_threads_leave ();
+
+        return lang;
+}
+
+void
+cc_language_chooser_clear_filter (GtkWidget *chooser)
+{
+	GtkEntry *entry;
+
+	entry = (GtkEntry *) g_object_get_data (G_OBJECT (chooser), "filter-entry");
+	gtk_entry_set_text (entry, "");
+}
+
+static void
+row_activated (GtkTreeView       *tree_view,
+               GtkTreePath       *path,
+               GtkTreeViewColumn *column,
+               GtkWidget         *chooser)
+{
+        gtk_dialog_response (GTK_DIALOG (chooser), GTK_RESPONSE_OK);
+}
+
+static void
+languages_foreach_cb (gpointer key,
+		      gpointer value,
+		      gpointer user_data)
+{
+	GtkListStore *store = (GtkListStore *) user_data;
+	const char *locale = (const char *) key;
+	const char *display_locale = (const char *) value;
+	GtkTreeIter iter;
+
+	gtk_list_store_append (store, &iter);
+	gtk_list_store_set (store, &iter,
+			    LOCALE_COL, locale,
+			    DISPLAY_LOCALE_COL, display_locale,
+			    -1);
+}
+
+void
+cc_add_user_languages (GtkTreeModel *model)
+{
+        char *name;
+        GtkTreeIter iter;
+        GtkListStore *store = GTK_LIST_STORE (model);
+        GHashTable *user_langs;
+        const char *display;
+
+        gtk_list_store_clear (store);
+
+	user_langs = cc_common_language_get_initial_languages ();
+
+	/* Add the current locale first */
+	name = cc_common_language_get_current_language ();
+	display = g_hash_table_lookup (user_langs, name);
+
+        gtk_list_store_append (store, &iter);
+        gtk_list_store_set (store, &iter, LOCALE_COL, name, DISPLAY_LOCALE_COL, display, -1);
+        g_hash_table_remove (user_langs, name);
+        g_free (name);
+
+        /* The rest of the languages */
+	g_hash_table_foreach (user_langs, (GHFunc) languages_foreach_cb, store);
+
+	/* And now the "Other..." selection */
+        gtk_list_store_append (store, &iter);
+        gtk_list_store_set (store, &iter, LOCALE_COL, NULL, DISPLAY_LOCALE_COL, _("Other..."), -1);
+
+        g_hash_table_destroy (user_langs);
+}
+
+static void
+remove_timeout (gpointer data,
+		GObject *where_the_object_was)
+{
+	guint timeout = GPOINTER_TO_UINT (data);
+	g_source_remove (timeout);
+}
+
+static void
+remove_async (gpointer data)
+{
+  guint async_id = GPOINTER_TO_UINT (data);
+
+  g_source_remove (async_id);
+}
+
+static void
+selection_changed (GtkTreeSelection *selection,
+                   GtkWidget        *chooser)
+{
+	gtk_dialog_set_response_sensitive (GTK_DIALOG (chooser),
+					   GTK_RESPONSE_OK,
+				           gtk_tree_selection_get_selected (selection, NULL, NULL));
+}
+
+static gboolean
+finish_language_chooser (gpointer user_data)
+{
+	GtkWidget *chooser = (GtkWidget *) user_data;
+	GtkWidget *list;
+	GtkTreeModel *model;
+	GtkWindow *parent;
+	GHashTable *user_langs;
+	guint timeout;
+        guint async_id;
+	GtkTreeSelection *selection;
+        gboolean regions;
+
+	/* Did we get called after the widget was destroyed? */
+	if (chooser == NULL)
+		return FALSE;
+
+        regions = GPOINTER_TO_INT (g_object_get_data (G_OBJECT (chooser), "regions"));
+
+	list = g_object_get_data (G_OBJECT (chooser), "list");
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (model));
+	user_langs = g_object_get_data (G_OBJECT (chooser), "user-langs");
+
+	async_id = cc_common_language_add_available_languages (GTK_LIST_STORE (model), regions, user_langs);
+        g_object_set_data_full (G_OBJECT (chooser), "language-async", GUINT_TO_POINTER (async_id), remove_async);
+
+	parent = gtk_window_get_transient_for (GTK_WINDOW (chooser));
+	gdk_window_set_cursor (gtk_widget_get_window (GTK_WIDGET (parent)), NULL);
+
+	g_object_set_data (G_OBJECT (chooser), "user-langs", NULL);
+	timeout = GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (chooser), "timeout"));
+	g_object_weak_unref (G_OBJECT (chooser), (GWeakNotify) remove_timeout, GUINT_TO_POINTER (timeout));
+
+	/* And now listen for changes */
+        selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (list));
+        g_signal_connect (G_OBJECT (selection), "changed",
+                          G_CALLBACK (selection_changed), chooser);
+
+	return FALSE;
+}
+
+static void
+filter_clear (GtkEntry *entry, GtkEntryIconPosition icon_pos, GdkEvent *event, gpointer user_data)
+{
+	gtk_entry_set_text (entry, "");
+}
+
+static void
+filter_changed (GtkWidget *entry, GParamSpec *pspec, GtkWidget *list)
+{
+	const gchar *pattern;
+	GtkTreeModel *filter_model;
+	GtkTreeModel *model;
+
+	pattern = gtk_entry_get_text (GTK_ENTRY (entry));
+
+	filter_model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+	model = gtk_tree_model_filter_get_model (GTK_TREE_MODEL_FILTER (filter_model));
+
+	if (g_strcmp0 (pattern, "") == 0) {
+                g_object_set (G_OBJECT (entry),
+                              "secondary-icon-name", "edit-find-symbolic",
+                              "secondary-icon-activatable", FALSE,
+                              "secondary-icon-sensitive", FALSE,
+                              NULL);
+
+		g_object_set_data_full (G_OBJECT (model), "filter-string",
+					g_strdup (""), g_free);
+
+        } else {
+                g_object_set (G_OBJECT (entry),
+                              "secondary-icon-name", "edit-clear-symbolic",
+                              "secondary-icon-activatable", TRUE,
+                              "secondary-icon-sensitive", TRUE,
+                              NULL);
+
+		g_object_set_data_full (G_OBJECT (model), "filter-string",
+					g_utf8_casefold (pattern, -1), g_free);
+        }
+
+	gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (filter_model));
+}
+
+static gboolean
+filter_languages (GtkTreeModel *model,
+		  GtkTreeIter  *iter,
+		  gpointer      data)
+{
+	const gchar *filter_string;
+	gchar *locale, *l;
+	gboolean visible;
+
+	filter_string = g_object_get_data (G_OBJECT (model), "filter-string");
+
+	if (filter_string == NULL) {
+		return TRUE;
+	}
+
+	gdk_threads_enter ();
+	gtk_tree_model_get (model, iter, DISPLAY_LOCALE_COL, &locale, -1);
+	gdk_threads_leave ();
+
+	l = g_utf8_casefold (locale, -1);
+
+	visible = strstr (l, filter_string) != NULL;
+
+	g_free (locale);
+	g_free (l);
+
+	return visible;
+}
+
+GtkWidget *
+cc_language_chooser_new (GtkWidget *parent, gboolean regions)
+{
+        GtkBuilder *builder;
+        const char *filename;
+        GError *error = NULL;
+        GtkWidget *chooser;
+        GtkWidget *list;
+        GtkWidget *button;
+	GtkWidget *entry;
+        GtkWidget *widget;
+        GHashTable *user_langs;
+        GdkCursor *cursor;
+        guint timeout;
+	GtkTreeModel *model;
+	GtkTreeModel *filter_model;
+
+        builder = gtk_builder_new ();
+        filename = UIDIR "/language-chooser.ui";
+        if (!g_file_test (filename, G_FILE_TEST_EXISTS))
+                filename = "data/language-chooser.ui";
+        if (!gtk_builder_add_from_file (builder, filename, &error)) {
+                g_warning ("failed to load language chooser: %s", error->message);
+                g_error_free (error);
+                return NULL;
+        }
+
+        chooser = (GtkWidget *) gtk_builder_get_object (builder, "dialog");
+
+        if (regions) {
+                widget = (GtkWidget *) gtk_builder_get_object (builder, "title");
+                gtk_label_set_text (GTK_LABEL (widget), _("Select a region"));
+
+                /* communicate the preference to finish_language_chooser() */
+                g_object_set_data (G_OBJECT (chooser), "regions", GINT_TO_POINTER (TRUE));
+        }
+
+        list = (GtkWidget *) gtk_builder_get_object (builder, "language-list");
+        g_object_set_data (G_OBJECT (chooser), "list", list);
+        g_signal_connect (list, "row-activated",
+                          G_CALLBACK (row_activated), chooser);
+
+        button = (GtkWidget *) gtk_builder_get_object (builder, "ok-button");
+        gtk_widget_grab_default (button);
+
+	entry = (GtkWidget *) gtk_builder_get_object (builder, "filter-entry");
+        g_object_set_data (G_OBJECT (chooser), "filter-entry", entry);
+	g_signal_connect (entry, "notify::text",
+			  G_CALLBACK (filter_changed), list);
+	g_signal_connect (entry, "icon-release",
+			  G_CALLBACK (filter_clear), NULL);
+        gtk_widget_grab_focus (entry);
+
+	user_langs = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+	cc_common_language_setup_list (list, user_langs);
+
+	model = gtk_tree_view_get_model (GTK_TREE_VIEW (list));
+	filter_model = gtk_tree_model_filter_new (model, NULL);
+	gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (filter_model), filter_languages,
+						NULL, NULL);
+	gtk_tree_view_set_model (GTK_TREE_VIEW (list), filter_model);
+
+	/* Setup so that the list is added after the dialogue is shown */
+        cursor = gdk_cursor_new (GDK_WATCH);
+        gdk_window_set_cursor (gtk_widget_get_window (parent), cursor);
+        g_object_unref (cursor);
+
+        gtk_window_set_transient_for (GTK_WINDOW (chooser), GTK_WINDOW (parent));
+
+	g_object_set_data_full (G_OBJECT (chooser), "user-langs",
+				user_langs, (GDestroyNotify) g_hash_table_destroy);
+        timeout = g_idle_add ((GSourceFunc) finish_language_chooser, chooser);
+        g_object_set_data (G_OBJECT (chooser), "timeout", GUINT_TO_POINTER (timeout));
+        g_object_weak_ref (G_OBJECT (chooser), (GWeakNotify) remove_timeout, GUINT_TO_POINTER (timeout));
+
+        g_object_unref (builder);
+
+        return chooser;
+}
diff --git a/gnome-initial-setup/languages/cc-language-chooser.h b/gnome-initial-setup/languages/cc-language-chooser.h
new file mode 100644
index 0000000..7cd6873
--- /dev/null
+++ b/gnome-initial-setup/languages/cc-language-chooser.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2009-2010  Red Hat, Inc,
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Matthias Clasen <mclasen redhat com>
+ */
+
+#ifndef __CC_LANGUAGE_CHOOSER_H__
+#define __CC_LANGUAGE_CHOOSER_H__
+
+#include <gtk/gtk.h>
+
+G_BEGIN_DECLS
+
+void              cc_add_user_languages            (GtkTreeModel *model);
+
+GtkWidget        *cc_language_chooser_new          (GtkWidget *parent,
+                                                    gboolean   regions);
+void              cc_language_chooser_clear_filter (GtkWidget *chooser);
+gchar            *cc_language_chooser_get_language (GtkWidget *chooser);
+
+G_END_DECLS
+
+#endif
diff --git a/gnome-initial-setup/languages/gdm-languages.c b/gnome-initial-setup/languages/gdm-languages.c
new file mode 100644
index 0000000..64c72e4
--- /dev/null
+++ b/gnome-initial-setup/languages/gdm-languages.c
@@ -0,0 +1,1326 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008  Red Hat, Inc,
+ *           2007  William Jon McCann <mccann jhu edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by : William Jon McCann <mccann jhu edu>
+ *              Ray Strode <rstrode redhat com>
+ */
+
+#include "config.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <dirent.h>
+#include <locale.h>
+#include <langinfo.h>
+#include <sys/stat.h>
+
+#include <glib.h>
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include "gdm-languages.h"
+
+#include <langinfo.h>
+#ifndef __LC_LAST
+#define __LC_LAST       13
+#endif
+#include "locarchive.h"
+
+#define ALIASES_FILE DATADIR "/gdm/locale.alias"
+#define ARCHIVE_FILE LIBLOCALEDIR "/locale-archive"
+#define SYSTEM_ARCHIVE_FILE "/usr/lib/locale/locale-archive"
+#define ISO_CODES_DATADIR ISO_CODES_PREFIX "/share/xml/iso-codes"
+#define ISO_CODES_LOCALESDIR ISO_CODES_PREFIX "/share/locale"
+
+typedef struct _GdmLocale {
+        char *id;
+        char *name;
+        char *language_code;
+        char *territory_code;
+        char *codeset;
+        char *modifier;
+} GdmLocale;
+
+static GHashTable *gdm_languages_map;
+static GHashTable *gdm_territories_map;
+static GHashTable *gdm_available_locales_map;
+static GHashTable *gdm_language_count_map;
+static GHashTable *gdm_territory_count_map;
+
+static char * construct_language_name (const char *language,
+                                       const char *territory,
+                                       const char *codeset,
+                                       const char *modifier);
+
+static gboolean language_name_is_valid (const char *language_name);
+
+static void
+gdm_locale_free (GdmLocale *locale)
+{
+        if (locale == NULL) {
+                return;
+        }
+
+        g_free (locale->id);
+        g_free (locale->name);
+        g_free (locale->codeset);
+        g_free (locale->modifier);
+        g_free (locale);
+}
+
+static char *
+normalize_codeset (const char *codeset)
+{
+        char *normalized_codeset;
+        const char *p;
+        char *q;
+
+        normalized_codeset = g_strdup (codeset);
+
+        if (codeset != NULL) {
+                for (p = codeset, q = normalized_codeset;
+                     *p != '\0'; p++) {
+
+                        if (*p == '-' || *p == '_') {
+                                continue;
+                        }
+
+                        *q = g_ascii_tolower (*p);
+                        q++;
+                }
+                *q = '\0';
+        }
+
+        return normalized_codeset;
+}
+
+/*
+ * According to http://en.wikipedia.org/wiki/Locale
+ * locale names are of the form:
+ * [language[_territory][ codeset][ modifier]]
+ */
+gboolean
+gdm_parse_language_name (const char *name,
+                         char      **language_codep,
+                         char      **territory_codep,
+                         char      **codesetp,
+                         char      **modifierp)
+{
+        GRegex     *re;
+        GMatchInfo *match_info;
+        gboolean    res;
+        GError     *error;
+        gchar      *normalized_codeset = NULL;
+        gchar      *normalized_name = NULL;
+        gboolean    retval;
+
+        match_info = NULL;
+        retval = FALSE;
+
+        error = NULL;
+        re = g_regex_new ("^(?P<language>[^_  [:space:]]+)"
+                          "(_(?P<territory>[[:upper:]]+))?"
+                          "(\\.(?P<codeset>[-_0-9a-zA-Z]+))?"
+                          "(@(?P<modifier>[[:ascii:]]+))?$",
+                          0, 0, &error);
+        if (re == NULL) {
+                g_warning ("%s", error->message);
+                goto out;
+        }
+
+        if (!g_regex_match (re, name, 0, &match_info) ||
+            g_match_info_is_partial_match (match_info)) {
+                g_warning ("locale '%s' isn't valid\n", name);
+                goto out;
+        }
+
+        res = g_match_info_matches (match_info);
+        if (! res) {
+                g_warning ("Unable to parse locale: %s", name);
+                goto out;
+        }
+
+        retval = TRUE;
+
+        if (language_codep != NULL) {
+                *language_codep = g_match_info_fetch_named (match_info, "language");
+        }
+
+        if (territory_codep != NULL) {
+                *territory_codep = g_match_info_fetch_named (match_info, "territory");
+
+                if (*territory_codep != NULL &&
+                    *territory_codep[0] == '\0') {
+                        g_free (*territory_codep);
+                        *territory_codep = NULL;
+                }
+        }
+
+        if (codesetp != NULL) {
+                *codesetp = g_match_info_fetch_named (match_info, "codeset");
+
+                if (*codesetp != NULL &&
+                    *codesetp[0] == '\0') {
+                        g_free (*codesetp);
+                        *codesetp = NULL;
+                }
+        }
+
+        if (modifierp != NULL) {
+                *modifierp = g_match_info_fetch_named (match_info, "modifier");
+
+                if (*modifierp != NULL &&
+                    *modifierp[0] == '\0') {
+                        g_free (*modifierp);
+                        *modifierp = NULL;
+                }
+        }
+
+        if (codesetp != NULL && *codesetp != NULL) {
+                normalized_codeset = normalize_codeset (*codesetp);
+                normalized_name = construct_language_name (language_codep ? *language_codep : NULL,
+                                                           territory_codep ? *territory_codep : NULL,
+                                                           normalized_codeset,
+                                                           modifierp ? *modifierp : NULL);
+
+                if (language_name_is_valid (normalized_name)) {
+                        g_free (*codesetp);
+                        *codesetp = normalized_codeset;
+                } else {
+                        g_free (normalized_codeset);
+                }
+                g_free (normalized_name);
+        }
+
+ out:
+        g_match_info_free (match_info);
+        g_regex_unref (re);
+
+        return retval;
+}
+
+static char *
+construct_language_name (const char *language,
+                         const char *territory,
+                         const char *codeset,
+                         const char *modifier)
+{
+        char *name;
+
+        g_assert (language[0] != 0);
+        g_assert (territory == NULL || territory[0] != 0);
+        g_assert (codeset == NULL || codeset[0] != 0);
+        g_assert (modifier == NULL || modifier[0] != 0);
+
+        name = g_strdup_printf ("%s%s%s%s%s%s%s",
+                                language,
+                                territory != NULL? "_" : "",
+                                territory != NULL? territory : "",
+                                codeset != NULL? "." : "",
+                                codeset != NULL? codeset : "",
+                                modifier != NULL? "@" : "",
+                                modifier != NULL? modifier : "");
+
+        return name;
+}
+
+char *
+gdm_normalize_language_name (const char *name)
+{
+        char *normalized_name;
+        char *language_code;
+        char *territory_code;
+        char *codeset;
+        char *modifier;
+
+        if (name[0] == '\0') {
+                return NULL;
+        }
+
+        gdm_parse_language_name (name,
+                                 &language_code,
+                                 &territory_code,
+                                 &codeset, &modifier);
+
+        normalized_name = construct_language_name (language_code,
+                                                   territory_code,
+                                                   codeset, modifier);
+        g_free (language_code);
+        g_free (territory_code);
+        g_free (codeset);
+        g_free (modifier);
+
+        return normalized_name;
+}
+
+static gboolean
+language_name_is_valid (const char *language_name)
+{
+        char     *old_locale;
+        gboolean  is_valid;
+#ifdef WITH_INCOMPLETE_LOCALES
+        int lc_type_id = LC_CTYPE;
+#else
+        int lc_type_id = LC_MESSAGES;
+#endif
+
+        old_locale = g_strdup (setlocale (lc_type_id, NULL));
+        is_valid = setlocale (lc_type_id, language_name) != NULL;
+        setlocale (lc_type_id, old_locale);
+        g_free (old_locale);
+
+        return is_valid;
+}
+
+static void
+language_name_get_codeset_details (const char  *language_name,
+                                   char       **pcodeset,
+                                   gboolean    *is_utf8)
+{
+        char     *old_locale;
+        char     *codeset;
+
+        old_locale = g_strdup (setlocale (LC_CTYPE, NULL));
+
+        if (setlocale (LC_CTYPE, language_name) == NULL) {
+                g_free (old_locale);
+                return;
+        }
+
+        codeset = nl_langinfo (CODESET);
+
+        if (pcodeset != NULL) {
+                *pcodeset = g_strdup (codeset);
+        }
+
+        if (is_utf8 != NULL) {
+                codeset = normalize_codeset (codeset);
+
+                *is_utf8 = strcmp (codeset, "utf8") == 0;
+                g_free (codeset);
+        }
+
+        setlocale (LC_CTYPE, old_locale);
+        g_free (old_locale);
+}
+
+gboolean
+gdm_language_has_translations (const char *language_name)
+{
+        GDir        *dir;
+        char        *path;
+        const char  *name;
+        gboolean     has_translations;
+
+        path = g_build_filename (GNOMELOCALEDIR, language_name, "LC_MESSAGES", NULL);
+
+        has_translations = FALSE;
+        dir = g_dir_open (path, 0, NULL);
+        g_free (path);
+
+        if (dir == NULL) {
+                goto out;
+        }
+
+        do {
+                name = g_dir_read_name (dir);
+
+                if (name == NULL) {
+                        break;
+                }
+
+                if (g_str_has_suffix (name, ".mo")) {
+                        has_translations = TRUE;
+                        break;
+                }
+        } while (name != NULL);
+        g_dir_close (dir);
+
+out:
+        return has_translations;
+}
+
+static gboolean
+add_locale (const char *language_name,
+            gboolean    utf8_only)
+{
+        GdmLocale *locale;
+        GdmLocale *old_locale;
+        char      *name;
+        gboolean   is_utf8;
+
+        g_return_val_if_fail (language_name != NULL, FALSE);
+        g_return_val_if_fail (*language_name != '\0', FALSE);
+
+        language_name_get_codeset_details (language_name, NULL, &is_utf8);
+
+        if (is_utf8) {
+                name = g_strdup (language_name);
+        } else if (utf8_only) {
+                name = g_strdup_printf ("%s.utf8", language_name);
+
+                language_name_get_codeset_details (name, NULL, &is_utf8);
+                if (!is_utf8) {
+                        g_free (name);
+                        return FALSE;
+                }
+        } else {
+                name = g_strdup (language_name);
+        }
+
+        if (!language_name_is_valid (name)) {
+                g_debug ("Ignoring '%s' as a locale, since it's invalid", name);
+                g_free (name);
+                return FALSE;
+        }
+
+        locale = g_new0 (GdmLocale, 1);
+        gdm_parse_language_name (name,
+                                 &locale->language_code,
+                                 &locale->territory_code,
+                                 &locale->codeset,
+                                 &locale->modifier);
+        g_free (name);
+        name = NULL;
+
+#ifdef WITH_INCOMPLETE_LOCALES
+        if (utf8_only) {
+                if (locale->territory_code == NULL || locale->modifier) {
+                        g_debug ("Ignoring '%s' as a locale, since it lacks territory code or modifier", name);
+                        gdm_locale_free (locale);
+                        return FALSE;
+                }
+        }
+#endif
+
+        locale->id = construct_language_name (locale->language_code, locale->territory_code,
+                                              NULL, locale->modifier);
+        locale->name = construct_language_name (locale->language_code, locale->territory_code,
+                                                locale->codeset, locale->modifier);
+
+#ifndef WITH_INCOMPLETE_LOCALES
+        if (!gdm_language_has_translations (locale->name) &&
+            !gdm_language_has_translations (locale->id) &&
+            !gdm_language_has_translations (locale->language_code) &&
+            utf8_only) {
+                g_debug ("Ignoring '%s' as a locale, since it lacks translations", locale->name);
+                gdm_locale_free (locale);
+                return FALSE;
+        }
+#endif
+
+        if (!utf8_only) {
+                g_free (locale->id);
+                locale->id = g_strdup (locale->name);
+        }
+
+        old_locale = g_hash_table_lookup (gdm_available_locales_map, locale->id);
+        if (old_locale != NULL) {
+                if (strlen (old_locale->name) > strlen (locale->name)) {
+                        gdm_locale_free (locale);
+                        return FALSE;
+                }
+        }
+
+        g_hash_table_insert (gdm_available_locales_map, g_strdup (locale->id), locale);
+
+        return TRUE;
+}
+
+struct nameent
+{
+        char    *name;
+        uint32_t locrec_offset;
+};
+
+static gboolean
+collect_locales_from_archive (void)
+{
+        GMappedFile        *mapped;
+        GError             *error;
+        char               *addr;
+        struct locarhead   *head;
+        struct namehashent *namehashtab;
+        struct nameent     *names;
+        uint32_t            used;
+        uint32_t            cnt;
+        gsize               len;
+        gboolean            locales_collected;
+
+        error = NULL;
+        mapped = g_mapped_file_new (ARCHIVE_FILE, FALSE, &error);
+        if (mapped == NULL) {
+                mapped = g_mapped_file_new (SYSTEM_ARCHIVE_FILE, FALSE, NULL);
+                if (mapped == NULL) {
+                        g_warning ("Mapping failed for %s: %s", ARCHIVE_FILE, error->message);
+                        g_error_free (error);
+                        return FALSE;
+                }
+                g_error_free (error);
+        }
+
+        locales_collected = FALSE;
+
+        addr = g_mapped_file_get_contents (mapped);
+        len = g_mapped_file_get_length (mapped);
+
+        head = (struct locarhead *) addr;
+        if (head->namehash_offset + head->namehash_size > len
+            || head->string_offset + head->string_size > len
+            || head->locrectab_offset + head->locrectab_size > len
+            || head->sumhash_offset + head->sumhash_size > len) {
+                goto out;
+        }
+
+        namehashtab = (struct namehashent *) (addr + head->namehash_offset);
+
+        names = (struct nameent *) g_new0 (struct nameent, head->namehash_used);
+        for (cnt = used = 0; cnt < head->namehash_size; ++cnt) {
+                if (namehashtab[cnt].locrec_offset != 0) {
+                        names[used].name = addr + namehashtab[cnt].name_offset;
+                        names[used++].locrec_offset = namehashtab[cnt].locrec_offset;
+                }
+        }
+
+        for (cnt = 0; cnt < used; ++cnt) {
+                add_locale (names[cnt].name, TRUE);
+        }
+
+        g_free (names);
+
+        locales_collected = TRUE;
+ out:
+
+        g_mapped_file_unref (mapped);
+        return locales_collected;
+}
+
+static int
+select_dirs (const struct dirent *dirent)
+{
+        int result = 0;
+
+        if (strcmp (dirent->d_name, ".") != 0 && strcmp (dirent->d_name, "..") != 0) {
+                mode_t mode = 0;
+
+#ifdef _DIRENT_HAVE_D_TYPE
+                if (dirent->d_type != DT_UNKNOWN && dirent->d_type != DT_LNK) {
+                        mode = DTTOIF (dirent->d_type);
+                } else
+#endif
+                        {
+                                struct stat st;
+                                char       *path;
+
+                                path = g_build_filename (LIBLOCALEDIR, dirent->d_name, NULL);
+                                if (g_stat (path, &st) == 0) {
+                                        mode = st.st_mode;
+                                }
+                                g_free (path);
+                        }
+
+                result = S_ISDIR (mode);
+        }
+
+        return result;
+}
+
+static void
+collect_locales_from_directory (void)
+{
+        struct dirent **dirents;
+        int             ndirents;
+        int             cnt;
+
+        ndirents = scandir (LIBLOCALEDIR, &dirents, select_dirs, alphasort);
+
+        for (cnt = 0; cnt < ndirents; ++cnt) {
+                add_locale (dirents[cnt]->d_name, TRUE);
+        }
+
+        if (ndirents > 0) {
+                free (dirents);
+        }
+}
+
+static void
+collect_locales_from_locale_file (const char *locale_file)
+{
+        FILE *langlist;
+        char curline[256];
+        char *getsret;
+
+        if (locale_file == NULL)
+                return;
+
+        langlist = fopen (locale_file, "r");
+
+        if (langlist == NULL)
+                return;
+
+        for (;;) {
+                char *name;
+                char *lang;
+                char **lang_list;
+                int i;
+
+                getsret = fgets (curline, sizeof (curline), langlist);
+                if (getsret == NULL)
+                        break;
+
+                if (curline[0] <= ' ' ||
+                    curline[0] == '#')
+                        continue;
+
+                name = strtok (curline, " \t\r\n");
+                if (name == NULL)
+                        continue;
+
+                lang = strtok (NULL, " \t\r\n");
+                if (lang == NULL)
+                        continue;
+
+                lang_list = g_strsplit (lang, ",", -1);
+                if (lang_list == NULL)
+                        continue;
+
+                lang = NULL;
+                for (i = 0; lang_list[i] != NULL; i++) {
+                        if (add_locale (lang_list[i], FALSE)) {
+                                break;
+                        }
+                }
+                g_strfreev (lang_list);
+        }
+
+        fclose (langlist);
+}
+
+static void
+count_languages_and_territories (void)
+{
+	gpointer value;
+	GHashTableIter iter;
+
+	gdm_language_count_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+	gdm_territory_count_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, NULL);
+
+        g_hash_table_iter_init (&iter, gdm_available_locales_map);
+        while (g_hash_table_iter_next (&iter, NULL, &value)) {
+                GdmLocale *locale;
+
+                locale = (GdmLocale *) value;
+
+		if (locale->language_code != NULL) {
+			int count;
+
+			count = GPOINTER_TO_INT (g_hash_table_lookup (gdm_language_count_map, locale->language_code));
+			count++;
+			g_hash_table_insert (gdm_language_count_map, g_strdup (locale->language_code), GINT_TO_POINTER (count));
+		}
+
+		if (locale->territory_code != NULL) {
+			int count;
+
+			count = GPOINTER_TO_INT (g_hash_table_lookup (gdm_territory_count_map, locale->territory_code));
+			count++;
+			g_hash_table_insert (gdm_territory_count_map, g_strdup (locale->territory_code), GINT_TO_POINTER (count));
+		}
+        }
+}
+
+static void
+collect_locales (void)
+{
+
+        if (gdm_available_locales_map == NULL) {
+                gdm_available_locales_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, (GDestroyNotify) gdm_locale_free);
+        }
+
+        if (!collect_locales_from_archive ()) {
+#ifndef WITH_INCOMPLETE_LOCALES
+                g_warning ("Could not read list of available locales from libc, "
+                           "guessing possible locales from available translations, "
+                           "but list may be incomplete!");
+#endif
+        }
+
+        collect_locales_from_directory ();
+
+        collect_locales_from_locale_file (ALIASES_FILE);
+
+	count_languages_and_territories ();
+}
+
+static gint
+get_language_count (const char *language)
+{
+        if (gdm_language_count_map == NULL) {
+                collect_locales ();
+        }
+
+	return GPOINTER_TO_INT (g_hash_table_lookup (gdm_language_count_map, language));
+}
+
+static gboolean
+is_unique_language (const char *language)
+{
+  return get_language_count (language) == 1;
+}
+
+static gint
+get_territory_count (const char *territory)
+{
+        if (gdm_territory_count_map == NULL) {
+                collect_locales ();
+        }
+
+	return GPOINTER_TO_INT (g_hash_table_lookup (gdm_territory_count_map, territory));
+}
+
+static gboolean
+is_unique_territory (const char *territory)
+{
+  return get_territory_count (territory) == 1;
+}
+
+static gboolean
+is_fallback_language (const char *code)
+{
+        const char *fallback_language_names[] = { "C", "POSIX", NULL };
+        int i;
+
+        for (i = 0; fallback_language_names[i] != NULL; i++) {
+                if (strcmp (code, fallback_language_names[i]) == 0) {
+                        return TRUE;
+                }
+        }
+
+        return FALSE;
+}
+
+static const char *
+get_language (const char *code)
+{
+        const char *name;
+        int         len;
+
+        g_assert (code != NULL);
+
+        if (is_fallback_language (code)) {
+                return "Unspecified";
+        }
+
+        len = strlen (code);
+        if (len != 2 && len != 3) {
+                return NULL;
+        }
+
+        name = (const char *) g_hash_table_lookup (gdm_languages_map, code);
+
+        return name;
+}
+
+static char *
+get_first_item_in_semicolon_list (const char *list)
+{
+    char **items;
+    char  *item;
+
+    /* Some entries in iso codes have multiple values, separated
+     * by semicolons.  Not really sure which one to pick, so
+     * we just arbitrarily pick the first one.
+     */
+    items = g_strsplit (list, "; ", 2);
+
+    item = g_strdup (items[0]);
+    g_strfreev (items);
+
+    return item;
+}
+
+static char *
+get_translated_language (const char *code,
+                         const char *locale)
+{
+        const char *language;
+        char *name;
+
+        language = get_language (code);
+
+        name = NULL;
+        if (language != NULL) {
+                const char  *translated_name;
+                char        *old_locale;
+
+                if (locale != NULL) {
+                        old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+                        setlocale (LC_MESSAGES, locale);
+                }
+
+                if (is_fallback_language (code)) {
+                        name = g_strdup (_("Unspecified"));
+                } else {
+                        translated_name = dgettext ("iso_639", language);
+                        name = get_first_item_in_semicolon_list (translated_name);
+                }
+
+                if (locale != NULL) {
+                        setlocale (LC_MESSAGES, old_locale);
+                        g_free (old_locale);
+                }
+        }
+
+        return name;
+}
+
+static const char *
+get_territory (const char *code)
+{
+        const char *name;
+        int         len;
+
+        g_assert (code != NULL);
+
+        len = strlen (code);
+        if (len != 2 && len != 3) {
+                return NULL;
+        }
+
+        name = (const char *) g_hash_table_lookup (gdm_territories_map, code);
+
+        return name;
+}
+
+static char *
+get_translated_territory (const char *code,
+                          const char *locale)
+{
+        const char *territory;
+        char       *name;
+
+        territory = get_territory (code);
+
+        name = NULL;
+        if (territory != NULL) {
+                const char *translated_territory;
+                char       *old_locale;
+
+                if (locale != NULL) {
+                        old_locale = g_strdup (setlocale (LC_MESSAGES, NULL));
+                        setlocale (LC_MESSAGES, locale);
+                }
+
+                translated_territory = dgettext ("iso_3166", territory);
+                name = get_first_item_in_semicolon_list (translated_territory);
+
+                if (locale != NULL) {
+                        setlocale (LC_MESSAGES, old_locale);
+                        g_free (old_locale);
+                }
+        }
+
+        return name;
+}
+
+static void
+languages_parse_start_tag (GMarkupParseContext      *ctx,
+                           const char               *element_name,
+                           const char              **attr_names,
+                           const char              **attr_values,
+                           gpointer                  user_data,
+                           GError                  **error)
+{
+        const char *ccode_longB;
+        const char *ccode_longT;
+        const char *ccode;
+        const char *ccode_id;
+        const char *lang_name;
+
+        if (! (g_str_equal (element_name, "iso_639_entry") || g_str_equal (element_name, "iso_639_3_entry"))
+            || attr_names == NULL || attr_values == NULL) {
+                return;
+        }
+
+        ccode = NULL;
+        ccode_longB = NULL;
+        ccode_longT = NULL;
+        ccode_id = NULL;
+        lang_name = NULL;
+
+        while (*attr_names && *attr_values) {
+                if (g_str_equal (*attr_names, "iso_639_1_code")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                if (strlen (*attr_values) != 2) {
+                                        return;
+                                }
+                                ccode = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "iso_639_2B_code")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                if (strlen (*attr_values) != 3) {
+                                        return;
+                                }
+                                ccode_longB = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "iso_639_2T_code")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                if (strlen (*attr_values) != 3) {
+                                        return;
+                                }
+                                ccode_longT = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "id")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                if (strlen (*attr_values) != 2 &&
+                                    strlen (*attr_values) != 3) {
+                                        return;
+                                }
+                                ccode_id = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "name")) {
+                        lang_name = *attr_values;
+                }
+
+                ++attr_names;
+                ++attr_values;
+        }
+
+        if (lang_name == NULL) {
+                return;
+        }
+
+        if (ccode != NULL) {
+                g_hash_table_insert (gdm_languages_map,
+                                     g_strdup (ccode),
+                                     g_strdup (lang_name));
+        }
+        if (ccode_longB != NULL) {
+                g_hash_table_insert (gdm_languages_map,
+                                     g_strdup (ccode_longB),
+                                     g_strdup (lang_name));
+        }
+        if (ccode_longT != NULL) {
+                g_hash_table_insert (gdm_languages_map,
+                                     g_strdup (ccode_longT),
+                                     g_strdup (lang_name));
+        }
+        if (ccode_id != NULL) {
+                g_hash_table_insert (gdm_languages_map,
+                                     g_strdup (ccode_id),
+                                     g_strdup (lang_name));
+        }
+}
+
+static void
+territories_parse_start_tag (GMarkupParseContext      *ctx,
+                             const char               *element_name,
+                             const char              **attr_names,
+                             const char              **attr_values,
+                             gpointer                  user_data,
+                             GError                  **error)
+{
+        const char *acode_2;
+        const char *acode_3;
+        const char *ncode;
+        const char *territory_common_name;
+        const char *territory_name;
+
+        if (! g_str_equal (element_name, "iso_3166_entry") || attr_names == NULL || attr_values == NULL) {
+                return;
+        }
+
+        acode_2 = NULL;
+        acode_3 = NULL;
+        ncode = NULL;
+        territory_common_name = NULL;
+        territory_name = NULL;
+
+        while (*attr_names && *attr_values) {
+                if (g_str_equal (*attr_names, "alpha_2_code")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                if (strlen (*attr_values) != 2) {
+                                        return;
+                                }
+                                acode_2 = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "alpha_3_code")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                if (strlen (*attr_values) != 3) {
+                                        return;
+                                }
+                                acode_3 = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "numeric_code")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                if (strlen (*attr_values) != 3) {
+                                        return;
+                                }
+                                ncode = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "common_name")) {
+                        /* skip if empty */
+                        if (**attr_values) {
+                                territory_common_name = *attr_values;
+                        }
+                } else if (g_str_equal (*attr_names, "name")) {
+                        territory_name = *attr_values;
+                }
+
+                ++attr_names;
+                ++attr_values;
+        }
+
+        if (territory_common_name != NULL) {
+                territory_name = territory_common_name;
+        }
+
+        if (territory_name == NULL) {
+                return;
+        }
+
+        if (acode_2 != NULL) {
+                g_hash_table_insert (gdm_territories_map,
+                                     g_strdup (acode_2),
+                                     g_strdup (territory_name));
+        }
+        if (acode_3 != NULL) {
+                g_hash_table_insert (gdm_territories_map,
+                                     g_strdup (acode_3),
+                                     g_strdup (territory_name));
+        }
+        if (ncode != NULL) {
+                g_hash_table_insert (gdm_territories_map,
+                                     g_strdup (ncode),
+                                     g_strdup (territory_name));
+        }
+}
+
+static void
+languages_variant_init (const char *variant)
+{
+        GError  *error;
+        gboolean res;
+        char    *buf;
+        gsize    buf_len;
+        char    *filename;
+
+        bindtextdomain (variant, ISO_CODES_LOCALESDIR);
+        bind_textdomain_codeset (variant, "UTF-8");
+
+        error = NULL;
+        filename = g_strdup_printf (ISO_CODES_DATADIR "/%s.xml", variant);
+        res = g_file_get_contents (filename,
+                                   &buf,
+                                   &buf_len,
+                                   &error);
+        if (res) {
+                GMarkupParseContext *ctx;
+                GMarkupParser        parser = { languages_parse_start_tag, NULL, NULL, NULL, NULL };
+
+                ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+
+                error = NULL;
+                res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
+
+                if (! res) {
+                        g_warning ("Failed to parse '%s': %s\n",
+                                   filename,
+                                   error->message);
+                        g_error_free (error);
+                        g_free (filename);
+                }
+
+                g_markup_parse_context_free (ctx);
+                g_free (buf);
+        } else {
+                g_warning ("Failed to load '%s': %s\n",
+                           filename,
+                           error->message);
+                g_error_free (error);
+        }
+}
+
+static void
+languages_init (void)
+{
+        gdm_languages_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+        languages_variant_init ("iso_639");
+        languages_variant_init ("iso_639_3");
+}
+
+static void
+territories_init (void)
+{
+        GError  *error;
+        gboolean res;
+        char    *buf;
+        gsize    buf_len;
+
+        bindtextdomain ("iso_3166", ISO_CODES_LOCALESDIR);
+        bind_textdomain_codeset ("iso_3166", "UTF-8");
+
+        gdm_territories_map = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+        error = NULL;
+        res = g_file_get_contents (ISO_CODES_DATADIR "/iso_3166.xml",
+                                   &buf,
+                                   &buf_len,
+                                   &error);
+        if (res) {
+                GMarkupParseContext *ctx;
+                GMarkupParser        parser = { territories_parse_start_tag, NULL, NULL, NULL, NULL };
+
+                ctx = g_markup_parse_context_new (&parser, 0, NULL, NULL);
+
+                error = NULL;
+                res = g_markup_parse_context_parse (ctx, buf, buf_len, &error);
+
+                if (! res) {
+                        g_warning ("Failed to parse '%s': %s\n",
+                                   ISO_CODES_DATADIR "/iso_3166.xml",
+                                   error->message);
+                        g_error_free (error);
+                }
+
+                g_markup_parse_context_free (ctx);
+                g_free (buf);
+        } else {
+                g_warning ("Failed to load '%s': %s\n",
+                           ISO_CODES_DATADIR "/iso_3166.xml",
+                           error->message);
+                g_error_free (error);
+        }
+}
+
+char *
+gdm_get_language_from_name (const char *name,
+                            const char *locale)
+{
+        GString *full_language;
+        char *language_code;
+        char *territory_code;
+        char *codeset_code;
+        char *langinfo_codeset;
+        char *translated_language;
+        char *translated_territory;
+        gboolean is_utf8 = TRUE;
+
+        g_return_val_if_fail (name != NULL, NULL);
+        g_return_val_if_fail (*name != '\0', NULL);
+
+        translated_territory = NULL;
+        translated_language = NULL;
+        langinfo_codeset = NULL;
+
+        full_language = g_string_new (NULL);
+
+        if (gdm_languages_map == NULL) {
+                languages_init ();
+        }
+
+        if (gdm_territories_map == NULL) {
+                territories_init ();
+        }
+
+        language_code = NULL;
+        territory_code = NULL;
+        codeset_code = NULL;
+
+        gdm_parse_language_name (name,
+                                 &language_code,
+                                 &territory_code,
+                                 &codeset_code,
+                                 NULL);
+
+        if (language_code == NULL) {
+                goto out;
+        }
+
+        translated_language = get_translated_language (language_code, locale);
+        if (translated_language == NULL) {
+                goto out;
+        }
+
+        full_language = g_string_append (full_language, translated_language);
+
+	if (is_unique_language (language_code)) {
+		goto out;
+	}
+
+        if (territory_code != NULL) {
+                translated_territory = get_translated_territory (territory_code, locale);
+        }
+        if (translated_territory != NULL) {
+                g_string_append_printf (full_language,
+                                        " (%s)",
+                                        translated_territory);
+        }
+
+        language_name_get_codeset_details (name, &langinfo_codeset, &is_utf8);
+
+        if (codeset_code == NULL && langinfo_codeset != NULL) {
+            codeset_code = g_strdup (langinfo_codeset);
+        }
+
+        if (!is_utf8 && codeset_code) {
+                g_string_append_printf (full_language,
+                                        " [%s]",
+                                        codeset_code);
+        }
+
+out:
+       g_free (language_code);
+       g_free (territory_code);
+       g_free (codeset_code);
+       g_free (langinfo_codeset);
+       g_free (translated_language);
+       g_free (translated_territory);
+
+       if (full_language->len == 0) {
+               g_string_free (full_language, TRUE);
+               return NULL;
+       }
+
+       return g_string_free (full_language, FALSE);
+}
+
+char *
+gdm_get_region_from_name (const char *name,
+                          const char *locale)
+{
+        GString *full_name;
+        char *language_code;
+        char *territory_code;
+        char *codeset_code;
+        char *langinfo_codeset;
+        char *translated_language;
+        char *translated_territory;
+        gboolean is_utf8 = TRUE;
+
+        g_return_val_if_fail (name != NULL, NULL);
+        g_return_val_if_fail (*name != '\0', NULL);
+
+        translated_territory = NULL;
+        translated_language = NULL;
+        langinfo_codeset = NULL;
+
+        full_name = g_string_new (NULL);
+
+        if (gdm_languages_map == NULL) {
+                languages_init ();
+        }
+
+        if (gdm_territories_map == NULL) {
+                territories_init ();
+        }
+
+        language_code = NULL;
+        territory_code = NULL;
+        codeset_code = NULL;
+
+        gdm_parse_language_name (name,
+                                 &language_code,
+                                 &territory_code,
+                                 &codeset_code,
+                                 NULL);
+
+        if (territory_code == NULL) {
+                goto out;
+        }
+
+        translated_territory = get_translated_territory (territory_code, locale);
+        g_string_append (full_name, translated_territory);
+
+	if (is_unique_territory (territory_code)) {
+		goto out;
+	}
+
+        if (language_code != NULL) {
+                translated_language = get_translated_language (language_code, locale);
+        }
+        if (translated_language != NULL) {
+                g_string_append_printf (full_name,
+                                        " (%s)",
+                                        translated_language);
+        }
+
+        language_name_get_codeset_details (name, &langinfo_codeset, &is_utf8);
+
+        if (codeset_code == NULL && langinfo_codeset != NULL) {
+            codeset_code = g_strdup (langinfo_codeset);
+        }
+
+        if (!is_utf8 && codeset_code) {
+                g_string_append_printf (full_name,
+                                        " [%s]",
+                                        codeset_code);
+        }
+
+out:
+       g_free (language_code);
+       g_free (territory_code);
+       g_free (codeset_code);
+       g_free (langinfo_codeset);
+       g_free (translated_language);
+       g_free (translated_territory);
+
+       if (full_name->len == 0) {
+               g_string_free (full_name, TRUE);
+               return NULL;
+       }
+
+       return g_string_free (full_name, FALSE);
+}
+
+char **
+gdm_get_all_language_names (void)
+{
+        GHashTableIter iter;
+        gpointer key, value;
+        GPtrArray *array;
+
+        if (gdm_available_locales_map == NULL) {
+                collect_locales ();
+        }
+
+        array = g_ptr_array_new ();
+        g_hash_table_iter_init (&iter, gdm_available_locales_map);
+        while (g_hash_table_iter_next (&iter, &key, &value)) {
+                GdmLocale *locale;
+
+                locale = (GdmLocale *) value;
+
+                g_ptr_array_add (array, g_strdup (locale->name));
+        }
+        g_ptr_array_add (array, NULL);
+
+        return (char **) g_ptr_array_free (array, FALSE);
+}
diff --git a/gnome-initial-setup/languages/gdm-languages.h b/gnome-initial-setup/languages/gdm-languages.h
new file mode 100644
index 0000000..5ee33b3
--- /dev/null
+++ b/gnome-initial-setup/languages/gdm-languages.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 8 -*-
+ *
+ * Copyright 2008 Red Hat, Inc.
+ * Copyright 2007 William Jon McCann <mccann jhu edu>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Written by: Ray Strode
+ *             William Jon McCann
+ */
+
+#ifndef __GDM_LANGUAGES_H
+#define __GDM_LANGUAGES_H
+
+G_BEGIN_DECLS
+
+char *        gdm_get_language_from_name  (const char *name,
+                                           const char *locale);
+char *        gdm_get_region_from_name    (const char *name,
+                                           const char *locale);
+char **       gdm_get_all_language_names  (void);
+gboolean      gdm_parse_language_name     (const char *name,
+                                           char      **language_codep,
+                                           char      **territory_codep,
+                                           char      **codesetp,
+                                           char      **modifierp);
+char *        gdm_normalize_language_name (const char *name);
+gboolean      gdm_language_has_translations (const char *language_name);
+
+G_END_DECLS
+
+#endif /* __GDM_LANGUAGE_CHOOSER_WIDGET_H */
diff --git a/gnome-initial-setup/languages/language-chooser.ui b/gnome-initial-setup/languages/language-chooser.ui
new file mode 100644
index 0000000..a6ef96a
--- /dev/null
+++ b/gnome-initial-setup/languages/language-chooser.ui
@@ -0,0 +1,114 @@
+<?xml version="1.0"?>
+<interface>
+  <object class="GtkDialog" id="dialog">
+    <property name="height_request">400</property>
+    <property name="border_width">5</property>
+    <property name="modal">True</property>
+    <property name="window_position">center-on-parent</property>
+    <property name="type_hint">dialog</property>
+    <property name="title"> </property>
+    <property name="icon_name">system-users</property>
+    <child internal-child="vbox">
+      <object class="GtkBox" id="content-area">
+        <property name="visible">True</property>
+        <property name="orientation">vertical</property>
+        <property name="spacing">6</property>
+	<property name="margin_left">10</property>
+   	<property name="margin_right">10</property>
+        <child>
+          <object class="GtkLabel" id="title">
+            <property name="visible">True</property>
+	    <property name="halign">start</property>
+            <property name="label" translatable="yes">Select a language</property>
+          </object>
+          <packing>
+            <property name="position">1</property>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkScrolledWindow" id="scrolledwindow1">
+            <property name="visible">True</property>
+            <property name="can_focus">True</property>
+            <property name="hscrollbar_policy">never</property>
+            <property name="vscrollbar_policy">automatic</property>
+            <property name="shadow_type">in</property>
+            <child>
+              <object class="GtkTreeView" id="language-list">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="headers_visible">False</property>
+                <property name="headers_clickable">False</property>
+                <property name="enable_search">False</property>
+              </object>
+            </child>
+          </object>
+          <packing>
+            <property name="position">2</property>
+            <property name="expand">True</property>
+            <property name="fill">True</property>
+          </packing>
+        </child>
+        <child>
+          <object class="GtkEntry" id="filter-entry">
+            <property name="visible">True</property>
+	    <property name="can_focus">True</property>
+	    <property name="secondary_icon_name">edit-find-symbolic</property>
+	    <property name="secondary_icon_activatable">False</property>
+	    <property name="secondary_icon_sensitive">False</property>
+          </object>
+          <packing>
+            <property name="position">3</property>
+            <property name="expand">False</property>
+            <property name="fill">False</property>
+          </packing>
+        </child>
+        <child internal-child="action_area">
+          <object class="GtkButtonBox" id="action-area">
+            <property name="visible">True</property>
+            <property name="layout_style">end</property>
+            <child>
+              <object class="GtkButton" id="cancel-button">
+                <property name="label" translatable="yes">_Cancel</property>
+                <property name="use_underline">True</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">0</property>
+              </packing>
+            </child>
+            <child>
+              <object class="GtkButton" id="ok-button">
+                <property name="label" translatable="yes">_Select</property>
+                <property name="use_underline">True</property>
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="can_default">True</property>
+                <property name="receives_default">True</property>
+              </object>
+              <packing>
+                <property name="expand">False</property>
+                <property name="fill">False</property>
+                <property name="position">1</property>
+              </packing>
+            </child>
+          </object>
+          <packing>
+            <property name="expand">False</property>
+            <property name="pack_type">end</property>
+            <property name="position">0</property>
+          </packing>
+        </child>
+      </object>
+    </child>
+    <action-widgets>
+      <action-widget response="-6">cancel-button</action-widget>
+      <action-widget response="-5">ok-button</action-widget>
+    </action-widgets>
+  </object>
+</interface>
diff --git a/gnome-initial-setup/languages/list-languages.c b/gnome-initial-setup/languages/list-languages.c
new file mode 100644
index 0000000..29ebc46
--- /dev/null
+++ b/gnome-initial-setup/languages/list-languages.c
@@ -0,0 +1,36 @@
+#include <glib.h>
+#include <glib-object.h>
+#include "gdm-languages.h"
+
+int main (int argc, char **argv)
+{
+	char **langs;
+	guint i;
+
+	g_type_init ();
+
+	if (argc > 1) {
+		guint i;
+		for (i = 1; i < argc; i++) {
+			char *lang;
+			lang = gdm_get_language_from_name (argv[i], NULL);
+			g_print ("%s == %s\n", argv[i], lang);
+			g_free (lang);
+		}
+		return 0;
+	}
+
+	langs = gdm_get_all_language_names ();
+	if (langs == NULL) {
+		g_warning ("No languages found");
+		return 1;
+	}
+
+	for (i = 0; langs[i] != NULL; i++)
+		g_print ("%s == %s\n", langs[i], gdm_get_language_from_name (langs[i], NULL));
+
+	g_strfreev (langs);
+
+	return 0;
+}
+
diff --git a/gnome-initial-setup/languages/locarchive.h b/gnome-initial-setup/languages/locarchive.h
new file mode 100644
index 0000000..f933f4d
--- /dev/null
+++ b/gnome-initial-setup/languages/locarchive.h
@@ -0,0 +1,97 @@
+/* Definitions for locale archive handling.
+   Copyright (C) 2002 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C 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.1 of the License, or (at your option) any later version.
+
+   The GNU C 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 the GNU C Library; if not, write to the Free
+   Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
+   02111-1307 USA.  */
+
+#ifndef _LOCARCHIVE_H
+#define _LOCARCHIVE_H 1
+
+#include <stdint.h>
+
+#define AR_MAGIC 0xde020109
+
+struct locarhead
+{
+  uint32_t magic;
+  /* Serial number.  */
+  uint32_t serial;
+  /* Name hash table.  */
+  uint32_t namehash_offset;
+  uint32_t namehash_used;
+  uint32_t namehash_size;
+  /* String table.  */
+  uint32_t string_offset;
+  uint32_t string_used;
+  uint32_t string_size;
+  /* Table with locale records.  */
+  uint32_t locrectab_offset;
+  uint32_t locrectab_used;
+  uint32_t locrectab_size;
+  /* MD5 sum hash table.  */
+  uint32_t sumhash_offset;
+  uint32_t sumhash_used;
+  uint32_t sumhash_size;
+};
+
+
+struct namehashent
+{
+  /* Hash value of the name.  */
+  uint32_t hashval;
+  /* Offset of the name in the string table.  */
+  uint32_t name_offset;
+  /* Offset of the locale record.  */
+  uint32_t locrec_offset;
+};
+
+
+struct sumhashent
+{
+  /* MD5 sum.  */
+  char sum[16];
+  /* Offset of the file in the archive.  */
+  uint32_t file_offset;
+};
+
+struct locrecent
+{
+  uint32_t refs;		/* # of namehashent records that point here */
+  struct
+  {
+    uint32_t offset;
+    uint32_t len;
+  } record[__LC_LAST];
+};
+
+
+struct locarhandle
+{
+  int fd;
+  void *addr;
+  size_t len;
+};
+
+
+/* In memory data for the locales with their checksums.  */
+typedef struct locale_category_data
+{
+  off_t size;
+  void *addr;
+  char sum[16];
+} locale_data_t[__LC_LAST];
+
+#endif	/* locarchive.h */



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