summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRichard Hughes <richard@hughsie.com>2016-01-26 15:56:45 +0000
committerRichard Hughes <richard@hughsie.com>2016-01-26 15:56:55 +0000
commite740ab00457cd7bbbf5a940d79a356ed0b918d1b (patch)
tree5ae1e2abd3bfd8001038bda62b873b2aa853a681
parent50d31f68ffe161195f6aff606f824bdca7805ea9 (diff)
downloadappstream-glib-e740ab00457cd7bbbf5a940d79a356ed0b918d1b.tar.gz
Support the QT translation system
Fixes: https://github.com/hughsie/appdata-tools/issues/26
-rw-r--r--client/as-compose.c47
-rw-r--r--data/tests/Makefile.am5
-rw-r--r--data/tests/kdeapp.cpp4
-rw-r--r--data/tests/kdeapp.pro3
-rw-r--r--data/tests/kdeapp_fr.ts12
-rw-r--r--data/tests/usr/share/kdeapp/translations/kdeapp_fr.qmbin0 -> 112 bytes
-rwxr-xr-xdata/tests/usr/share/locale/en_GB/LC_MESSAGES/app.mo (renamed from data/tests/locale/en_GB/LC_MESSAGES/app.mo)bin628 -> 628 bytes
-rwxr-xr-xdata/tests/usr/share/locale/ru/LC_MESSAGES/app.mo (renamed from data/tests/locale/ru/LC_MESSAGES/app.mo)bin181 -> 181 bytes
-rw-r--r--libappstream-builder/plugins/asb-plugin-gettext.c29
-rw-r--r--libappstream-glib/Makefile.am4
-rw-r--r--libappstream-glib/appstream-glib.h2
-rw-r--r--libappstream-glib/as-app-builder.c514
-rw-r--r--libappstream-glib/as-app-builder.h (renamed from libappstream-glib/as-app-gettext.h)26
-rw-r--r--libappstream-glib/as-app-gettext.c291
-rw-r--r--libappstream-glib/as-self-test.c81
-rw-r--r--libappstream-glib/as-translation.c8
-rw-r--r--libappstream-glib/as-translation.h6
17 files changed, 652 insertions, 380 deletions
diff --git a/client/as-compose.c b/client/as-compose.c
index c84c660..2ad4357 100644
--- a/client/as-compose.c
+++ b/client/as-compose.c
@@ -300,29 +300,6 @@ load_appdata (const gchar *prefix, const gchar *app_name, GError **error)
}
/**
- * get_gettext_domains:
- **/
-static gchar **
-get_gettext_domains (GPtrArray *translations)
-{
- guint i;
- guint cnt = 0;
- AsTranslation *t;
- g_auto(GStrv) intl_domains = NULL;
-
- intl_domains = g_new0 (gchar *, translations->len + 1);
- for (i = 0; i < translations->len; i++) {
- t = g_ptr_array_index (translations, i);
- if (as_translation_get_kind (t) != AS_TRANSLATION_KIND_GETTEXT)
- continue;
- intl_domains[cnt++] = g_strdup (as_translation_get_id (t));
- }
- if (cnt == 0)
- return NULL;
- return g_steal_pointer (&intl_domains);
-}
-
-/**
* main:
**/
int
@@ -419,9 +396,7 @@ main (int argc, char **argv)
/* load each application specified */
for (i = 1; i < (guint) argc; i++) {
const gchar *app_name = argv[i];
- GPtrArray *translations;
g_auto(GStrv) intl_domains = NULL;
- g_autofree gchar *locale_path = NULL;
g_autoptr(AsApp) app_appdata = NULL;
g_autoptr(AsApp) app_desktop = NULL;
@@ -438,24 +413,16 @@ main (int argc, char **argv)
}
/* set translations */
- translations = as_app_get_translations (app_appdata);
- intl_domains = get_gettext_domains (translations);
- if (intl_domains != NULL) {
- locale_path = g_build_filename (prefix,
- "share",
- "locale",
- NULL);
- if (!as_app_gettext_search_path (app_appdata,
- locale_path,
- intl_domains,
+ if (!as_app_builder_search_translations (app_appdata,
+ prefix,
25,
+ AS_APP_BUILDER_FLAG_NONE,
NULL,
&error)) {
- /* TRANSLATORS: the .mo files could not be parsed */
- g_print ("%s: %s\n", _("Error parsing translations"),
- error->message);
- return EXIT_FAILURE;
- }
+ /* TRANSLATORS: the .mo files could not be parsed */
+ g_print ("%s: %s\n", _("Error parsing translations"),
+ error->message);
+ return EXIT_FAILURE;
}
as_store_add_app (store, app_appdata);
diff --git a/data/tests/Makefile.am b/data/tests/Makefile.am
index 330bfa3..42cd208 100644
--- a/data/tests/Makefile.am
+++ b/data/tests/Makefile.am
@@ -30,8 +30,6 @@ test_files = \
font-1-1.fc21.noarch.rpm \
font-serif-1-1.fc21.noarch.rpm \
intltool.appdata.xml.in \
- locale/en_GB/LC_MESSAGES/app.mo \
- locale/ru/LC_MESSAGES/app.mo \
origin-icons.tar.gz \
origin.xml \
rpmbuild/app.png \
@@ -49,6 +47,9 @@ test_files = \
usr/share/applications/test.desktop \
usr/share/icons/hicolor/64x64/apps/test2.png \
usr/share/icons/hicolor/128x128/apps/test3.png \
+ usr/share/kdeapp/translations/kdeapp_fr.qm \
+ usr/share/locale/en_GB/LC_MESSAGES/app.mo \
+ usr/share/locale/ru/LC_MESSAGES/app.mo \
usr/share/pixmaps/test.png \
validate.xml.gz
diff --git a/data/tests/kdeapp.cpp b/data/tests/kdeapp.cpp
new file mode 100644
index 0000000..327efe8
--- /dev/null
+++ b/data/tests/kdeapp.cpp
@@ -0,0 +1,4 @@
+// lupdate-qt4 kdeapp.pro && lrelease-qt4 kdeapp.pro
+
+//TRANSLATIONS: comment
+label->setText(QObject::tr("Hello World"))
diff --git a/data/tests/kdeapp.pro b/data/tests/kdeapp.pro
new file mode 100644
index 0000000..3f2f5a4
--- /dev/null
+++ b/data/tests/kdeapp.pro
@@ -0,0 +1,3 @@
+SOURCES = kdeapp.cpp
+CODECFORSRC = UTF-8
+TRANSLATIONS = kdeapp_fr.ts
diff --git a/data/tests/kdeapp_fr.ts b/data/tests/kdeapp_fr.ts
new file mode 100644
index 0000000..02e3874
--- /dev/null
+++ b/data/tests/kdeapp_fr.ts
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE TS>
+<TS version="2.0" language="fr_FR">
+<context>
+ <name>QObject</name>
+ <message>
+ <location filename="kdeapp.cpp" line="4"/>
+ <source>Hello World</source>
+ <translation type="unfinished">Bonjour le monde</translation>
+ </message>
+</context>
+</TS>
diff --git a/data/tests/usr/share/kdeapp/translations/kdeapp_fr.qm b/data/tests/usr/share/kdeapp/translations/kdeapp_fr.qm
new file mode 100644
index 0000000..3c6397e
--- /dev/null
+++ b/data/tests/usr/share/kdeapp/translations/kdeapp_fr.qm
Binary files differ
diff --git a/data/tests/locale/en_GB/LC_MESSAGES/app.mo b/data/tests/usr/share/locale/en_GB/LC_MESSAGES/app.mo
index 985a1ce..985a1ce 100755
--- a/data/tests/locale/en_GB/LC_MESSAGES/app.mo
+++ b/data/tests/usr/share/locale/en_GB/LC_MESSAGES/app.mo
Binary files differ
diff --git a/data/tests/locale/ru/LC_MESSAGES/app.mo b/data/tests/usr/share/locale/ru/LC_MESSAGES/app.mo
index e70e55a..e70e55a 100755
--- a/data/tests/locale/ru/LC_MESSAGES/app.mo
+++ b/data/tests/usr/share/locale/ru/LC_MESSAGES/app.mo
Binary files differ
diff --git a/libappstream-builder/plugins/asb-plugin-gettext.c b/libappstream-builder/plugins/asb-plugin-gettext.c
index 1f4d4a7..bb035bc 100644
--- a/libappstream-builder/plugins/asb-plugin-gettext.c
+++ b/libappstream-builder/plugins/asb-plugin-gettext.c
@@ -39,6 +39,7 @@ void
asb_plugin_add_globs (AsbPlugin *plugin, GPtrArray *globs)
{
asb_plugin_add_glob (globs, "/usr/share/locale/*/LC_MESSAGES/*.mo");
+ asb_plugin_add_glob (globs, "/usr/share/*/translations/*.qm");
}
/**
@@ -51,20 +52,20 @@ asb_plugin_process_app (AsbPlugin *plugin,
const gchar *tmpdir,
GError **error)
{
- g_autofree gchar *root = NULL;
- g_auto(GStrv) intl_domains = NULL;
+ g_autofree gchar *prefix = NULL;
+ GPtrArray *translations;
- /* search for .mo files in the prefix */
- root = g_build_filename (tmpdir, "/usr/share/locale", NULL);
- if (!g_file_test (root, G_FILE_TEST_EXISTS))
- return TRUE;
+ /* auto-add this */
+ translations = as_app_get_translations (AS_APP (app));
+ if (translations->len == 0) {
+ g_autoptr(AsTranslation) translation = as_translation_new ();
+ as_translation_set_id (translation, asb_package_get_name (pkg));
+ as_app_add_translation (AS_APP (app), translation);
+ }
- /* generate */
- intl_domains = g_strsplit (asb_package_get_name (pkg), ",", -1);
- return as_app_gettext_search_path (AS_APP (app),
- root,
- intl_domains,
- 25,
- NULL,
- error);
+ /* search for .mo files in the prefix */
+ prefix = g_build_filename (tmpdir, "usr", NULL);
+ return as_app_builder_search_translations (AS_APP (app), prefix, 25,
+ AS_APP_BUILDER_FLAG_USE_FALLBACKS,
+ NULL, error);
}
diff --git a/libappstream-glib/Makefile.am b/libappstream-glib/Makefile.am
index 212e180..da43fe8 100644
--- a/libappstream-glib/Makefile.am
+++ b/libappstream-glib/Makefile.am
@@ -69,7 +69,7 @@ libappstream_glib_includedir = $(includedir)/libappstream-glib
libappstream_glib_include_HEADERS = \
appstream-glib.h \
as-app.h \
- as-app-gettext.h \
+ as-app-builder.h \
as-bundle.h \
as-checksum.h \
as-enums.h \
@@ -91,7 +91,7 @@ libappstream_glib_include_HEADERS = \
libappstream_glib_la_SOURCES = \
as-app.c \
as-app-desktop.c \
- as-app-gettext.c \
+ as-app-builder.c \
as-app-inf.c \
as-app-private.h \
as-app-validate.c \
diff --git a/libappstream-glib/appstream-glib.h b/libappstream-glib/appstream-glib.h
index c50eb69..26f2bea 100644
--- a/libappstream-glib/appstream-glib.h
+++ b/libappstream-glib/appstream-glib.h
@@ -25,7 +25,7 @@
#define __APPSTREAM_GLIB_H_INSIDE__
#include <as-app.h>
-#include <as-app-gettext.h>
+#include <as-app-builder.h>
#include <as-bundle.h>
#include <as-checksum.h>
#include <as-enums.h>
diff --git a/libappstream-glib/as-app-builder.c b/libappstream-glib/as-app-builder.c
new file mode 100644
index 0000000..8e0cea3
--- /dev/null
+++ b/libappstream-glib/as-app-builder.c
@@ -0,0 +1,514 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
+ *
+ * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
+ *
+ * Licensed under the GNU Lesser General Public License Version 2.1
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2.1 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ */
+
+/**
+ * SECTION:as-app-builder
+ * @short_description: Scan the filesystem for installed languages
+ * @include: appstream-glib.h
+ * @stability: Stable
+ *
+ * This object will parse a gettext catalog directory and calculate the
+ * language stats for an application.
+ *
+ * See also: #AsApp
+ */
+
+#include "config.h"
+
+#include <fnmatch.h>
+#include <string.h>
+
+#include "as-app-builder.h"
+
+typedef struct {
+ gchar *locale;
+ guint nstrings;
+ guint percentage;
+} AsAppBuilderEntry;
+
+typedef struct {
+ guint max_nstrings;
+ GList *data;
+ GPtrArray *translations; /* no ref */
+} AsAppBuilderContext;
+
+/**
+ * as_app_builder_entry_new:
+ **/
+static AsAppBuilderEntry *
+as_app_builder_entry_new (void)
+{
+ AsAppBuilderEntry *entry;
+ entry = g_slice_new0 (AsAppBuilderEntry);
+ return entry;
+}
+
+/**
+ * as_app_builder_entry_free:
+ **/
+static void
+as_app_builder_entry_free (AsAppBuilderEntry *entry)
+{
+ g_free (entry->locale);
+ g_slice_free (AsAppBuilderEntry, entry);
+}
+
+/**
+ * as_app_builder_ctx_new:
+ **/
+static AsAppBuilderContext *
+as_app_builder_ctx_new (void)
+{
+ AsAppBuilderContext *ctx;
+ ctx = g_new0 (AsAppBuilderContext, 1);
+ return ctx;
+}
+
+/**
+ * as_app_builder_ctx_free:
+ **/
+static void
+as_app_builder_ctx_free (AsAppBuilderContext *ctx)
+{
+ g_list_free_full (ctx->data, (GDestroyNotify) as_app_builder_entry_free);
+ g_free (ctx);
+}
+
+typedef struct {
+ guint32 magic;
+ guint32 revision;
+ guint32 nstrings;
+ guint32 orig_tab_offset;
+ guint32 trans_tab_offset;
+ guint32 hash_tab_size;
+ guint32 hash_tab_offset;
+ guint32 n_sysdep_segments;
+ guint32 sysdep_segments_offset;
+ guint32 n_sysdep_strings;
+ guint32 orig_sysdep_tab_offset;
+ guint32 trans_sysdep_tab_offset;
+} AsAppBuilderGettextHeader;
+
+/**
+ * as_app_builder_parse_file_gettext:
+ **/
+static gboolean
+as_app_builder_parse_file_gettext (AsAppBuilderContext *ctx,
+ const gchar *locale,
+ const gchar *filename,
+ GError **error)
+{
+ AsAppBuilderEntry *entry;
+ AsAppBuilderGettextHeader *h;
+ g_autofree gchar *data = NULL;
+ gboolean swapped;
+
+ /* read data, although we only strictly need the header */
+ if (!g_file_get_contents (filename, &data, NULL, error))
+ return FALSE;
+
+ h = (AsAppBuilderGettextHeader *) data;
+ if (h->magic == 0x950412de)
+ swapped = FALSE;
+ else if (h->magic == 0xde120495)
+ swapped = TRUE;
+ else {
+ g_set_error_literal (error,
+ AS_APP_ERROR,
+ AS_APP_ERROR_FAILED,
+ "file is invalid");
+ return FALSE;
+ }
+ entry = as_app_builder_entry_new ();
+ entry->locale = g_strdup (locale);
+ if (swapped)
+ entry->nstrings = GUINT32_SWAP_LE_BE (h->nstrings);
+ else
+ entry->nstrings = h->nstrings;
+ if (entry->nstrings > ctx->max_nstrings)
+ ctx->max_nstrings = entry->nstrings;
+ ctx->data = g_list_prepend (ctx->data, entry);
+ return TRUE;
+}
+
+/**
+ * as_app_builder_search_locale_gettext:
+ **/
+static gboolean
+as_app_builder_search_locale_gettext (AsAppBuilderContext *ctx,
+ const gchar *locale,
+ const gchar *messages_path,
+ AsAppBuilderFlags flags,
+ GError **error)
+{
+ const gchar *filename;
+ gboolean found_anything = FALSE;
+ guint i;
+ g_autoptr(GDir) dir = NULL;
+ g_autoptr(GPtrArray) mo_paths = NULL;
+
+ /* list files */
+ dir = g_dir_open (messages_path, 0, error);
+ if (dir == NULL)
+ return FALSE;
+
+ /* do a first pass at this, trying to find the prefered .mo */
+ mo_paths = g_ptr_array_new_with_free_func (g_free);
+ while ((filename = g_dir_read_name (dir)) != NULL) {
+ g_autofree gchar *path = NULL;
+ path = g_build_filename (messages_path, filename, NULL);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ continue;
+ for (i = 0; i < ctx->translations->len; i++) {
+ AsTranslation *t = g_ptr_array_index (ctx->translations, i);
+ g_autofree gchar *fn = NULL;
+ if (as_translation_get_kind (t) != AS_TRANSLATION_KIND_GETTEXT &&
+ as_translation_get_kind (t) != AS_TRANSLATION_KIND_UNKNOWN)
+ continue;
+ fn = g_strdup_printf ("%s.mo", as_translation_get_id (t));
+ if (g_strcmp0 (filename, fn) == 0) {
+ if (!as_app_builder_parse_file_gettext (ctx,
+ locale,
+ path,
+ error))
+ return FALSE;
+ found_anything = TRUE;
+ }
+ }
+ g_ptr_array_add (mo_paths, g_strdup (path));
+ }
+
+ /* we got data from one or more of the translations */
+ if (found_anything == TRUE)
+ return TRUE;
+
+ /* fall back to parsing *everything*, which might give us more
+ * language results than is actually true */
+ if (flags & AS_APP_BUILDER_FLAG_USE_FALLBACKS) {
+ for (i = 0; i < mo_paths->len; i++) {
+ filename = g_ptr_array_index (mo_paths, i);
+ if (!as_app_builder_parse_file_gettext (ctx,
+ locale,
+ filename,
+ error))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+typedef enum {
+ AS_APP_TRANSLATION_QM_TAG_END = 1,
+ /* SourceText16 */
+ AS_APP_TRANSLATION_QM_TAG_TRANSLATION = 3,
+ /* Context16 */
+ AS_APP_TRANSLATION_QM_TAG_OBSOLETE1 = 5,
+ AS_APP_TRANSLATION_QM_TAG_SOURCE_TEXT = 6,
+ AS_APP_TRANSLATION_QM_TAG_CONTEXT = 7,
+ AS_APP_TRANSLATION_QM_TAG_COMMENT = 8,
+ /* Obsolete2 */
+ AS_APP_TRANSLATION_QM_TAG_LAST
+} AsAppBuilderQmTag;
+
+static guint8
+_read_uint8 (const guint8 *data, guint32 *offset)
+{
+ guint8 tmp;
+ tmp = data[*offset];
+ (*offset) += 1;
+ return tmp;
+}
+
+static guint32
+_read_uint32 (const guint8 *data, guint32 *offset)
+{
+ guint32 tmp = 0;
+ memcpy (&tmp, data + *offset, 4);
+ (*offset) += 4;
+ return GUINT32_FROM_BE (tmp);
+}
+
+/**
+ * as_app_builder_parse_file_qt:
+ **/
+static gboolean
+as_app_builder_parse_file_qt (AsAppBuilderContext *ctx,
+ const gchar *locale,
+ const gchar *filename,
+ GError **error)
+{
+ AsAppBuilderEntry *entry;
+ guint32 addr = 0;
+ guint32 len;
+ guint32 m = 0;
+ guint nstrings = 0;
+ g_autofree guint8 *data = NULL;
+ const guint8 qm_magic[] = {
+ 0x3c, 0xb8, 0x64, 0x18, 0xca, 0xef, 0x9c, 0x95,
+ 0xcd, 0x21, 0x1c, 0xbf, 0x60, 0xa1, 0xbd, 0xdd
+ };
+
+ /* load file */
+ if (!g_file_get_contents (filename, (gchar **) &data, (gsize *) &len, error))
+ return FALSE;
+
+ /* check header */
+ if (len < sizeof(qm_magic) ||
+ memcmp (data, qm_magic, sizeof(qm_magic)) != 0) {
+ g_set_error_literal (error,
+ AS_APP_ERROR,
+ AS_APP_ERROR_FAILED,
+ "file is invalid");
+ return FALSE;
+ }
+ m += sizeof(qm_magic);
+
+ /* unknown value 0x42? */
+ _read_uint8 (data, &m);
+
+ /* find offset to data table */
+ addr = _read_uint32 (data, &m);
+ m += addr;
+
+ /* unknown! */
+ _read_uint8 (data, &m);
+ _read_uint32 (data, &m);
+ //g_debug ("seeking to QM @ %x\n", m);
+
+ /* read data */
+ while (m < len) {
+ guint8 tag;
+ guint32 tag_len;
+ //g_debug ("QM @%x", m);
+ tag = _read_uint8(data, &m);
+ switch (tag) {
+ case AS_APP_TRANSLATION_QM_TAG_END:
+ //g_debug ("QM{END}");
+ break;
+ case AS_APP_TRANSLATION_QM_TAG_OBSOLETE1:
+ m += 4;
+ break;
+ case AS_APP_TRANSLATION_QM_TAG_TRANSLATION:
+ tag_len = _read_uint32 (data, &m);
+ if (tag_len < 0xffffffff)
+ m += tag_len;
+ //g_debug ("QM{TRANSLATION} len %i", tag_len);
+ nstrings++;
+ break;
+ case AS_APP_TRANSLATION_QM_TAG_SOURCE_TEXT:
+ tag_len = _read_uint32 (data, &m);
+ m += tag_len;
+ //g_debug ("QM{SOURCE_TEXT} len %i", tag_len);
+ break;
+ case AS_APP_TRANSLATION_QM_TAG_CONTEXT:
+ tag_len = _read_uint32 (data, &m);
+ m += tag_len;
+ //g_debug ("QM{CONTEXT} len %i", tag_len);
+ break;
+ case AS_APP_TRANSLATION_QM_TAG_COMMENT:
+ tag_len = _read_uint32 (data, &m);
+ m += tag_len;
+ //g_debug ("QM{COMMENT} len %i", tag_len);
+ break;
+ default:
+ //g_debug ("QM{unknown} tag kind %i", tag);
+ m = G_MAXUINT32;
+ break;
+ }
+ }
+
+// g_debug ("for QT locale %s, nstrings=%i", locale, nstrings);
+
+ /* add new entry */
+ entry = as_app_builder_entry_new ();
+ entry->locale = g_strdup (locale);
+ entry->nstrings = nstrings;
+ if (entry->nstrings > ctx->max_nstrings)
+ ctx->max_nstrings = entry->nstrings;
+ ctx->data = g_list_prepend (ctx->data, entry);
+ return TRUE;
+}
+
+/**
+ * as_app_builder_search_translations_qt:
+ **/
+static gboolean
+as_app_builder_search_translations_qt (AsAppBuilderContext *ctx,
+ const gchar *prefix,
+ AsAppBuilderFlags flags,
+ GError **error)
+{
+ guint i;
+
+ /* search for each translation ID */
+ for (i = 0; i < ctx->translations->len; i++) {
+ AsTranslation *t;
+ const gchar *filename;
+ const gchar *install_dir;
+ g_autofree gchar *path = NULL;
+ g_autoptr(GDir) dir = NULL;
+
+ /* FIXME: this path probably has to be specified as an attribute
+ * in the <translations> tag from the AppData file */
+ t = g_ptr_array_index (ctx->translations, i);
+ install_dir = as_translation_get_id (t);
+ path = g_build_filename (prefix,
+ "share",
+ install_dir,
+ "translations",
+ NULL);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ return TRUE;
+ dir = g_dir_open (path, 0, error);
+ if (dir == NULL)
+ return FALSE;
+
+ /* the format is ${prefix}/share/${install_dir}/translations/${id}_${locale}.qm */
+ while ((filename = g_dir_read_name (dir)) != NULL) {
+ g_autofree gchar *fn = NULL;
+ g_autofree gchar *locale = NULL;
+ if (as_translation_get_kind (t) != AS_TRANSLATION_KIND_QT &&
+ as_translation_get_kind (t) != AS_TRANSLATION_KIND_UNKNOWN)
+ continue;
+ if (!g_str_has_prefix (filename, as_translation_get_id (t)))
+ continue;
+ locale = g_strdup (filename + strlen (as_translation_get_id (t)) + 1);
+ g_strdelimit (locale, ".", '\0');
+ fn = g_build_filename (path, filename, NULL);
+ if (!as_app_builder_parse_file_qt (ctx, locale, fn, error))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+/**
+ * as_app_builder_search_translations_gettext:
+ **/
+static gboolean
+as_app_builder_search_translations_gettext (AsAppBuilderContext *ctx,
+ const gchar *prefix,
+ AsAppBuilderFlags flags,
+ GError **error)
+{
+ const gchar *locale;
+ g_autofree gchar *path = NULL;
+ g_autoptr(GDir) dir = NULL;
+
+ path = g_build_filename (prefix, "share", "locale", NULL);
+ if (!g_file_test (path, G_FILE_TEST_EXISTS))
+ return TRUE;
+ dir = g_dir_open (path, 0, error);
+ if (dir == NULL)
+ return FALSE;
+ while ((locale = g_dir_read_name (dir)) != NULL) {
+ g_autofree gchar *fn = NULL;
+ fn = g_build_filename (path, locale, "LC_MESSAGES", NULL);
+ if (!g_file_test (fn, G_FILE_TEST_EXISTS))
+ continue;
+ if (!as_app_builder_search_locale_gettext (ctx, locale, fn, flags, error))
+ return FALSE;
+ }
+ return TRUE;
+}
+
+/**
+ * as_app_builder_entry_sort_cb:
+ **/
+static gint
+as_app_builder_entry_sort_cb (gconstpointer a, gconstpointer b)
+{
+ return g_strcmp0 (((AsAppBuilderEntry *) a)->locale,
+ ((AsAppBuilderEntry *) b)->locale);
+}
+
+G_DEFINE_AUTOPTR_CLEANUP_FUNC(AsAppBuilderContext, as_app_builder_ctx_free)
+
+/**
+ * as_app_builder_search_translations:
+ * @app: an #AsApp
+ * @prefix: a prefix to search, e.g. "/usr"
+ * @min_percentage: minimum percentage to add language
+ * @flags: #AsAppBuilderFlags, e.g. %AS_APP_BUILDER_FLAG_USE_FALLBACKS
+ * @cancellable: a #GCancellable or %NULL
+ * @error: a #GError or %NULL
+ *
+ * Searches a prefix for languages, and using a heuristic adds <language>
+ * tags to the specified application.
+ *
+ * If there are no #AsTranslation objects set on the #AsApp then all domains
+ * are matched, which may include more languages than you intended to.
+ *
+ * @min_percentage sets the minimum percentage to add a language tag.
+ * The usual value would be 25% and any language less complete than
+ * this will not be added.
+ *
+ * The purpose of this functionality is to avoid blowing up the size
+ * of the AppStream metadata with a lot of extra data detailing
+ * languages with very few translated strings.
+ *
+ * Returns: %TRUE for success
+ *
+ * Since: 0.5.8
+ **/
+gboolean
+as_app_builder_search_translations (AsApp *app,
+ const gchar *prefix,
+ guint min_percentage,
+ AsAppBuilderFlags flags,
+ GCancellable *cancellable,
+ GError **error)
+{
+ AsAppBuilderEntry *e;
+ GList *l;
+ g_autoptr(AsAppBuilderContext) ctx = NULL;
+
+ ctx = as_app_builder_ctx_new ();
+ ctx->translations = as_app_get_translations (app);
+
+ /* search for QT .qm files */
+ if (!as_app_builder_search_translations_qt (ctx, prefix, flags, error))
+ return FALSE;
+
+ /* search for gettext .mo files */
+ if (!as_app_builder_search_translations_gettext (ctx, prefix, flags, error))
+ return FALSE;
+
+ /* calculate percentages */
+ for (l = ctx->data; l != NULL; l = l->next) {
+ e = l->data;
+ e->percentage = MIN (e->nstrings * 100 / ctx->max_nstrings, 100);
+ }
+
+ /* sort */
+ ctx->data = g_list_sort (ctx->data, as_app_builder_entry_sort_cb);
+
+ /* add results */
+ for (l = ctx->data; l != NULL; l = l->next) {
+ e = l->data;
+ if (e->percentage < min_percentage)
+ continue;
+ as_app_add_language (app, e->percentage, e->locale);
+ }
+ return TRUE;
+}
diff --git a/libappstream-glib/as-app-gettext.h b/libappstream-glib/as-app-builder.h
index 432e1be..9d0166f 100644
--- a/libappstream-glib/as-app-gettext.h
+++ b/libappstream-glib/as-app-builder.h
@@ -33,12 +33,26 @@
G_BEGIN_DECLS
-gboolean as_app_gettext_search_path (AsApp *app,
- const gchar *path,
- gchar **intl_domains,
- guint min_percentage,
- GCancellable *cancellable,
- GError **error);
+/**
+ * AsAppBuilderFlags:
+ * @AS_APP_BUILDER_FLAG_NONE: No special actions to use
+ * @AS_APP_BUILDER_FLAG_USE_FALLBACKS: Fall back to guesses where required
+ *
+ * The flags to use when building applications.
+ **/
+typedef enum {
+ AS_APP_BUILDER_FLAG_NONE,
+ AS_APP_BUILDER_FLAG_USE_FALLBACKS = 1, /* Since: 0.5.8 */
+ /*< private >*/
+ AS_APP_BUILDER_FLAG_LAST,
+} AsAppBuilderFlags;
+
+gboolean as_app_builder_search_translations (AsApp *app,
+ const gchar *prefix,
+ guint min_percentage,
+ AsAppBuilderFlags flags,
+ GCancellable *cancellable,
+ GError **error);
G_END_DECLS
diff --git a/libappstream-glib/as-app-gettext.c b/libappstream-glib/as-app-gettext.c
deleted file mode 100644
index 8283bef..0000000
--- a/libappstream-glib/as-app-gettext.c
+++ /dev/null
@@ -1,291 +0,0 @@
-/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*-
- *
- * Copyright (C) 2015 Richard Hughes <richard@hughsie.com>
- *
- * Licensed under the GNU Lesser General Public License Version 2.1
- *
- * This library is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 2.1 of the License, or (at your option) any later version.
- *
- * This library is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with this library; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- */
-
-/**
- * SECTION:as-gettext
- * @short_description: a hashed array gettext of applications
- * @include: appstream-glib.h
- * @stability: Stable
- *
- * This object will parse a gettext catalog directory and calculate the
- * language stats for an application.
- *
- * See also: #AsApp
- */
-
-#include "config.h"
-
-#include <fnmatch.h>
-
-#include "as-app-gettext.h"
-
-typedef struct {
-} AsGettextPrivate;
-
-typedef struct {
- guint32 magic;
- guint32 revision;
- guint32 nstrings;
- guint32 orig_tab_offset;
- guint32 trans_tab_offset;
- guint32 hash_tab_size;
- guint32 hash_tab_offset;
- guint32 n_sysdep_segments;
- guint32 sysdep_segments_offset;
- guint32 n_sysdep_strings;
- guint32 orig_sysdep_tab_offset;
- guint32 trans_sysdep_tab_offset;
-} AsGettextHeader;
-
-typedef struct {
- gchar *locale;
- guint nstrings;
- guint percentage;
-} AsGettextEntry;
-
-typedef struct {
- guint max_nstrings;
- GList *data;
- gchar **intl_domains;
- guint min_percentage;
-} AsGettextContext;
-
-/**
- * as_gettext_entry_new:
- **/
-static AsGettextEntry *
-as_gettext_entry_new (void)
-{
- AsGettextEntry *entry;
- entry = g_slice_new0 (AsGettextEntry);
- return entry;
-}
-
-/**
- * as_gettext_entry_free:
- **/
-static void
-as_gettext_entry_free (AsGettextEntry *entry)
-{
- g_free (entry->locale);
- g_slice_free (AsGettextEntry, entry);
-}
-
-/**
- * as_gettext_ctx_new:
- **/
-static AsGettextContext *
-as_gettext_ctx_new (void)
-{
- AsGettextContext *ctx;
- ctx = g_new0 (AsGettextContext, 1);
- return ctx;
-}
-
-/**
- * as_gettext_ctx_free:
- **/
-static void
-as_gettext_ctx_free (AsGettextContext *ctx)
-{
- g_list_free_full (ctx->data, (GDestroyNotify) as_gettext_entry_free);
- g_strfreev (ctx->intl_domains);
- g_free (ctx);
-}
-
-/**
- * as_gettext_parse_file:
- **/
-static gboolean
-as_gettext_parse_file (AsGettextContext *ctx,
- const gchar *locale,
- const gchar *filename,
- GError **error)
-{
- AsGettextEntry *entry;
- AsGettextHeader *h;
- g_autofree gchar *data = NULL;
- gboolean swapped;
-
- /* read data, although we only strictly need the header */
- if (!g_file_get_contents (filename, &data, NULL, error))
- return FALSE;
-
- h = (AsGettextHeader *) data;
- if (h->magic == 0x950412de)
- swapped = FALSE;
- else if (h->magic == 0xde120495)
- swapped = TRUE;
- else
- return FALSE;
- entry = as_gettext_entry_new ();
- entry->locale = g_strdup (locale);
- if (swapped)
- entry->nstrings = GUINT32_SWAP_LE_BE (h->nstrings);
- else
- entry->nstrings = h->nstrings;
- if (entry->nstrings > ctx->max_nstrings)
- ctx->max_nstrings = entry->nstrings;
- ctx->data = g_list_prepend (ctx->data, entry);
- return TRUE;
-}
-
-/**
- * as_gettext_ctx_search_locale:
- **/
-static gboolean
-as_gettext_ctx_search_locale (AsGettextContext *ctx,
- const gchar *locale,
- const gchar *messages_path,
- GError **error)
-{
- const gchar *filename;
- gboolean found_anything = FALSE;
- guint i;
- g_autoptr(GDir) dir = NULL;
- g_autoptr(GPtrArray) mo_paths = NULL;
-
- /* list files */
- dir = g_dir_open (messages_path, 0, error);
- if (dir == NULL)
- return FALSE;
-
- /* do a first pass at this, trying to find the prefered .mo */
- mo_paths = g_ptr_array_new_with_free_func (g_free);
- while ((filename = g_dir_read_name (dir)) != NULL) {
- g_autofree gchar *path = NULL;
- path = g_build_filename (messages_path, filename, NULL);
- if (!g_file_test (path, G_FILE_TEST_EXISTS))
- continue;
- for (i = 0; ctx->intl_domains != NULL &&
- ctx->intl_domains[i] != NULL; i++) {
- g_autofree gchar *fn = NULL;
- fn = g_strdup_printf ("%s.mo", ctx->intl_domains[i]);
- if (g_strcmp0 (filename, fn) == 0) {
- if (!as_gettext_parse_file (ctx, locale, path, error))
- return FALSE;
- found_anything = TRUE;
- }
- }
- g_ptr_array_add (mo_paths, g_strdup (path));
- }
-
- /* we got data from one or more of the intl_domains */
- if (found_anything == TRUE)
- return TRUE;
-
- /* fall back to parsing *everything*, which might give us more
- * language results than is actually true */
- for (i = 0; i < mo_paths->len; i++) {
- filename = g_ptr_array_index (mo_paths, i);
- if (!as_gettext_parse_file (ctx, locale, filename, error))
- return FALSE;
- }
-
- return TRUE;
-}
-
-/**
- * as_gettext_entry_sort_cb:
- **/
-static gint
-as_gettext_entry_sort_cb (gconstpointer a, gconstpointer b)
-{
- return g_strcmp0 (((AsGettextEntry *) a)->locale,
- ((AsGettextEntry *) b)->locale);
-}
-
-G_DEFINE_AUTOPTR_CLEANUP_FUNC(AsGettextContext, as_gettext_ctx_free)
-
-/**
- * as_app_gettext_search_path:
- * @app: an #AsApp
- * @path: a path to search, e.g. "/usr/share/locale"
- * @intl_domains: (allow-none): gettext domains, e.g. ["gnome-software"]
- * @min_percentage: minimum percentage to add language
- * @cancellable: a #GCancellable or %NULL
- * @error: a #GError or %NULL
- *
- * Searches a gettext catalog path for languages, and using a heuristic
- * adds <language> tags to the specified application.
- *
- * If @intl_domains is not set then all domains are matched, which may
- * include more languages than you intended to.
- *
- * @min_percentage sets the minimum percentage to add a language tag.
- * The usual value would be 25% and any language less complete than
- * this will not be added.
- *
- * The purpose of this functionality is to avoid blowing up the size
- * of the AppStream metadata with a lot of extra data detailing
- * languages with very few translated strings.
- *
- * Returns: %TRUE for success
- *
- * Since: 0.5.6
- **/
-gboolean
-as_app_gettext_search_path (AsApp *app,
- const gchar *path,
- gchar **intl_domains,
- guint min_percentage,
- GCancellable *cancellable,
- GError **error)
-{
- AsGettextEntry *e;
- GList *l;
- const gchar *filename;
- g_autoptr(AsGettextContext) ctx = NULL;
- g_autoptr(GDir) dir = NULL;
-
- dir = g_dir_open (path, 0, error);
- if (dir == NULL)
- return FALSE;
- ctx = as_gettext_ctx_new ();
- ctx->min_percentage = min_percentage;
- ctx->intl_domains = g_strdupv (intl_domains);
- while ((filename = g_dir_read_name (dir)) != NULL) {
- g_autofree gchar *fn = NULL;
- fn = g_build_filename (path, filename, "LC_MESSAGES", NULL);
- if (g_file_test (fn, G_FILE_TEST_EXISTS)) {
- if (!as_gettext_ctx_search_locale (ctx, filename, fn, error))
- return FALSE;
- }
- }
-
- /* calculate percentages */
- for (l = ctx->data; l != NULL; l = l->next) {
- e = l->data;
- e->percentage = MIN (e->nstrings * 100 / ctx->max_nstrings, 100);
- }
-
- /* sort */
- ctx->data = g_list_sort (ctx->data, as_gettext_entry_sort_cb);
-
- /* add results */
- for (l = ctx->data; l != NULL; l = l->next) {
- e = l->data;
- if (e->percentage < ctx->min_percentage)
- continue;
- as_app_add_language (app, e->percentage, e->locale);
- }
- return TRUE;
-}
diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c
index b79e219..81ccc81 100644
--- a/libappstream-glib/as-self-test.c
+++ b/libappstream-glib/as-self-test.c
@@ -27,7 +27,7 @@
#include <fnmatch.h>
#include "as-app-private.h"
-#include "as-app-gettext.h"
+#include "as-app-builder.h"
#include "as-bundle-private.h"
#include "as-translation-private.h"
#include "as-checksum-private.h"
@@ -327,25 +327,29 @@ as_test_monitor_file_func (void)
}
static void
-as_test_app_gettext_func (void)
+as_test_app_builder_gettext_func (void)
{
GError *error = NULL;
gboolean ret;
+ guint i;
g_autofree gchar *fn = NULL;
g_autoptr(AsApp) app = NULL;
g_autoptr(GList) list = NULL;
- g_auto(GStrv) intl_domains = NULL;
+ const gchar *gettext_domains[] = { "app", "notgoingtoexist", NULL };
app = as_app_new ();
- fn = as_test_get_filename ("locale");
+ fn = as_test_get_filename ("usr");
g_assert (fn != NULL);
- intl_domains = g_strsplit ("app,notgoingtoexist", ",", -1);
- ret = as_app_gettext_search_path (app,
- fn,
- intl_domains,
- 25,
- NULL,
- &error);
+ for (i = 0; gettext_domains[i] != NULL; i++) {
+ g_autoptr(AsTranslation) translation = NULL;
+ translation = as_translation_new ();
+ as_translation_set_kind (translation, AS_TRANSLATION_KIND_GETTEXT);
+ as_translation_set_id (translation, gettext_domains[i]);
+ as_app_add_translation (app, translation);
+ }
+ ret = as_app_builder_search_translations (app, fn, 25,
+ AS_APP_BUILDER_FLAG_NONE,
+ NULL, &error);
g_assert_no_error (error);
g_assert (ret);
@@ -360,23 +364,19 @@ as_test_app_gettext_func (void)
}
static void
-as_test_app_gettext_nodomain_func (void)
+as_test_app_builder_gettext_nodomain_func (void)
{
GError *error = NULL;
gboolean ret;
g_autofree gchar *fn = NULL;
g_autoptr(AsApp) app = NULL;
g_autoptr(GList) list = NULL;
- g_auto(GStrv) intl_domains = NULL;
app = as_app_new ();
- fn = as_test_get_filename ("locale");
- ret = as_app_gettext_search_path (app,
- fn,
- NULL,
- 50,
- NULL,
- &error);
+ fn = as_test_get_filename ("usr");
+ ret = as_app_builder_search_translations (app, fn, 50,
+ AS_APP_BUILDER_FLAG_USE_FALLBACKS,
+ NULL, &error);
g_assert_no_error (error);
g_assert (ret);
@@ -391,6 +391,42 @@ as_test_app_gettext_nodomain_func (void)
}
static void
+as_test_app_builder_qt_func (void)
+{
+ GError *error = NULL;
+ gboolean ret;
+ guint i;
+ g_autofree gchar *fn = NULL;
+ g_autoptr(AsApp) app = NULL;
+ g_autoptr(GList) list = NULL;
+ const gchar *gettext_domains[] = { "kdeapp", "notgoingtoexist", NULL };
+
+ app = as_app_new ();
+ fn = as_test_get_filename ("usr");
+ g_assert (fn != NULL);
+ for (i = 0; gettext_domains[i] != NULL; i++) {
+ g_autoptr(AsTranslation) translation = NULL;
+ translation = as_translation_new ();
+ as_translation_set_kind (translation, AS_TRANSLATION_KIND_QT);
+ as_translation_set_id (translation, gettext_domains[i]);
+ as_app_add_translation (app, translation);
+ }
+ ret = as_app_builder_search_translations (app, fn, 25,
+ AS_APP_BUILDER_FLAG_NONE,
+ NULL, &error);
+ g_assert_no_error (error);
+ g_assert (ret);
+
+ /* check langs */
+ g_assert_cmpint (as_app_get_language (app, "fr"), ==, 100);
+ g_assert_cmpint (as_app_get_language (app, "en_GB"), ==, -1);
+
+ /* check size */
+ list = as_app_get_languages (app);
+ g_assert_cmpint (g_list_length (list), ==, 1);
+}
+
+static void
as_test_tag_func (void)
{
guint i;
@@ -4564,8 +4600,9 @@ main (int argc, char **argv)
g_test_add_func ("/AppStream/image{alpha}", as_test_image_alpha_func);
g_test_add_func ("/AppStream/screenshot", as_test_screenshot_func);
g_test_add_func ("/AppStream/app", as_test_app_func);
- g_test_add_func ("/AppStream/app{gettext}", as_test_app_gettext_func);
- g_test_add_func ("/AppStream/app{gettext-nodomain}", as_test_app_gettext_nodomain_func);
+ g_test_add_func ("/AppStream/app{builder:gettext}", as_test_app_builder_gettext_func);
+ g_test_add_func ("/AppStream/app{builder:gettext-nodomain}", as_test_app_builder_gettext_nodomain_func);
+ g_test_add_func ("/AppStream/app{builder:qt}", as_test_app_builder_qt_func);
g_test_add_func ("/AppStream/app{translated}", as_test_app_translated_func);
g_test_add_func ("/AppStream/app{validate-style}", as_test_app_validate_style_func);
g_test_add_func ("/AppStream/app{validate-appdata-good}", as_test_app_validate_appdata_good_func);
diff --git a/libappstream-glib/as-translation.c b/libappstream-glib/as-translation.c
index 79a9675..9a19d56 100644
--- a/libappstream-glib/as-translation.c
+++ b/libappstream-glib/as-translation.c
@@ -98,6 +98,8 @@ as_translation_kind_from_string (const gchar *kind)
{
if (g_strcmp0 (kind, "gettext") == 0)
return AS_TRANSLATION_KIND_GETTEXT;
+ if (g_strcmp0 (kind, "qt") == 0)
+ return AS_TRANSLATION_KIND_QT;
return AS_TRANSLATION_KIND_UNKNOWN;
}
@@ -116,6 +118,8 @@ as_translation_kind_to_string (AsTranslationKind kind)
{
if (kind == AS_TRANSLATION_KIND_GETTEXT)
return "gettext";
+ if (kind == AS_TRANSLATION_KIND_QT)
+ return "qt";
return NULL;
}
@@ -204,6 +208,10 @@ as_translation_node_insert (AsTranslation *translation, GNode *parent, AsNodeCon
AsTranslationPrivate *priv = GET_PRIVATE (translation);
GNode *n;
+ /* invalid */
+ if (priv->kind == AS_TRANSLATION_KIND_UNKNOWN)
+ return NULL;
+
n = as_node_insert (parent, "translation", priv->id,
AS_NODE_INSERT_FLAG_NONE,
"type", as_translation_kind_to_string (priv->kind),
diff --git a/libappstream-glib/as-translation.h b/libappstream-glib/as-translation.h
index e13937f..333f9dc 100644
--- a/libappstream-glib/as-translation.h
+++ b/libappstream-glib/as-translation.h
@@ -51,12 +51,14 @@ struct _AsTranslationClass
* AsTranslationKind:
* @AS_TRANSLATION_KIND_UNKNOWN: Type invalid or not known
* @AS_TRANSLATION_KIND_GETTEXT: Gettext translation system
+ * @AS_TRANSLATION_KIND_QT: QT translation system
*
* The translation type.
**/
typedef enum {
- AS_TRANSLATION_KIND_UNKNOWN,
- AS_TRANSLATION_KIND_GETTEXT,
+ AS_TRANSLATION_KIND_UNKNOWN, /* Since: 0.5.7 */
+ AS_TRANSLATION_KIND_GETTEXT, /* Since: 0.5.7 */
+ AS_TRANSLATION_KIND_QT, /* Since: 0.5.8 */
/*< private >*/
AS_TRANSLATION_KIND_LAST
} AsTranslationKind;