summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPhilip Withnall <withnall@endlessm.com>2020-05-21 15:14:47 +0100
committerRichard Hughes <richard@hughsie.com>2020-06-05 15:47:27 +0100
commit867114a596ce1304e40e57673c3a3672aa80d966 (patch)
treeaa88507f8f4644e451ff8bd00ad97ec86bd29813
parente00e4b87b71d67bca58e202b7fd5c83a77286d4e (diff)
downloadappstream-glib-867114a596ce1304e40e57673c3a3672aa80d966.tar.gz
as-content-rating: Add content rating system APIs from gnome-software
These are moving to libappstream-glib as they are used by gnome-software and libmalcontent-ui, and are potentially useful in other projects. In particular, there are a number of translatable strings which need to be centralised so that translation work isn’t repeated. This code was originally written by: • Richard Hughes <richard@hughsie.com> • Piotr Drąg <piotrdrag@gmail.com> • Philip Withnall <withnall@endlessm.com> • Jordi Mas <jmas@softcatala.org> It was previously in `src/gs-content-rating.[ch]` in gnome-software, under the GPL-2.0+; and in `libmalcontent-ui/gs-content-rating.[ch]` in malcontent, also under the GPL-2.0+. It has been relicensed from GPL-2.0+ to LGPL-2.1+ for the move to appstream-glib, and all the previous contributors have agreed. See https://github.com/hughsie/appstream-glib/pull/364#issuecomment-632773905 and below for their agreements. In moving it from one project to the other, I have added documentation comments and tweaked some of the APIs to make them more usable. Signed-off-by: Philip Withnall <withnall@endlessm.com>
-rw-r--r--libappstream-glib/as-content-rating.c943
-rw-r--r--libappstream-glib/as-content-rating.h58
-rw-r--r--libappstream-glib/as-self-test.c52
-rw-r--r--po/POTFILES.in1
4 files changed, 1054 insertions, 0 deletions
diff --git a/libappstream-glib/as-content-rating.c b/libappstream-glib/as-content-rating.c
index 11bc20d..936923d 100644
--- a/libappstream-glib/as-content-rating.c
+++ b/libappstream-glib/as-content-rating.c
@@ -18,6 +18,8 @@
#include "config.h"
+#include <glib/gi18n-lib.h>
+
#include "as-node-private.h"
#include "as-content-rating-private.h"
#include "as-ref-string.h"
@@ -209,6 +211,905 @@ as_content_rating_value_from_string (const gchar *value)
return AS_CONTENT_RATING_VALUE_UNKNOWN;
}
+static const gchar *rating_system_names[] = {
+ [AS_CONTENT_RATING_SYSTEM_UNKNOWN] = NULL,
+ [AS_CONTENT_RATING_SYSTEM_INCAA] = "INCAA",
+ [AS_CONTENT_RATING_SYSTEM_ACB] = "ACB",
+ [AS_CONTENT_RATING_SYSTEM_DJCTQ] = "DJCTQ",
+ [AS_CONTENT_RATING_SYSTEM_GSRR] = "GSRR",
+ [AS_CONTENT_RATING_SYSTEM_PEGI] = "PEGI",
+ [AS_CONTENT_RATING_SYSTEM_KAVI] = "KAVI",
+ [AS_CONTENT_RATING_SYSTEM_USK] = "USK",
+ [AS_CONTENT_RATING_SYSTEM_ESRA] = "ESRA",
+ [AS_CONTENT_RATING_SYSTEM_CERO] = "CERO",
+ [AS_CONTENT_RATING_SYSTEM_OFLCNZ] = "OFLCNZ",
+ [AS_CONTENT_RATING_SYSTEM_RUSSIA] = "RUSSIA",
+ [AS_CONTENT_RATING_SYSTEM_MDA] = "MDA",
+ [AS_CONTENT_RATING_SYSTEM_GRAC] = "GRAC",
+ [AS_CONTENT_RATING_SYSTEM_ESRB] = "ESRB",
+ [AS_CONTENT_RATING_SYSTEM_IARC] = "IARC",
+};
+G_STATIC_ASSERT (G_N_ELEMENTS (rating_system_names) == AS_CONTENT_RATING_SYSTEM_LAST);
+
+/**
+ * as_content_rating_system_to_string:
+ * @system: an #AsContentRatingSystem
+ *
+ * Get a human-readable string to identify @system. %NULL will be returned for
+ * %AS_CONTENT_RATING_SYSTEM_UNKNOWN.
+ *
+ * Returns: (nullable): a human-readable string for @system, or %NULL if unknown
+ * Since: 0.7.18
+ */
+const gchar *
+as_content_rating_system_to_string (AsContentRatingSystem system)
+{
+ if ((gint) system < AS_CONTENT_RATING_SYSTEM_UNKNOWN ||
+ (gint) system >= AS_CONTENT_RATING_SYSTEM_LAST)
+ return NULL;
+
+ return rating_system_names[system];
+}
+
+static char *
+get_esrb_string (const gchar *source, const gchar *translate)
+{
+ if (g_strcmp0 (source, translate) == 0)
+ return g_strdup (source);
+ /* TRANSLATORS: This is the formatting of English and localized name
+ * of the rating e.g. "Adults Only (solo adultos)" */
+ return g_strdup_printf (_("%s (%s)"), source, translate);
+}
+
+/**
+ * as_content_rating_system_format_age:
+ * @system: an #AsContentRatingSystem
+ * @age: a CSM age to format
+ *
+ * Format @age as a human-readable string in the given rating @system. This is
+ * the way to present system-specific strings in a UI.
+ *
+ * Returns: (transfer full) (nullable): a newly allocated formatted version of
+ * @age, or %NULL if the given @system has no representation for @age
+ * Since: 0.7.18
+ */
+/* data obtained from https://en.wikipedia.org/wiki/Video_game_rating_system */
+gchar *
+as_content_rating_system_format_age (AsContentRatingSystem system, guint age)
+{
+ if (system == AS_CONTENT_RATING_SYSTEM_INCAA) {
+ if (age >= 18)
+ return g_strdup ("+18");
+ if (age >= 13)
+ return g_strdup ("+13");
+ return g_strdup ("ATP");
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_ACB) {
+ if (age >= 18)
+ return g_strdup ("R18+");
+ if (age >= 15)
+ return g_strdup ("MA15+");
+ return g_strdup ("PG");
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_DJCTQ) {
+ if (age >= 18)
+ return g_strdup ("18");
+ if (age >= 16)
+ return g_strdup ("16");
+ if (age >= 14)
+ return g_strdup ("14");
+ if (age >= 12)
+ return g_strdup ("12");
+ if (age >= 10)
+ return g_strdup ("10");
+ return g_strdup ("L");
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_GSRR) {
+ if (age >= 18)
+ return g_strdup ("限制");
+ if (age >= 15)
+ return g_strdup ("輔15");
+ if (age >= 12)
+ return g_strdup ("輔12");
+ if (age >= 6)
+ return g_strdup ("保護");
+ return g_strdup ("普通");
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_PEGI) {
+ if (age >= 18)
+ return g_strdup ("18");
+ if (age >= 16)
+ return g_strdup ("16");
+ if (age >= 12)
+ return g_strdup ("12");
+ if (age >= 7)
+ return g_strdup ("7");
+ if (age >= 3)
+ return g_strdup ("3");
+ return NULL;
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_KAVI) {
+ if (age >= 18)
+ return g_strdup ("18+");
+ if (age >= 16)
+ return g_strdup ("16+");
+ if (age >= 12)
+ return g_strdup ("12+");
+ if (age >= 7)
+ return g_strdup ("7+");
+ if (age >= 3)
+ return g_strdup ("3+");
+ return NULL;
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_USK) {
+ if (age >= 18)
+ return g_strdup ("18");
+ if (age >= 16)
+ return g_strdup ("16");
+ if (age >= 12)
+ return g_strdup ("12");
+ if (age >= 6)
+ return g_strdup ("6");
+ return g_strdup ("0");
+ }
+ /* Reference: http://www.esra.org.ir/ */
+ if (system == AS_CONTENT_RATING_SYSTEM_ESRA) {
+ if (age >= 18)
+ return g_strdup ("+18");
+ if (age >= 15)
+ return g_strdup ("+15");
+ if (age >= 12)
+ return g_strdup ("+12");
+ if (age >= 7)
+ return g_strdup ("+7");
+ if (age >= 3)
+ return g_strdup ("+3");
+ return NULL;
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_CERO) {
+ if (age >= 18)
+ return g_strdup ("Z");
+ if (age >= 17)
+ return g_strdup ("D");
+ if (age >= 15)
+ return g_strdup ("C");
+ if (age >= 12)
+ return g_strdup ("B");
+ return g_strdup ("A");
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_OFLCNZ) {
+ if (age >= 18)
+ return g_strdup ("R18");
+ if (age >= 16)
+ return g_strdup ("R16");
+ if (age >= 15)
+ return g_strdup ("R15");
+ if (age >= 13)
+ return g_strdup ("R13");
+ return g_strdup ("G");
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_RUSSIA) {
+ if (age >= 18)
+ return g_strdup ("18+");
+ if (age >= 16)
+ return g_strdup ("16+");
+ if (age >= 12)
+ return g_strdup ("12+");
+ if (age >= 6)
+ return g_strdup ("6+");
+ return g_strdup ("0+");
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_MDA) {
+ if (age >= 18)
+ return g_strdup ("M18");
+ if (age >= 16)
+ return g_strdup ("ADV");
+ return get_esrb_string ("General", _("General"));
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_GRAC) {
+ if (age >= 18)
+ return g_strdup ("18");
+ if (age >= 15)
+ return g_strdup ("15");
+ if (age >= 12)
+ return g_strdup ("12");
+ return get_esrb_string ("ALL", _("ALL"));
+ }
+ if (system == AS_CONTENT_RATING_SYSTEM_ESRB) {
+ if (age >= 18)
+ return get_esrb_string ("Adults Only", _("Adults Only"));
+ if (age >= 17)
+ return get_esrb_string ("Mature", _("Mature"));
+ if (age >= 13)
+ return get_esrb_string ("Teen", _("Teen"));
+ if (age >= 10)
+ return get_esrb_string ("Everyone 10+", _("Everyone 10+"));
+ if (age >= 6)
+ return get_esrb_string ("Everyone", _("Everyone"));
+
+ return get_esrb_string ("Early Childhood", _("Early Childhood"));
+ }
+ /* IARC = everything else */
+ if (age >= 18)
+ return g_strdup ("18+");
+ if (age >= 16)
+ return g_strdup ("16+");
+ if (age >= 12)
+ return g_strdup ("12+");
+ if (age >= 7)
+ return g_strdup ("7+");
+ if (age >= 3)
+ return g_strdup ("3+");
+ return NULL;
+}
+
+static const gchar *content_rating_strings[AS_CONTENT_RATING_SYSTEM_LAST][7] = {
+ /* AS_CONTENT_RATING_SYSTEM_UNKNOWN is handled in code */
+ [AS_CONTENT_RATING_SYSTEM_INCAA] = { "ATP", "+13", "+18", NULL },
+ [AS_CONTENT_RATING_SYSTEM_ACB] = { "PG", "MA15+", "R18+", NULL },
+ [AS_CONTENT_RATING_SYSTEM_DJCTQ] = { "L", "10", "12", "14", "16", "18", NULL },
+ [AS_CONTENT_RATING_SYSTEM_GSRR] = { "普通", "保護", "輔12", "輔15", "限制", NULL },
+ [AS_CONTENT_RATING_SYSTEM_PEGI] = { "3", "7", "12", "16", "18", NULL },
+ [AS_CONTENT_RATING_SYSTEM_KAVI] = { "3+", "7+", "12+", "16+", "18+", NULL },
+ [AS_CONTENT_RATING_SYSTEM_USK] = { "0", "6", "12", "16", "18", NULL },
+ [AS_CONTENT_RATING_SYSTEM_ESRA] = { "+3", "+7", "+12", "+15", "+18", NULL },
+ [AS_CONTENT_RATING_SYSTEM_CERO] = { "A", "B", "C", "D", "Z", NULL },
+ [AS_CONTENT_RATING_SYSTEM_OFLCNZ] = { "G", "R13", "R15", "R16", "R18", NULL },
+ [AS_CONTENT_RATING_SYSTEM_RUSSIA] = { "0+", "6+", "12+", "16+", "18+", NULL },
+ [AS_CONTENT_RATING_SYSTEM_MDA] = { "General", "ADV", "M18", NULL },
+ [AS_CONTENT_RATING_SYSTEM_GRAC] = { "ALL", "12", "15", "18", NULL },
+ /* Note: ESRB has locale-specific suffixes, so needs special further
+ * handling in code. These strings are just the locale-independent parts. */
+ [AS_CONTENT_RATING_SYSTEM_ESRB] = { "Early Childhood", "Everyone", "Everyone 10+", "Teen", "Mature", "Adults Only", NULL },
+ [AS_CONTENT_RATING_SYSTEM_IARC] = { "3+", "7+", "12+", "16+", "18+", NULL },
+};
+
+/**
+ * as_content_rating_system_get_formatted_ages:
+ * @system: an #AsContentRatingSystem
+ *
+ * Get an array of all the possible return values of
+ * as_content_rating_system_format_age() for the given @system. The array is
+ * sorted with youngest CSM age first.
+ *
+ * Returns: (transfer full): %NULL-terminated array of human-readable age strings
+ * Since: 0.7.18
+ */
+gchar **
+as_content_rating_system_get_formatted_ages (AsContentRatingSystem system)
+{
+ g_return_val_if_fail ((int) system < AS_CONTENT_RATING_SYSTEM_LAST, NULL);
+
+ /* IARC is the fallback for everything */
+ if (system == AS_CONTENT_RATING_SYSTEM_UNKNOWN)
+ system = AS_CONTENT_RATING_SYSTEM_IARC;
+
+ /* ESRB is special as it requires localised suffixes */
+ if (system == AS_CONTENT_RATING_SYSTEM_ESRB) {
+ g_auto(GStrv) esrb_ages = g_new0 (gchar *, 7);
+
+ esrb_ages[0] = get_esrb_string (content_rating_strings[system][0], _("Early Childhood"));
+ esrb_ages[1] = get_esrb_string (content_rating_strings[system][1], _("Everyone"));
+ esrb_ages[2] = get_esrb_string (content_rating_strings[system][2], _("Everyone 10+"));
+ esrb_ages[3] = get_esrb_string (content_rating_strings[system][3], _("Teen"));
+ esrb_ages[4] = get_esrb_string (content_rating_strings[system][4], _("Mature"));
+ esrb_ages[5] = get_esrb_string (content_rating_strings[system][5], _("Adults Only"));
+ esrb_ages[6] = NULL;
+
+ return g_steal_pointer (&esrb_ages);
+ }
+
+ return g_strdupv ((gchar **) content_rating_strings[system]);
+}
+
+static guint content_rating_csm_ages[AS_CONTENT_RATING_SYSTEM_LAST][7] = {
+ /* AS_CONTENT_RATING_SYSTEM_UNKNOWN is handled in code */
+ [AS_CONTENT_RATING_SYSTEM_INCAA] = { 0, 13, 18 },
+ [AS_CONTENT_RATING_SYSTEM_ACB] = { 0, 15, 18 },
+ [AS_CONTENT_RATING_SYSTEM_DJCTQ] = { 0, 10, 12, 14, 16, 18 },
+ [AS_CONTENT_RATING_SYSTEM_GSRR] = { 0, 6, 12, 15, 18 },
+ [AS_CONTENT_RATING_SYSTEM_PEGI] = { 3, 7, 12, 16, 18 },
+ [AS_CONTENT_RATING_SYSTEM_KAVI] = { 3, 7, 12, 16, 18 },
+ [AS_CONTENT_RATING_SYSTEM_USK] = { 0, 6, 12, 16, 18 },
+ [AS_CONTENT_RATING_SYSTEM_ESRA] = { 3, 7, 12, 15, 18 },
+ [AS_CONTENT_RATING_SYSTEM_CERO] = { 0, 12, 15, 17, 18 },
+ [AS_CONTENT_RATING_SYSTEM_OFLCNZ] = { 0, 13, 15, 16, 18 },
+ [AS_CONTENT_RATING_SYSTEM_RUSSIA] = { 0, 6, 12, 16, 18 },
+ [AS_CONTENT_RATING_SYSTEM_MDA] = { 0, 16, 18 },
+ [AS_CONTENT_RATING_SYSTEM_GRAC] = { 0, 12, 15, 18 },
+ [AS_CONTENT_RATING_SYSTEM_ESRB] = { 0, 6, 10, 13, 17, 18 },
+ [AS_CONTENT_RATING_SYSTEM_IARC] = { 3, 7, 12, 16, 18 },
+};
+
+/**
+ * as_content_rating_system_get_csm_ages:
+ * @system: an #AsContentRatingSystem
+ * @length_out: (out) (not optional): return location for the length of the
+ * returned array
+ *
+ * Get the CSM ages corresponding to the entries returned by
+ * as_content_rating_system_get_formatted_ages() for this @system.
+ *
+ * Returns: (transfer container) (array length=length_out): an array of CSM ages
+ * Since: 0.7.18
+ */
+const guint *
+as_content_rating_system_get_csm_ages (AsContentRatingSystem system, gsize *length_out)
+{
+ g_return_val_if_fail ((int) system < AS_CONTENT_RATING_SYSTEM_LAST, NULL);
+ g_return_val_if_fail (length_out != NULL, NULL);
+
+ /* IARC is the fallback for everything */
+ if (system == AS_CONTENT_RATING_SYSTEM_UNKNOWN)
+ system = AS_CONTENT_RATING_SYSTEM_IARC;
+
+ *length_out = g_strv_length ((gchar **) content_rating_strings[system]);
+ return content_rating_csm_ages[system];
+}
+
+/*
+ * parse_locale:
+ * @locale: (transfer full): a locale to parse
+ * @language_out: (out) (optional) (nullable): return location for the parsed
+ * language, or %NULL to ignore
+ * @territory_out: (out) (optional) (nullable): return location for the parsed
+ * territory, or %NULL to ignore
+ * @codeset_out: (out) (optional) (nullable): return location for the parsed
+ * codeset, or %NULL to ignore
+ * @modifier_out: (out) (optional) (nullable): return location for the parsed
+ * modifier, or %NULL to ignore
+ *
+ * Parse @locale as a locale string of the form
+ * `language[_territory][.codeset][@modifier]` — see `man 3 setlocale` for
+ * details.
+ *
+ * On success, %TRUE will be returned, and the components of the locale will be
+ * returned in the given addresses, with each component not including any
+ * separators. Otherwise, %FALSE will be returned and the components will be set
+ * to %NULL.
+ *
+ * @locale is modified, and any returned non-%NULL pointers will point inside
+ * it.
+ *
+ * Returns: %TRUE on success, %FALSE otherwise
+ */
+static gboolean
+parse_locale (gchar *locale /* (transfer full) */,
+ const gchar **language_out,
+ const gchar **territory_out,
+ const gchar **codeset_out,
+ const gchar **modifier_out)
+{
+ gchar *separator;
+ const gchar *language = NULL, *territory = NULL, *codeset = NULL, *modifier = NULL;
+
+ separator = strrchr (locale, '@');
+ if (separator != NULL) {
+ modifier = separator + 1;
+ *separator = '\0';
+ }
+
+ separator = strrchr (locale, '.');
+ if (separator != NULL) {
+ codeset = separator + 1;
+ *separator = '\0';
+ }
+
+ separator = strrchr (locale, '_');
+ if (separator != NULL) {
+ territory = separator + 1;
+ *separator = '\0';
+ }
+
+ language = locale;
+
+ /* Parse failure? */
+ if (*language == '\0') {
+ language = NULL;
+ territory = NULL;
+ codeset = NULL;
+ modifier = NULL;
+ }
+
+ if (language_out != NULL)
+ *language_out = language;
+ if (territory_out != NULL)
+ *territory_out = territory;
+ if (codeset_out != NULL)
+ *codeset_out = codeset;
+ if (modifier_out != NULL)
+ *modifier_out = modifier;
+
+ return (language != NULL);
+}
+
+/**
+ * as_content_rating_system_from_locale:
+ * @locale: a locale, in the format described in `man 3 setlocale`
+ *
+ * Determine the most appropriate #AsContentRatingSystem for the given @locale.
+ * Content rating systems are selected by territory. If no content rating system
+ * seems suitable, %AS_CONTENT_RATING_SYSTEM_IARC is returned.
+ *
+ * Returns: the most relevant #AsContentRatingSystem
+ * Since: 0.7.18
+ */
+/* data obtained from https://en.wikipedia.org/wiki/Video_game_rating_system */
+AsContentRatingSystem
+as_content_rating_system_from_locale (const gchar *locale)
+{
+ g_autofree gchar *locale_copy = g_strdup (locale);
+ const gchar *territory;
+
+ /* Default to IARC for locales which can’t be parsed. */
+ if (!parse_locale (locale_copy, NULL, &territory, NULL, NULL))
+ return AS_CONTENT_RATING_SYSTEM_IARC;
+
+ /* Argentina */
+ if (g_strcmp0 (territory, "AR") == 0)
+ return AS_CONTENT_RATING_SYSTEM_INCAA;
+
+ /* Australia */
+ if (g_strcmp0 (territory, "AU") == 0)
+ return AS_CONTENT_RATING_SYSTEM_ACB;
+
+ /* Brazil */
+ if (g_strcmp0 (territory, "BR") == 0)
+ return AS_CONTENT_RATING_SYSTEM_DJCTQ;
+
+ /* Taiwan */
+ if (g_strcmp0 (territory, "TW") == 0)
+ return AS_CONTENT_RATING_SYSTEM_GSRR;
+
+ /* Europe (but not Finland or Germany), India, Israel,
+ * Pakistan, Quebec, South Africa */
+ if ((g_strcmp0 (territory, "GB") == 0) ||
+ g_strcmp0 (territory, "AL") == 0 ||
+ g_strcmp0 (territory, "AD") == 0 ||
+ g_strcmp0 (territory, "AM") == 0 ||
+ g_strcmp0 (territory, "AT") == 0 ||
+ g_strcmp0 (territory, "AZ") == 0 ||
+ g_strcmp0 (territory, "BY") == 0 ||
+ g_strcmp0 (territory, "BE") == 0 ||
+ g_strcmp0 (territory, "BA") == 0 ||
+ g_strcmp0 (territory, "BG") == 0 ||
+ g_strcmp0 (territory, "HR") == 0 ||
+ g_strcmp0 (territory, "CY") == 0 ||
+ g_strcmp0 (territory, "CZ") == 0 ||
+ g_strcmp0 (territory, "DK") == 0 ||
+ g_strcmp0 (territory, "EE") == 0 ||
+ g_strcmp0 (territory, "FR") == 0 ||
+ g_strcmp0 (territory, "GE") == 0 ||
+ g_strcmp0 (territory, "GR") == 0 ||
+ g_strcmp0 (territory, "HU") == 0 ||
+ g_strcmp0 (territory, "IS") == 0 ||
+ g_strcmp0 (territory, "IT") == 0 ||
+ g_strcmp0 (territory, "LZ") == 0 ||
+ g_strcmp0 (territory, "XK") == 0 ||
+ g_strcmp0 (territory, "LV") == 0 ||
+ g_strcmp0 (territory, "FL") == 0 ||
+ g_strcmp0 (territory, "LU") == 0 ||
+ g_strcmp0 (territory, "LT") == 0 ||
+ g_strcmp0 (territory, "MK") == 0 ||
+ g_strcmp0 (territory, "MT") == 0 ||
+ g_strcmp0 (territory, "MD") == 0 ||
+ g_strcmp0 (territory, "MC") == 0 ||
+ g_strcmp0 (territory, "ME") == 0 ||
+ g_strcmp0 (territory, "NL") == 0 ||
+ g_strcmp0 (territory, "NO") == 0 ||
+ g_strcmp0 (territory, "PL") == 0 ||
+ g_strcmp0 (territory, "PT") == 0 ||
+ g_strcmp0 (territory, "RO") == 0 ||
+ g_strcmp0 (territory, "SM") == 0 ||
+ g_strcmp0 (territory, "RS") == 0 ||
+ g_strcmp0 (territory, "SK") == 0 ||
+ g_strcmp0 (territory, "SI") == 0 ||
+ g_strcmp0 (territory, "ES") == 0 ||
+ g_strcmp0 (territory, "SE") == 0 ||
+ g_strcmp0 (territory, "CH") == 0 ||
+ g_strcmp0 (territory, "TR") == 0 ||
+ g_strcmp0 (territory, "UA") == 0 ||
+ g_strcmp0 (territory, "VA") == 0 ||
+ g_strcmp0 (territory, "IN") == 0 ||
+ g_strcmp0 (territory, "IL") == 0 ||
+ g_strcmp0 (territory, "PK") == 0 ||
+ g_strcmp0 (territory, "ZA") == 0)
+ return AS_CONTENT_RATING_SYSTEM_PEGI;
+
+ /* Finland */
+ if (g_strcmp0 (territory, "FI") == 0)
+ return AS_CONTENT_RATING_SYSTEM_KAVI;
+
+ /* Germany */
+ if (g_strcmp0 (territory, "DE") == 0)
+ return AS_CONTENT_RATING_SYSTEM_USK;
+
+ /* Iran */
+ if (g_strcmp0 (territory, "IR") == 0)
+ return AS_CONTENT_RATING_SYSTEM_ESRA;
+
+ /* Japan */
+ if (g_strcmp0 (territory, "JP") == 0)
+ return AS_CONTENT_RATING_SYSTEM_CERO;
+
+ /* New Zealand */
+ if (g_strcmp0 (territory, "NZ") == 0)
+ return AS_CONTENT_RATING_SYSTEM_OFLCNZ;
+
+ /* Russia: Content rating law */
+ if (g_strcmp0 (territory, "RU") == 0)
+ return AS_CONTENT_RATING_SYSTEM_RUSSIA;
+
+ /* Singapore */
+ if (g_strcmp0 (territory, "SQ") == 0)
+ return AS_CONTENT_RATING_SYSTEM_MDA;
+
+ /* South Korea */
+ if (g_strcmp0 (territory, "KR") == 0)
+ return AS_CONTENT_RATING_SYSTEM_GRAC;
+
+ /* USA, Canada, Mexico */
+ if ((g_strcmp0 (territory, "US") == 0) ||
+ g_strcmp0 (territory, "CA") == 0 ||
+ g_strcmp0 (territory, "MX") == 0)
+ return AS_CONTENT_RATING_SYSTEM_ESRB;
+
+ /* everything else is IARC */
+ return AS_CONTENT_RATING_SYSTEM_IARC;
+}
+
+/* Table of the human-readable descriptions for each #AsContentRatingValue for
+ * each content rating category. @desc_none must be non-%NULL, but the other
+ * values may be %NULL if no description is appropriate. In that case, the next
+ * non-%NULL description for a lower #AsContentRatingValue will be used. */
+static const struct {
+ const gchar *id; /* (not nullable) */
+ const gchar *desc_none; /* (not nullable) */
+ const gchar *desc_mild; /* (nullable) */
+ const gchar *desc_moderate; /* (nullable) */
+ const gchar *desc_intense; /* (nullable) */
+} oars_descriptions[] = {
+ {
+ "violence-cartoon",
+ /* TRANSLATORS: content rating description */
+ N_("No cartoon violence"),
+ /* TRANSLATORS: content rating description */
+ N_("Cartoon characters in unsafe situations"),
+ /* TRANSLATORS: content rating description */
+ N_("Cartoon characters in aggressive conflict"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic violence involving cartoon characters"),
+ },
+ {
+ "violence-fantasy",
+ /* TRANSLATORS: content rating description */
+ N_("No fantasy violence"),
+ /* TRANSLATORS: content rating description */
+ N_("Characters in unsafe situations easily distinguishable from reality"),
+ /* TRANSLATORS: content rating description */
+ N_("Characters in aggressive conflict easily distinguishable from reality"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic violence easily distinguishable from reality"),
+ },
+ {
+ "violence-realistic",
+ /* TRANSLATORS: content rating description */
+ N_("No realistic violence"),
+ /* TRANSLATORS: content rating description */
+ N_("Mildly realistic characters in unsafe situations"),
+ /* TRANSLATORS: content rating description */
+ N_("Depictions of realistic characters in aggressive conflict"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic violence involving realistic characters"),
+ },
+ {
+ "violence-bloodshed",
+ /* TRANSLATORS: content rating description */
+ N_("No bloodshed"),
+ /* TRANSLATORS: content rating description */
+ N_("Unrealistic bloodshed"),
+ /* TRANSLATORS: content rating description */
+ N_("Realistic bloodshed"),
+ /* TRANSLATORS: content rating description */
+ N_("Depictions of bloodshed and the mutilation of body parts"),
+ },
+ {
+ "violence-sexual",
+ /* TRANSLATORS: content rating description */
+ N_("No sexual violence"),
+ /* TRANSLATORS: content rating description */
+ N_("Rape or other violent sexual behavior"),
+ NULL,
+ NULL,
+ },
+ {
+ "drugs-alcohol",
+ /* TRANSLATORS: content rating description */
+ N_("No references to alcohol"),
+ /* TRANSLATORS: content rating description */
+ N_("References to alcoholic beverages"),
+ /* TRANSLATORS: content rating description */
+ N_("Use of alcoholic beverages"),
+ NULL,
+ },
+ {
+ "drugs-narcotics",
+ /* TRANSLATORS: content rating description */
+ N_("No references to illicit drugs"),
+ /* TRANSLATORS: content rating description */
+ N_("References to illicit drugs"),
+ /* TRANSLATORS: content rating description */
+ N_("Use of illicit drugs"),
+ NULL,
+ },
+ {
+ "drugs-tobacco",
+ /* TRANSLATORS: content rating description */
+ N_("No references to tobacco products"),
+ /* TRANSLATORS: content rating description */
+ N_("References to tobacco products"),
+ /* TRANSLATORS: content rating description */
+ N_("Use of tobacco products"),
+ NULL,
+ },
+ {
+ "sex-nudity",
+ /* TRANSLATORS: content rating description */
+ N_("No nudity of any sort"),
+ /* TRANSLATORS: content rating description */
+ N_("Brief artistic nudity"),
+ /* TRANSLATORS: content rating description */
+ N_("Prolonged nudity"),
+ NULL,
+ },
+ {
+ "sex-themes",
+ /* TRANSLATORS: content rating description */
+ N_("No references to or depictions of sexual nature"),
+ /* TRANSLATORS: content rating description */
+ N_("Provocative references or depictions"),
+ /* TRANSLATORS: content rating description */
+ N_("Sexual references or depictions"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic sexual behavior"),
+ },
+ {
+ "language-profanity",
+ /* TRANSLATORS: content rating description */
+ N_("No profanity of any kind"),
+ /* TRANSLATORS: content rating description */
+ N_("Mild or infrequent use of profanity"),
+ /* TRANSLATORS: content rating description */
+ N_("Moderate use of profanity"),
+ /* TRANSLATORS: content rating description */
+ N_("Strong or frequent use of profanity"),
+ },
+ {
+ "language-humor",
+ /* TRANSLATORS: content rating description */
+ N_("No inappropriate humor"),
+ /* TRANSLATORS: content rating description */
+ N_("Slapstick humor"),
+ /* TRANSLATORS: content rating description */
+ N_("Vulgar or bathroom humor"),
+ /* TRANSLATORS: content rating description */
+ N_("Mature or sexual humor"),
+ },
+ {
+ "language-discrimination",
+ /* TRANSLATORS: content rating description */
+ N_("No discriminatory language of any kind"),
+ /* TRANSLATORS: content rating description */
+ N_("Negativity towards a specific group of people"),
+ /* TRANSLATORS: content rating description */
+ N_("Discrimination designed to cause emotional harm"),
+ /* TRANSLATORS: content rating description */
+ N_("Explicit discrimination based on gender, sexuality, race or religion"),
+ },
+ {
+ "money-advertising",
+ /* TRANSLATORS: content rating description */
+ N_("No advertising of any kind"),
+ /* TRANSLATORS: content rating description */
+ N_("Product placement"),
+ /* TRANSLATORS: content rating description */
+ N_("Explicit references to specific brands or trademarked products"),
+ /* TRANSLATORS: content rating description */
+ N_("Users are encouraged to purchase specific real-world items"),
+ },
+ {
+ "money-gambling",
+ /* TRANSLATORS: content rating description */
+ N_("No gambling of any kind"),
+ /* TRANSLATORS: content rating description */
+ N_("Gambling on random events using tokens or credits"),
+ /* TRANSLATORS: content rating description */
+ N_("Gambling using “play” money"),
+ /* TRANSLATORS: content rating description */
+ N_("Gambling using real money"),
+ },
+ {
+ "money-purchasing",
+ /* TRANSLATORS: content rating description */
+ N_("No ability to spend money"),
+ /* TRANSLATORS: content rating description */
+ N_("Users are encouraged to donate real money"),
+ NULL,
+ /* TRANSLATORS: content rating description */
+ N_("Ability to spend real money in-app"),
+ },
+ {
+ "social-chat",
+ /* TRANSLATORS: content rating description */
+ N_("No way to chat with other users"),
+ /* TRANSLATORS: content rating description */
+ N_("User-to-user interactions without chat functionality"),
+ /* TRANSLATORS: content rating description */
+ N_("Moderated chat functionality between users"),
+ /* TRANSLATORS: content rating description */
+ N_("Uncontrolled chat functionality between users"),
+ },
+ {
+ "social-audio",
+ /* TRANSLATORS: content rating description */
+ N_("No way to talk with other users"),
+ /* TRANSLATORS: content rating description */
+ N_("Uncontrolled audio or video chat functionality between users"),
+ NULL,
+ NULL,
+ },
+ {
+ "social-contacts",
+ /* TRANSLATORS: content rating description */
+ N_("No sharing of social network usernames or email addresses"),
+ /* TRANSLATORS: content rating description */
+ N_("Sharing social network usernames or email addresses"),
+ NULL,
+ NULL,
+ },
+ {
+ "social-info",
+ /* TRANSLATORS: content rating description */
+ N_("No sharing of user information with third parties"),
+ /* TRANSLATORS: content rating description */
+ N_("Checking for the latest application version"),
+ /* TRANSLATORS: content rating description */
+ N_("Sharing diagnostic data that does not let others identify the user"),
+ /* TRANSLATORS: content rating description */
+ N_("Sharing information that lets others identify the user"),
+ },
+ {
+ "social-location",
+ /* TRANSLATORS: content rating description */
+ N_("No sharing of physical location with other users"),
+ /* TRANSLATORS: content rating description */
+ N_("Sharing physical location with other users"),
+ NULL,
+ NULL,
+ },
+
+ /* v1.1 */
+ {
+ "sex-homosexuality",
+ /* TRANSLATORS: content rating description */
+ N_("No references to homosexuality"),
+ /* TRANSLATORS: content rating description */
+ N_("Indirect references to homosexuality"),
+ /* TRANSLATORS: content rating description */
+ N_("Kissing between people of the same gender"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic sexual behavior between people of the same gender"),
+ },
+ {
+ "sex-prostitution",
+ /* TRANSLATORS: content rating description */
+ N_("No references to prostitution"),
+ /* TRANSLATORS: content rating description */
+ N_("Indirect references to prostitution"),
+ /* TRANSLATORS: content rating description */
+ N_("Direct references to prostitution"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic depictions of the act of prostitution"),
+ },
+ {
+ "sex-adultery",
+ /* TRANSLATORS: content rating description */
+ N_("No references to adultery"),
+ /* TRANSLATORS: content rating description */
+ N_("Indirect references to adultery"),
+ /* TRANSLATORS: content rating description */
+ N_("Direct references to adultery"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic depictions of the act of adultery"),
+ },
+ {
+ "sex-appearance",
+ /* TRANSLATORS: content rating description */
+ N_("No sexualized characters"),
+ NULL,
+ /* TRANSLATORS: content rating description */
+ N_("Scantily clad human characters"),
+ /* TRANSLATORS: content rating description */
+ N_("Overtly sexualized human characters"),
+ },
+ {
+ "violence-worship",
+ /* TRANSLATORS: content rating description */
+ N_("No references to desecration"),
+ /* TRANSLATORS: content rating description */
+ N_("Depictions of or references to historical desecration"),
+ /* TRANSLATORS: content rating description */
+ N_("Depictions of modern-day human desecration"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic depictions of modern-day desecration"),
+ },
+ {
+ "violence-desecration",
+ /* TRANSLATORS: content rating description */
+ N_("No visible dead human remains"),
+ /* TRANSLATORS: content rating description */
+ N_("Visible dead human remains"),
+ /* TRANSLATORS: content rating description */
+ N_("Dead human remains that are exposed to the elements"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic depictions of desecration of human bodies"),
+ },
+ {
+ "violence-slavery",
+ /* TRANSLATORS: content rating description */
+ N_("No references to slavery"),
+ /* TRANSLATORS: content rating description */
+ N_("Depictions of or references to historical slavery"),
+ /* TRANSLATORS: content rating description */
+ N_("Depictions of modern-day slavery"),
+ /* TRANSLATORS: content rating description */
+ N_("Graphic depictions of modern-day slavery"),
+ },
+};
+
+/**
+ * as_content_rating_attribute_get_description:
+ * @id: the subsection ID e.g. `violence-cartoon`
+ * @value: the #AsContentRatingValue, e.g. %AS_CONTENT_RATING_VALUE_INTENSE
+ *
+ * Get a human-readable description of what content would be expected to
+ * require the content rating attribute given by @id and @value.
+ *
+ * Returns: a human-readable description of @id and @value
+ * Since: 0.7.18
+ */
+const gchar *
+as_content_rating_attribute_get_description (const gchar *id, AsContentRatingValue value)
+{
+ gsize i;
+
+ if ((gint) value < AS_CONTENT_RATING_VALUE_NONE ||
+ (gint) value > AS_CONTENT_RATING_VALUE_INTENSE)
+ return NULL;
+
+ for (i = 0; i < G_N_ELEMENTS (oars_descriptions); i++) {
+ if (!g_str_equal (oars_descriptions[i].id, id))
+ continue;
+
+ /* Return the most-intense non-NULL string. */
+ if (oars_descriptions[i].desc_intense != NULL && value >= AS_CONTENT_RATING_VALUE_INTENSE)
+ return _(oars_descriptions[i].desc_intense);
+ if (oars_descriptions[i].desc_moderate != NULL && value >= AS_CONTENT_RATING_VALUE_MODERATE)
+ return _(oars_descriptions[i].desc_moderate);
+ if (oars_descriptions[i].desc_mild != NULL && value >= AS_CONTENT_RATING_VALUE_MILD)
+ return _(oars_descriptions[i].desc_mild);
+ if (oars_descriptions[i].desc_none != NULL && value >= AS_CONTENT_RATING_VALUE_NONE)
+ return _(oars_descriptions[i].desc_none);
+ g_assert_not_reached ();
+ }
+
+ /* This means the requested @id is missing from @oars_descriptions, so
+ * presumably the OARS spec has been updated but appstream-glib hasn’t. */
+ g_warn_if_reached ();
+
+ return NULL;
+}
+
/* The struct definition below assumes we don’t grow more
* #AsContentRating values. */
G_STATIC_ASSERT (AS_CONTENT_RATING_VALUE_LAST == AS_CONTENT_RATING_VALUE_INTENSE + 1);
@@ -309,6 +1210,48 @@ as_content_rating_attribute_to_csm_age (const gchar *id, AsContentRatingValue va
}
/**
+ * as_content_rating_attribute_from_csm_age:
+ * @id: the subsection ID e.g. `violence-cartoon`
+ * @age: the CSM age
+ *
+ * Gets the highest #AsContentRatingValue which is allowed to be seen by the
+ * given Common Sense Media @age for the given subsection @id.
+ *
+ * For example, if the CSM age mappings for `violence-bloodshed` are:
+ * * age ≥ 0 for %AS_CONTENT_RATING_VALUE_NONE
+ * * age ≥ 9 for %AS_CONTENT_RATING_VALUE_MILD
+ * * age ≥ 11 for %AS_CONTENT_RATING_VALUE_MODERATE
+ * * age ≥ 18 for %AS_CONTENT_RATING_VALUE_INTENSE
+ * then calling this function with `violence-bloodshed` and @age set to 17 would
+ * return %AS_CONTENT_RATING_VALUE_MODERATE. Calling it with age 18 would
+ * return %AS_CONTENT_RATING_VALUE_INTENSE.
+ *
+ * Returns: the #AsContentRatingValue, or %AS_CONTENT_RATING_VALUE_UNKNOWN if
+ * unknown
+ * Since: 0.7.18
+ */
+AsContentRatingValue
+as_content_rating_attribute_from_csm_age (const gchar *id, guint age)
+{
+ for (gsize i = 0; G_N_ELEMENTS (oars_to_csm_mappings); i++) {
+ if (g_strcmp0 (id, oars_to_csm_mappings[i].id) == 0) {
+ if (age >= oars_to_csm_mappings[i].csm_age_intense)
+ return AS_CONTENT_RATING_VALUE_INTENSE;
+ else if (age >= oars_to_csm_mappings[i].csm_age_moderate)
+ return AS_CONTENT_RATING_VALUE_MODERATE;
+ else if (age >= oars_to_csm_mappings[i].csm_age_mild)
+ return AS_CONTENT_RATING_VALUE_MILD;
+ else if (age >= oars_to_csm_mappings[i].csm_age_none)
+ return AS_CONTENT_RATING_VALUE_NONE;
+ else
+ return AS_CONTENT_RATING_VALUE_UNKNOWN;
+ }
+ }
+
+ return AS_CONTENT_RATING_VALUE_UNKNOWN;
+}
+
+/**
* as_content_rating_get_all_rating_ids:
*
* Returns a list of all the valid OARS content rating attribute IDs as could
diff --git a/libappstream-glib/as-content-rating.h b/libappstream-glib/as-content-rating.h
index 42decd3..4d8b5b2 100644
--- a/libappstream-glib/as-content-rating.h
+++ b/libappstream-glib/as-content-rating.h
@@ -52,12 +52,66 @@ typedef enum {
AS_CONTENT_RATING_VALUE_LAST
} AsContentRatingValue;
+/**
+ * AsContentRatingSystem:
+ * @AS_CONTENT_RATING_SYSTEM_UNKNOWN: Unknown ratings system
+ * @AS_CONTENT_RATING_SYSTEM_INCAA: INCAA
+ * @AS_CONTENT_RATING_SYSTEM_ACB: ACB
+ * @AS_CONTENT_RATING_SYSTEM_DJCTQ: DJCTQ
+ * @AS_CONTENT_RATING_SYSTEM_GSRR: GSRR
+ * @AS_CONTENT_RATING_SYSTEM_PEGI: PEGI
+ * @AS_CONTENT_RATING_SYSTEM_KAVI: KAVI
+ * @AS_CONTENT_RATING_SYSTEM_USK: USK
+ * @AS_CONTENT_RATING_SYSTEM_ESRA: ESRA
+ * @AS_CONTENT_RATING_SYSTEM_CERO: CERO
+ * @AS_CONTENT_RATING_SYSTEM_OFLCNZ: OFLCNZ
+ * @AS_CONTENT_RATING_SYSTEM_RUSSIA: Russia
+ * @AS_CONTENT_RATING_SYSTEM_MDA: MDA
+ * @AS_CONTENT_RATING_SYSTEM_GRAC: GRAC
+ * @AS_CONTENT_RATING_SYSTEM_ESRB: ESRB
+ * @AS_CONTENT_RATING_SYSTEM_IARC: IARC
+ *
+ * A content rating system for a particular territory.
+ *
+ * Since: 0.7.18
+ */
+typedef enum {
+ AS_CONTENT_RATING_SYSTEM_UNKNOWN,
+ AS_CONTENT_RATING_SYSTEM_INCAA,
+ AS_CONTENT_RATING_SYSTEM_ACB,
+ AS_CONTENT_RATING_SYSTEM_DJCTQ,
+ AS_CONTENT_RATING_SYSTEM_GSRR,
+ AS_CONTENT_RATING_SYSTEM_PEGI,
+ AS_CONTENT_RATING_SYSTEM_KAVI,
+ AS_CONTENT_RATING_SYSTEM_USK,
+ AS_CONTENT_RATING_SYSTEM_ESRA,
+ AS_CONTENT_RATING_SYSTEM_CERO,
+ AS_CONTENT_RATING_SYSTEM_OFLCNZ,
+ AS_CONTENT_RATING_SYSTEM_RUSSIA,
+ AS_CONTENT_RATING_SYSTEM_MDA,
+ AS_CONTENT_RATING_SYSTEM_GRAC,
+ AS_CONTENT_RATING_SYSTEM_ESRB,
+ AS_CONTENT_RATING_SYSTEM_IARC,
+ /*< private >*/
+ AS_CONTENT_RATING_SYSTEM_LAST
+} AsContentRatingSystem;
+
AsContentRating *as_content_rating_new (void);
/* helpers */
const gchar *as_content_rating_value_to_string (AsContentRatingValue value);
AsContentRatingValue as_content_rating_value_from_string (const gchar *value);
+const gchar *as_content_rating_system_to_string (AsContentRatingSystem system);
+gchar *as_content_rating_system_format_age (AsContentRatingSystem system,
+ guint age);
+
+AsContentRatingSystem as_content_rating_system_from_locale (const gchar *locale);
+
+gchar **as_content_rating_system_get_formatted_ages (AsContentRatingSystem system);
+const guint *as_content_rating_system_get_csm_ages (AsContentRatingSystem system,
+ gsize *length_out);
+
/* getters */
const gchar *as_content_rating_get_kind (AsContentRating *content_rating);
guint as_content_rating_get_minimum_age (AsContentRating *content_rating);
@@ -71,6 +125,10 @@ const gchar **as_content_rating_get_rating_ids (AsContentRating *content_rating)
guint as_content_rating_attribute_to_csm_age (const gchar *id,
AsContentRatingValue value);
+AsContentRatingValue as_content_rating_attribute_from_csm_age (const gchar *id,
+ guint age);
+const gchar *as_content_rating_attribute_get_description (const gchar *id,
+ AsContentRatingValue value);
const gchar **as_content_rating_get_all_rating_ids (void);
/* setters */
diff --git a/libappstream-glib/as-self-test.c b/libappstream-glib/as-self-test.c
index 6616641..d7c726e 100644
--- a/libappstream-glib/as-self-test.c
+++ b/libappstream-glib/as-self-test.c
@@ -1884,6 +1884,57 @@ as_test_content_rating_mappings (void)
g_assert_cmpuint (as_content_rating_attribute_to_csm_age ("not-valid-id", AS_CONTENT_RATING_VALUE_INTENSE), ==, 0);
}
+/* Test that gs_utils_content_rating_system_from_locale() returns the correct
+ * rating system for various standard locales and various forms of locale name.
+ * See `locale -a` for the list of all available locales which some of these
+ * test vectors were derived from. */
+static void
+as_test_content_rating_from_locale (void)
+{
+ const struct {
+ const gchar *locale;
+ AsContentRatingSystem expected_system;
+ } vectors[] = {
+ /* Simple tests to get coverage of each rating system: */
+ { "es_AR", AS_CONTENT_RATING_SYSTEM_INCAA },
+ { "en_AU", AS_CONTENT_RATING_SYSTEM_ACB },
+ { "pt_BR", AS_CONTENT_RATING_SYSTEM_DJCTQ },
+ { "zh_TW", AS_CONTENT_RATING_SYSTEM_GSRR },
+ { "en_GB", AS_CONTENT_RATING_SYSTEM_PEGI },
+ { "hy_AM", AS_CONTENT_RATING_SYSTEM_PEGI },
+ { "bg_BG", AS_CONTENT_RATING_SYSTEM_PEGI },
+ { "fi_FI", AS_CONTENT_RATING_SYSTEM_KAVI },
+ { "de_DE", AS_CONTENT_RATING_SYSTEM_USK },
+ { "az_IR", AS_CONTENT_RATING_SYSTEM_ESRA },
+ { "jp_JP", AS_CONTENT_RATING_SYSTEM_CERO },
+ { "en_NZ", AS_CONTENT_RATING_SYSTEM_OFLCNZ },
+ { "ru_RU", AS_CONTENT_RATING_SYSTEM_RUSSIA },
+ { "en_SQ", AS_CONTENT_RATING_SYSTEM_MDA },
+ { "ko_KR", AS_CONTENT_RATING_SYSTEM_GRAC },
+ { "en_US", AS_CONTENT_RATING_SYSTEM_ESRB },
+ { "en_US", AS_CONTENT_RATING_SYSTEM_ESRB },
+ { "en_CA", AS_CONTENT_RATING_SYSTEM_ESRB },
+ { "es_MX", AS_CONTENT_RATING_SYSTEM_ESRB },
+ /* Fallback (arbitrarily chosen Venezuela since it seems to use IARC): */
+ { "es_VE", AS_CONTENT_RATING_SYSTEM_IARC },
+ /* Locale with a codeset: */
+ { "nl_NL.iso88591", AS_CONTENT_RATING_SYSTEM_PEGI },
+ /* Locale with a codeset and modifier: */
+ { "nl_NL.iso885915@euro", AS_CONTENT_RATING_SYSTEM_PEGI },
+ /* Locale with a less esoteric codeset: */
+ { "en_GB.UTF-8", AS_CONTENT_RATING_SYSTEM_PEGI },
+ /* Locale with a modifier but no codeset: */
+ { "fi_FI@euro", AS_CONTENT_RATING_SYSTEM_KAVI },
+ /* Invalid locale: */
+ { "_invalid", AS_CONTENT_RATING_SYSTEM_IARC },
+ };
+
+ for (gsize i = 0; i < G_N_ELEMENTS (vectors); i++) {
+ g_test_message ("Test %" G_GSIZE_FORMAT ": %s", i, vectors[i].locale);
+ g_assert_cmpint (as_content_rating_system_from_locale (vectors[i].locale), ==, vectors[i].expected_system);
+ }
+}
+
static void
as_test_app_func (void)
{
@@ -5717,6 +5768,7 @@ main (int argc, char **argv)
g_test_add_func ("/AppStream/content_rating", as_test_content_rating_func);
g_test_add_func ("/AppStream/content_rating/empty", as_test_content_rating_empty);
g_test_add_func ("/AppStream/content_rating/mappings", as_test_content_rating_mappings);
+ g_test_add_func ("/AppStream/content_rating/from-locale", as_test_content_rating_from_locale);
g_test_add_func ("/AppStream/release", as_test_release_func);
g_test_add_func ("/AppStream/release{date}", as_test_release_date_func);
g_test_add_func ("/AppStream/release{appdata}", as_test_release_appdata_func);
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 905d786..a683ff7 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -1,3 +1,4 @@
client/as-builder.c
client/as-compose.c
client/as-util.c
+libappstream-glib/as-content-rating.c