Files
gimp/app/widgets/gimplanguagestore-parser.c
Michael Natterer 539927ebfa app: replace all g_assert() by the newly added gimp_assert()
which is just a #define to g_assert for now, but can now easily be
turned into something that does some nicer debugging using our new
stack trace infrastructure. This commit also reverts all constructed()
functions to use assert again.
2018-02-11 22:23:10 +01:00

519 lines
16 KiB
C

/* GIMP - The GNU Image Manipulation Program
* Copyright (C) 1995 Spencer Kimball and Peter Mattis
*
* 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
* 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, see <http://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <locale.h>
#include <string.h>
#include <gtk/gtk.h>
#include "libgimpbase/gimpbase.h"
#include "widgets-types.h"
#include "config/gimpxmlparser.h"
#include "gimplanguagestore.h"
#include "gimplanguagestore-parser.h"
#include "gimp-intl.h"
typedef enum
{
ISO_CODES_START,
ISO_CODES_IN_ENTRIES,
ISO_CODES_IN_ENTRY,
ISO_CODES_IN_UNKNOWN
} IsoCodesParserState;
typedef struct
{
IsoCodesParserState state;
IsoCodesParserState last_known_state;
gint unknown_depth;
GHashTable *base_lang_list;
} IsoCodesParser;
static gboolean parse_iso_codes (GHashTable *base_lang_list,
GError **error);
#ifdef HAVE_ISO_CODES
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);
#endif /* HAVE_ISO_CODES */
/*
* 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;
/********************\
* Public Functions *
\********************/
/*
* 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;
GDir *locales_dir;
GError *error = NULL;
GHashTableIter lang_iter;
gpointer key;
if (l10n_lang_list != NULL)
{
g_warning ("gimp_language_store_parser_init() must be run only once.");
return;
}
current_env = g_strdup (g_getenv ("LANGUAGE"));
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;
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 = NULL;
gchar *base_code = NULL;
delimiter = strchr (locale, '_');
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);
}
g_dir_close (locales_dir);
}
/* Parse ISO-639 file to get full list of language and their names. */
parse_iso_codes (base_lang_list, &error);
/* 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 = (gchar*) key;
gchar *localized_name = NULL;
gchar *english_name = NULL;
gchar *delimiter = NULL;
gchar *base_code = NULL;
delimiter = strchr (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 = (gchar*) (g_hash_table_lookup (base_lang_list, base_code));
if (english_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);
}
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 =
{
iso_codes_parser_start_element,
iso_codes_parser_end_element,
NULL, /* characters */
NULL, /* passthrough */
NULL /* error */
};
GimpXmlParser *xml_parser;
GFile *file;
IsoCodesParser parser = { 0, };
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
iso_codes_parser_init ();
parser.base_lang_list = g_hash_table_ref (base_lang_list);
xml_parser = gimp_xml_parser_new (&markup_parser, &parser);
#if defined (G_OS_WIN32) || defined (PLATFORM_OSX)
file = gimp_data_directory_file ("..", "..",
"xml", "iso-codes", "iso_639.xml", NULL);
#else
file = g_file_new_for_path (ISO_CODES_LOCATION G_DIR_SEPARATOR_S "iso_639.xml");
#endif
success = gimp_xml_parser_parse_gfile (xml_parser, file, error);
if (error && *error)
{
g_warning ("%s: error parsing '%s': %s\n",
G_STRFUNC, g_file_get_path (file),
(*error)->message);
g_clear_error (error);
}
g_object_unref (file);
gimp_xml_parser_free (xml_parser);
g_hash_table_unref (parser.base_lang_list);
#endif /* HAVE_ISO_CODES */
return success;
}
#ifdef HAVE_ISO_CODES
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
bind_textdomain_codeset ("iso_639", "UTF-8");
initialized = TRUE;
}
static void
iso_codes_parser_entry (IsoCodesParser *parser,
const gchar **names,
const gchar **values)
{
const gchar *lang = NULL;
const gchar *code = NULL;
while (*names && *values)
{
if (strcmp (*names, "name") == 0)
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;
else if (strcmp (*names, "iso_639_2T_code") == 0 && code == NULL)
code = *values;
else if (strcmp (*names, "iso_639_1_code") == 0)
code = *values;
names++;
values++;
}
if (lang && *lang && code && *code)
{
gchar *semicolon;
gchar *localized_name = g_strdup (dgettext ("iso_639", 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 (localized_name, ';');
if (semicolon)
{
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);
}
}
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)
{
IsoCodesParser *parser = user_data;
switch (parser->state)
{
case ISO_CODES_START:
if (strcmp (element_name, "iso_639_entries") == 0)
{
parser->state = ISO_CODES_IN_ENTRIES;
break;
}
case ISO_CODES_IN_ENTRIES:
if (strcmp (element_name, "iso_639_entry") == 0)
{
parser->state = ISO_CODES_IN_ENTRY;
iso_codes_parser_entry (parser, attribute_names, attribute_values);
break;
}
case ISO_CODES_IN_ENTRY:
case ISO_CODES_IN_UNKNOWN:
iso_codes_parser_start_unknown (parser);
break;
}
}
static void
iso_codes_parser_end_element (GMarkupParseContext *context,
const gchar *element_name,
gpointer user_data,
GError **error)
{
IsoCodesParser *parser = user_data;
switch (parser->state)
{
case ISO_CODES_START:
g_warning ("%s: shouldn't get here", G_STRLOC);
break;
case ISO_CODES_IN_ENTRIES:
parser->state = ISO_CODES_START;
break;
case ISO_CODES_IN_ENTRY:
parser->state = ISO_CODES_IN_ENTRIES;
break;
case ISO_CODES_IN_UNKNOWN:
iso_codes_parser_end_unknown (parser);
break;
}
}
static void
iso_codes_parser_start_unknown (IsoCodesParser *parser)
{
if (parser->unknown_depth == 0)
parser->last_known_state = parser->state;
parser->state = ISO_CODES_IN_UNKNOWN;
parser->unknown_depth++;
}
static void
iso_codes_parser_end_unknown (IsoCodesParser *parser)
{
gimp_assert (parser->unknown_depth > 0 &&
parser->state == ISO_CODES_IN_UNKNOWN);
parser->unknown_depth--;
if (parser->unknown_depth == 0)
parser->state = parser->last_known_state;
}
#endif /* HAVE_ISO_CODES */