Commit a129f84c authored by Jehan's avatar Jehan

Bug 704592 - only load language lists once at gui startup.

Improvements:
- setenv/getenv() are not thread-safe, hence they should be run only at
startup before any threading occurs.
- it is counter-productive to load the huge ISO-639 XML file each time
the user opens the Preferences dialog or the text tool options.
parent 37195c8d
......@@ -60,6 +60,7 @@
#include "widgets/gimpsessioninfo.h"
#include "widgets/gimpuimanager.h"
#include "widgets/gimpwidgets-utils.h"
#include "widgets/gimplanguagestore-parser.h"
#include "actions/actions.h"
#include "actions/windows-commands.h"
......@@ -199,6 +200,7 @@ gui_init (Gimp *gimp,
the_gui_gimp = gimp;
gui_unique_init (gimp);
gimp_language_store_parser_init ();
gimp_widgets_init (gui_help_func,
gui_get_foreground_func,
......@@ -601,6 +603,8 @@ gui_exit_callback (Gimp *gimp,
gimp_tools_save (gimp, gui_config->save_tool_options, FALSE);
gimp_tools_exit (gimp);
gimp_language_store_parser_clean ();
return FALSE; /* continue exiting */
}
......
......@@ -3,6 +3,7 @@
*
* gimplanguagestore-parser.c
* Copyright (C) 2008, 2009 Sven Neumann <sven@gimp.org>
* Copyright (C) 2013 Jehan <jehan at girinstud.io>
*
* 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
......@@ -50,53 +51,259 @@ typedef struct
IsoCodesParserState state;
IsoCodesParserState last_known_state;
gint unknown_depth;
GimpLanguageStore *store;
GHashTable *base_lang_list;
} IsoCodesParser;
static void iso_codes_parser_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error);
static void iso_codes_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error);
static gboolean parse_iso_codes (GHashTable *base_lang_list,
GError **error);
static void iso_codes_parser_init (void);
static void iso_codes_parser_start_element (GMarkupParseContext *context,
const gchar *element_name,
const gchar **attribute_names,
const gchar **attribute_values,
gpointer user_data,
GError **error);
static void iso_codes_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error);
static void iso_codes_parser_start_unknown (IsoCodesParser *parser);
static void iso_codes_parser_end_unknown (IsoCodesParser *parser);
/*
* Language lists that we want to generate only once at program startup:
* @l10n_lang_list: all available localizations self-localized;
* @all_lang_list: all known languages, in the user-selected language.
*/
static GHashTable *l10n_lang_list = NULL;
static GHashTable *all_lang_list = NULL;
static void iso_codes_parser_start_unknown (IsoCodesParser *parser);
static void iso_codes_parser_end_unknown (IsoCodesParser *parser);
/********************\
* Public Functions *
\********************/
static void gimp_language_store_self_l10n (GimpLanguageStore *store,
const gchar *lang,
const gchar *code);
/*
* Initialize and run the language listing parser. This call must be
* made only once, at program initialization, but after language_init().
*/
void
gimp_language_store_parser_init (void)
{
GHashTable *base_lang_list;
gchar *current_env = g_strdup (g_getenv ("LANGUAGE"));
GDir *locales_dir;
GHashTableIter lang_iter;
gpointer key;
if (l10n_lang_list != NULL)
{
g_warning ("gimp_language_store_parser_init() must be run only once.");
return;
}
static void
iso_codes_parser_init (void)
{
static gboolean initialized = FALSE;
l10n_lang_list = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
all_lang_list = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
base_lang_list = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
/* Check all locales we have translations for. */
locales_dir = g_dir_open (gimp_locale_directory (), 0, NULL);
if (locales_dir)
{
const gchar *locale;
if (initialized)
return;
while ((locale = g_dir_read_name (locales_dir)) != NULL)
{
gchar *filename = g_build_filename (gimp_locale_directory (),
locale,
"LC_MESSAGES",
GETTEXT_PACKAGE ".mo",
NULL);
if (g_file_test (filename, G_FILE_TEST_EXISTS))
{
gchar *delimiter = strchr (locale, '_');
gchar *base_code = NULL;
if (delimiter)
base_code = g_strndup (locale, delimiter - locale);
else
base_code = g_strdup (locale);
delimiter = strchr (base_code, '@');
if (delimiter)
{
gchar *temp = base_code;
base_code = g_strndup (base_code, delimiter - base_code);
g_free (temp);
}
/* Save the full language code. */
g_hash_table_insert (l10n_lang_list, g_strdup (locale), NULL);
/* Save the base language code. */
g_hash_table_insert (base_lang_list, base_code, NULL);
}
g_free (filename);
}
#ifdef G_OS_WIN32
/* on Win32, assume iso-codes is installed in the same location as GIMP */
bindtextdomain ("iso_639", gimp_locale_directory ());
#else
bindtextdomain ("iso_639", ISO_CODES_LOCALEDIR);
#endif
g_dir_close (locales_dir);
}
bind_textdomain_codeset ("iso_639", "UTF-8");
/* Parse ISO-639 file to get full list of language and their names. */
parse_iso_codes (base_lang_list, NULL);
initialized = TRUE;
/* Generate the localized language names. */
g_hash_table_iter_init (&lang_iter, l10n_lang_list);
while (g_hash_table_iter_next (&lang_iter, &key, NULL))
{
gchar *code = GINT_TO_POINTER (key);
gchar *english_name;
gchar *delimiter = strchr (code, '_');
gchar *base_code;
if (delimiter)
base_code = g_strndup (code, delimiter - code);
else
base_code = g_strdup (code);
delimiter = strchr (base_code, '@');
if (delimiter)
{
gchar *temp = base_code;
base_code = g_strndup (base_code, delimiter - base_code);
g_free (temp);
}
english_name = GINT_TO_POINTER (g_hash_table_lookup (base_lang_list, base_code));
if (english_name)
{
gchar *localized_name;
gchar *semicolon;
/* If possible, we want to localize a language in itself.
* If it fails, gettext fallbacks to C (en_US) itself.
*/
g_setenv ("LANGUAGE", code, TRUE);
setlocale (LC_ALL, "");
localized_name = g_strdup (dgettext ("iso_639", english_name));
/* If original and localized names are the same for other than English,
* maybe localization failed. Try now in the main dialect. */
if (g_strcmp0 (english_name, localized_name) == 0 &&
g_strcmp0 (base_code, "en") != 0 &&
g_strcmp0 (code, base_code) != 0)
{
g_free (localized_name);
g_setenv ("LANGUAGE", base_code, TRUE);
setlocale (LC_ALL, "");
localized_name = g_strdup (dgettext ("iso_639", english_name));
}
/* there might be several language names; use the first one */
semicolon = strchr (localized_name, ';');
if (semicolon)
{
gchar *temp = localized_name;
localized_name = g_strndup (localized_name, semicolon - localized_name);
g_free (temp);
}
g_hash_table_replace (l10n_lang_list, g_strdup(code),
g_strdup_printf ("%s [%s]",
localized_name ?
localized_name : "???",
code));
g_free (localized_name);
}
g_free (base_code);
}
/* Add special entries for system locale.
* We want the system locale to be localized in itself. */
g_setenv ("LANGUAGE", setlocale (LC_ALL, NULL), TRUE);
setlocale (LC_ALL, "");
/* g_str_hash() does not accept NULL. I give an empty code instead.
* Other solution would to create a custom hash. */
g_hash_table_insert (l10n_lang_list, g_strdup(""),
g_strdup (_("System Language")));
/* Go back to original localization. */
if (current_env)
{
g_setenv ("LANGUAGE", current_env, TRUE);
g_free (current_env);
}
else
g_unsetenv ("LANGUAGE");
setlocale (LC_ALL, "");
/* Add special entry for C (en_US). */
g_hash_table_insert (l10n_lang_list, g_strdup ("en_US"),
g_strdup ("English [en_US]"));
g_hash_table_destroy (base_lang_list);
}
gboolean
gimp_language_store_parse_iso_codes (GimpLanguageStore *store,
GError **error)
void
gimp_language_store_parser_clean (void)
{
g_hash_table_destroy (l10n_lang_list);
g_hash_table_destroy (all_lang_list);
}
/*
* Returns a Hash table of languages.
* Keys and values are respectively language codes and names from the
* ISO-639 standard code.
*
* If @localization_only is TRUE, it returns only the list of available
* GIMP localizations, and language names are translated in their own
* locale.
* If @localization_only is FALSE, the full list of ISO-639 languages
* is returned, and language names are in the user-set locale.
*
* Do not free the list or elements of the list.
*/
GHashTable *
gimp_language_store_parser_get_languages (gboolean localization_only)
{
if (localization_only)
return l10n_lang_list;
else
return all_lang_list;
}
/*****************************\
* Private Parsing Functions *
\*****************************/
/*
* Parse the ISO-639 code list if available on this system, and fill
* @base_lang_list with English names of all needed base codes.
*
* It will also fill the static @all_lang_list.
*/
static gboolean
parse_iso_codes (GHashTable *base_lang_list,
GError **error)
{
gboolean success = TRUE;
#ifdef HAVE_ISO_CODES
static const GMarkupParser markup_parser =
{
......@@ -109,15 +316,13 @@ gimp_language_store_parse_iso_codes (GimpLanguageStore *store,
GimpXmlParser *xml_parser;
gchar *filename;
gboolean success;
IsoCodesParser parser = { 0, };
g_return_val_if_fail (GIMP_IS_LANGUAGE_STORE (store), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iso_codes_parser_init ();
parser.store = g_object_ref (store);
parser.base_lang_list = g_hash_table_ref (base_lang_list);
xml_parser = gimp_xml_parser_new (&markup_parser, &parser);
......@@ -134,12 +339,31 @@ gimp_language_store_parse_iso_codes (GimpLanguageStore *store,
g_free (filename);
gimp_xml_parser_free (xml_parser);
g_object_unref (parser.store);
g_hash_table_unref (parser.base_lang_list);
#endif
return success;
}
static void
iso_codes_parser_init (void)
{
static gboolean initialized = FALSE;
if (initialized)
return;
#ifdef G_OS_WIN32
/* on Win32, assume iso-codes is installed in the same location as GIMP */
bindtextdomain ("iso_639", gimp_locale_directory ());
#else
bindtextdomain ("iso_639", ISO_CODES_LOCALEDIR);
#endif
return TRUE;
bind_textdomain_codeset ("iso_639", "UTF-8");
initialized = TRUE;
}
static void
......@@ -153,101 +377,41 @@ iso_codes_parser_entry (IsoCodesParser *parser,
while (*names && *values)
{
if (strcmp (*names, "name") == 0)
{
lang = *values;
}
lang = *values;
else if (strcmp (*names, "iso_639_2B_code") == 0 && code == NULL)
{
/* 2-letter ISO 639-1 codes have priority.
* But some languages have no 2-letter code.
* Ex: Asturian (ast).
*/
code = *values;
}
/* 2-letter ISO 639-1 codes have priority.
* But some languages have no 2-letter code. Ex: Asturian (ast).
*/
code = *values;
else if (strcmp (*names, "iso_639_2T_code") == 0 && code == NULL)
{
code = *values;
}
code = *values;
else if (strcmp (*names, "iso_639_1_code") == 0)
{
code = *values;
}
code = *values;
names++;
values++;
}
/* This is a hack for some special exception.
* It seems localization won't work for the base language "zh". Probably because
* written locale dialect are too different. So we have to be accurate and localize
* separately each Chinese dialect we support.
*
* There was unfortunately no cleaner way to achieve this since there is no standardized
* link between regions in iso-3166 and base languages in iso-639, which would allow
* automatization for generating locale dialects codes.
*/
if (g_strcmp0 (code, "zh") == 0)
{
gimp_language_store_self_l10n (parser->store, "Chinese", "zh_CN");
gimp_language_store_self_l10n (parser->store, "Chinese", "zh_TW");
gimp_language_store_self_l10n (parser->store, "Chinese", "zh_HK");
}
else
gimp_language_store_self_l10n (parser->store, lang, code);
}
/* If possible, we want to localize a language in itself.
* If it fails, fallback to the currently selected language, then to system lang.
* Only fallback to C (en_US) as a last resort.
*/
static void
gimp_language_store_self_l10n (GimpLanguageStore *store,
const gchar *lang,
const gchar *code)
{
if (lang && *lang && code && *code)
{
const gchar *semicolon;
/* English does not need localization. */
if (g_strcmp0 (code, "en") != 0)
{
gchar *current_lang = g_strdup (g_getenv ("LANGUAGE"));
gchar *temp_lang;
gchar *semicolon;
gchar *localized_name = g_strdup (dgettext ("iso_639", lang));
if (current_lang)
temp_lang = g_strdup_printf ("%s:%s:%s", code, current_lang, setlocale (LC_ALL, NULL));
else
temp_lang = g_strdup (code);
/* Temporarily change the localization language. */
g_setenv ("LANGUAGE", temp_lang, TRUE);
setlocale (LC_ALL, "");
lang = dgettext ("iso_639", lang);
if (current_lang)
g_setenv ("LANGUAGE", current_lang, TRUE);
else
g_unsetenv("LANGUAGE");
setlocale (LC_ALL, "");
g_free (current_lang);
g_free (temp_lang);
}
/* If the language is in our base table, we save its standard English name. */
if (g_hash_table_contains (parser->base_lang_list, code))
g_hash_table_replace (parser->base_lang_list, g_strdup (code), g_strdup (lang));
/* there might be several language names; use the first one */
semicolon = strchr (lang, ';');
semicolon = strchr (localized_name, ';');
if (semicolon)
{
gchar *first = g_strndup (lang, semicolon - lang);
gimp_language_store_add (store, first, code);
g_free (first);
}
else
{
gimp_language_store_add (store, lang, code);
gchar *temp = localized_name;
localized_name = g_strndup (localized_name, semicolon - localized_name);
g_free (temp);
}
/* In any case, we save the name in user-set language for all lang. */
g_hash_table_insert (all_lang_list, g_strdup (code), localized_name);
}
}
......
......@@ -3,6 +3,7 @@
*
* gimplanguagestore-parser.h
* Copyright (C) 2008, 2009 Sven Neumann <sven@gimp.org>
* Copyright (C) 2013 Jehan <jehan at girinstud.io>
*
* 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
......@@ -22,8 +23,10 @@
#define __GIMP_LANGUAGE_STORE_PARSER_H__
gboolean gimp_language_store_parse_iso_codes (GimpLanguageStore *store,
GError **error);
void gimp_language_store_parser_init (void);
void gimp_language_store_parser_clean (void);
GHashTable* gimp_language_store_parser_get_languages (gboolean localization_only);
#endif /* __GIMP_LANGUAGE_STORE_PARSER_H__ */
......@@ -76,9 +76,20 @@ gimp_language_store_init (GimpLanguageStore *store)
static void
gimp_language_store_constructed (GObject *object)
{
GHashTable *lang_list;
GHashTableIter lang_iter;
gpointer code;
gpointer name;
G_OBJECT_CLASS (parent_class)->constructed (object);
gimp_language_store_parse_iso_codes (GIMP_LANGUAGE_STORE (object), NULL);
lang_list = gimp_language_store_parser_get_languages (FALSE);
g_hash_table_iter_init (&lang_iter, lang_list);
while (g_hash_table_iter_next (&lang_iter, &code, &name))
GIMP_LANGUAGE_STORE_GET_CLASS (object)->add (GIMP_LANGUAGE_STORE (object),
GINT_TO_POINTER (name),
GINT_TO_POINTER (code));
}
static void
......@@ -109,10 +120,10 @@ gimp_language_store_sort (GtkTreeModel *model,
gtk_tree_model_get_value (model, a, GIMP_LANGUAGE_STORE_CODE, &avalue);
gtk_tree_model_get_value (model, b, GIMP_LANGUAGE_STORE_CODE, &bvalue);
if (! g_value_get_string (&avalue))
if (g_strcmp0 ("", g_value_get_string (&avalue)) == 0)
cmp = -1;
if (! g_value_get_string (&bvalue))
if (g_strcmp0 ("", g_value_get_string (&bvalue)) == 0)
cmp = 1;
g_value_unset (&avalue);
......@@ -140,17 +151,6 @@ gimp_language_store_new (void)
return g_object_new (GIMP_TYPE_LANGUAGE_STORE, NULL);
}
void
gimp_language_store_add (GimpLanguageStore *store,
const gchar *label,
const gchar *code)
{
g_return_if_fail (GIMP_IS_LANGUAGE_STORE (store));
g_return_if_fail (label != NULL);
GIMP_LANGUAGE_STORE_GET_CLASS (store)->add (store, label, code);
}
gboolean
gimp_language_store_lookup (GimpLanguageStore *store,
const gchar *code,
......
......@@ -62,10 +62,4 @@ gboolean gimp_language_store_lookup (GimpLanguageStore *store,
const gchar *code,
GtkTreeIter *iter);
/* used from gimplanguagestore-parser.c */
void gimp_language_store_add (GimpLanguageStore *store,
const gchar *label,
const gchar *code);
#endif /* __GIMP_LANGUAGE_STORE_H__ */
......@@ -18,8 +18,6 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <locale.h>
#include <string.h>
......@@ -31,8 +29,6 @@
#include "gimptranslationstore.h"
#include "gimp-intl.h"
struct _GimpTranslationStoreClass
{
......@@ -42,19 +38,11 @@ struct _GimpTranslationStoreClass
struct _GimpTranslationStore
{
GimpLanguageStore parent_instance;
GHashTable *map;
};
static void gimp_translation_store_constructed (GObject *object);
static void gimp_translation_store_add (GimpLanguageStore *store,
const gchar *lang,
const gchar *code);
static void gimp_translation_store_populate (GimpTranslationStore *store);
G_DEFINE_TYPE (GimpTranslationStore, gimp_translation_store,
GIMP_TYPE_LANGUAGE_STORE)
......@@ -66,134 +54,30 @@ static void
gimp_translation_store_class_init (GimpTranslationStoreClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
GimpLanguageStoreClass *store_class = GIMP_LANGUAGE_STORE_CLASS (klass);
object_class->constructed = gimp_translation_store_constructed;
store_class->add = gimp_translation_store_add;
}
static void
gimp_translation_store_init (GimpTranslationStore *store)
{
store->map = g_hash_table_new_full (g_str_hash, g_str_equal,
(GDestroyNotify) g_free,
(GDestroyNotify) g_free);
}
static void
gimp_translation_store_constructed (GObject *object)
{
GimpTranslationStore *store = GIMP_TRANSLATION_STORE (object);
gchar *current_lang = g_strdup (g_getenv ("LANGUAGE"));
gchar *label;
G_OBJECT_CLASS (parent_class)->constructed (object);
gimp_translation_store_populate (store);
/* we don't need the map any longer */
g_hash_table_unref (store->map);
store->map = NULL;
/* add special entries for system locale and for "C".
* We want the system locale to be localized in itself. */
g_setenv ("LANGUAGE", setlocale (LC_ALL, NULL), TRUE);
setlocale (LC_ALL, "");
label = g_strdup_printf ("%s", _("System Language"));
if (current_lang)
g_setenv ("LANGUAGE", current_lang, TRUE);
else
g_unsetenv ("LANGUAGE");
setlocale (LC_ALL, "");
g_free (current_lang);
GIMP_LANGUAGE_STORE_CLASS (parent_class)->add (GIMP_LANGUAGE_STORE (store),
label,
NULL);
g_free (label);
label = g_strdup_printf ("%s [%s]", "English", "en_US");
GIMP_LANGUAGE_STORE_CLASS (parent_class)->add (GIMP_LANGUAGE_STORE (store),
label, "en_US");
g_free (label);
}
static const gchar *
gimp_translation_store_map (GimpTranslationStore *store,
const gchar *locale)
{
const gchar *lang;
/* A locale directory name is typically of the form language[_territory] */
lang = g_hash_table_lookup (store->map, locale);
if (! lang)
{
/* strip off the territory suffix */
const gchar *delimiter = strchr (locale, '_');
if (delimiter)
{
gchar *copy;
copy = g_strndup (locale, delimiter - locale);
lang = g_hash_table_lookup (store->map, copy);
g_free (copy);
}
}
return lang;
}
static void
gimp_translation_store_populate (GimpTranslationStore *store)
{
/* FIXME: this should better be done asynchronously */
GDir *dir = g_dir_open (gimp_locale_directory (), 0, NULL);
const gchar *dirname;
if (! dir)
return;
while ((dirname = g_dir_read_name (dir)) != NULL)
{
gchar *filename = g_build_filename (gimp_locale_directory (),
dirname,
"LC_MESSAGES",
GETTEXT_PACKAGE ".mo",
NULL);
if (g_file_test (filename, G_FILE_TEST_EXISTS))
{
const gchar *lang = gimp_translation_store_map (store, dirname);
if (lang)
{
GimpLanguageStore *language_store = GIMP_LANGUAGE_STORE (store);
gchar *label;
label = g_strdup_printf ("%s [%s]", lang, dirname);
GIMP_LANGUAGE_STORE_CLASS (parent_class)->add (language_store,
label, dirname);
g_free (label);
}
}
</