[gnome-maps] Place: translate opening hours



commit 047de5a79fa6d31fa2a58f42cd572eafa6f39cc5
Author: Marcus Lundblad <ml update uu se>
Date:   Thu Dec 11 21:22:14 2014 +0100

    Place: translate opening hours
    
    Parse the opening_hours tag from OSM to produce translatable
    strings.
    
    To make it manageable, handle up to three different time specifications
    (those separated by ;).
    
    Each day range can contain up to three comma-separated day lists of each
    either a single day or in the form start-end.
    
    For each component, the time specification list can contain one or two
    elements (each with start and end time, typically two are used i.e. when
    there is a lunch break).
    
    This is done to avoid doing a list iteration which could potentially be
    difficult to translate right in some cases.
    
    Furthermore, 24/7 and 00:00-24:00 are recognized as "around the clock".
    It also handles the special cases PH and SH for public and school holidays.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=740836

 po/POTFILES.in                  |    1 +
 src/gnome-maps.js.gresource.xml |    1 +
 src/place.js                    |    6 +
 src/searchResultBubble.js       |    2 +-
 src/translations.js             |  324 +++++++++++++++++++++++++++++++++++++++
 5 files changed, 333 insertions(+), 1 deletions(-)
---
diff --git a/po/POTFILES.in b/po/POTFILES.in
index be97b5f..69d26c1 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -21,5 +21,6 @@ src/searchResultBubble.js
 src/sidebar.js
 [type: gettext/glade]src/sidebar.ui
 [type: gettext/glade]src/social-place-more-results-row.ui
+src/translations.js
 [type: gettext/glade]src/user-location-bubble.ui
 src/utils.js
diff --git a/src/gnome-maps.js.gresource.xml b/src/gnome-maps.js.gresource.xml
index d3a2709..d6342f4 100644
--- a/src/gnome-maps.js.gresource.xml
+++ b/src/gnome-maps.js.gresource.xml
@@ -42,6 +42,7 @@
     <file>sidebar.js</file>
     <file>socialPlaceListBox.js</file>
     <file>socialPlaceMatcher.js</file>
+    <file>translations.js</file>
     <file>userLocationMarker.js</file>
     <file>userLocationBubble.js</file>
     <file>facebookBackend.js</file>
diff --git a/src/place.js b/src/place.js
index 025faa0..4979fcf 100644
--- a/src/place.js
+++ b/src/place.js
@@ -25,6 +25,7 @@ const _ = imports.gettext.gettext;
 const Geocode = imports.gi.GeocodeGlib;
 const GLib = imports.gi.GLib;
 const Lang = imports.lang;
+const Translations = imports.translations;
 
 const Place = new Lang.Class({
     Name: 'Place',
@@ -98,6 +99,10 @@ const Place = new Lang.Class({
         return this._openingHours;
     },
 
+    get openingHoursTranslated() {
+        return Translations.translateOpeningHours(this._openingHours);
+    },
+
     set wheelchair(v) {
         this._wheelchair = v;
     },
@@ -141,6 +146,7 @@ const Place = new Lang.Class({
         }
     },
 
+
     toJSON: function() {
         let bounding_box = null;
 
diff --git a/src/searchResultBubble.js b/src/searchResultBubble.js
index 1d4a07f..5989632 100644
--- a/src/searchResultBubble.js
+++ b/src/searchResultBubble.js
@@ -100,7 +100,7 @@ const SearchResultBubble = new Lang.Class({
             infos.push(_("Population: %s").format(place.population));
 
         if (place.openingHours)
-            infos.push(_("Opening hours: %s").format(place.openingHours));
+            infos.push(_("Opening hours: %s").format(place.openingHoursTranslated));
 
         if (place.wiki) {
             let link = this._formatWikiLink(place.wiki);
diff --git a/src/translations.js b/src/translations.js
new file mode 100644
index 0000000..be255e9
--- /dev/null
+++ b/src/translations.js
@@ -0,0 +1,324 @@
+/* -*- Mode: JS2; indent-tabs-mode: nil; js2-basic-offset: 4 -*- */
+/* vim: set et ts=4 sw=4: */
+/*
+ * Copyright (c) 2014 Marcus Lundblad
+ *
+ * GNOME Maps is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by the
+ * Free Software Foundation; either version 2 of the License, or (at your
+ * option) any later version.
+ *
+ * GNOME Maps 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 GNOME Maps; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ * Author: Marcus Lundblad <ml update uu se>
+ */
+
+const _ = imports.gettext.gettext;
+const C_ = imports.gettext.dgettext;
+
+const Gio = imports.gi.Gio;
+const GLib = imports.gi.GLib;
+
+// in org.gnome.desktop.interface
+const CLOCK_FORMAT_KEY = 'clock-format';
+
+let _desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
+let clockFormat = _desktopSettings.get_string(CLOCK_FORMAT_KEY);
+
+
+/* Translate an opening time specification tag value.
+ * from OSM to a human-readable string (marked for translation).
+ *
+ * Some limitations are imposed to keep the translations manageable:
+ * A maximum of three components (separated by ; in the tag) are considered.
+ * For each component a maximum of three day intervals are considered.
+ * Each day interval can have one or two time intervals specified.
+ * For each of these limitations, the input string is passed on unmodified
+ * if the format is outside the supported cases, so no data should be dropped.
+ * Currently only specifying weekdays and public and school holidays are
+ * supported.
+ * Other variants, such as month-specific opening hours and other interval
+ * variants are not currently supported. In these cases, the data is
+ * returned as-is.
+ *
+ * The definition for the opening_hours tag can be found at:
+ * http://wiki.openstreetmap.org/wiki/Key:opening_hours
+ */
+function translateOpeningHours(string) {
+    if (string === '24/7')
+        return _("around the clock");
+    else if (string === 'Mo-Su 00:00-24:00')
+        return _("around the clock");
+    else if (string === 'sunrise-sunset')
+        return _("from sunrise to sunset");
+    else {
+        /* split "components" */
+        let splitParts = string.split(';');
+        let part1, part2, part3;
+
+        switch (splitParts.length) {
+            case 1:
+                return _translateOpeningHoursPart(splitParts[0].trim());
+            case 2:
+                part1 = _translateOpeningHoursPart(splitParts[0].trim());
+                part2 = _translateOpeningHoursPart(splitParts[1].trim());
+
+                /* Translators:
+                 * This is a format string with two separate time ranges
+                 * such as "Mo-Fr 10:00-19:00 Sa 12:00-16:00"
+                 * The space between the format place holders could be
+                 * substituted with the appropriate separator.
+                 */
+                return C_("time range list", "%s %s").format(part1, part2);
+            case 3:
+                part1 = _translateOpeningHoursPart(splitParts[0].trim());
+                part2 = _translateOpeningHoursPart(splitParts[1].trim());
+                part3 = _translateOpeningHoursPart(splitParts[2].trim());
+
+                /* Translators:
+                 * This is a format string with three separate time ranges
+                 * such as "Mo-Fr 10:00-19:00 Sa 10:00-17:00 Su 12:00-16:00"
+                 * The space between the format place holders could be
+                 * substituted with the appropriate separator.
+                 */
+            return C_("time range list", "%s %s %s").format(part1,
+                                                            part2,
+                                                            part3);
+            default:
+                return string;
+        }
+    }
+
+}
+
+/*
+ * Parse a time range component, comprised of day and time ranges, such as:
+ * Mo-Fr 10:00-19:00
+ * Mo-We,Fr 10:00-12:00,13:00-17:00
+ */
+function _translateOpeningHoursPart(string) {
+    let splitString = string.split(' ');
+
+    if (splitString.length == 2) {
+        let dayIntervalSpec =
+            _translateOpeningHoursDayIntervalList(splitString[0].trim());
+        let timeIntervalSpec =
+            _translateOpeningHoursTimeIntervalList(splitString[1].trim());
+
+        /* Translators:
+         * This is a format string consisting of a part specifying the days for
+         * which the specified time is applied and the time interval
+         * specification as the second argument.
+         * The space between the format place holders could be substituted with
+         * the appropriate separator or phrase and the ordering of the arguments
+         * can be rearranged with the %n#s syntax. */
+        return C_("time range component", "%s %s").
+                    format(dayIntervalSpec, timeIntervalSpec);
+    } else {
+        // for an unknown format, just output the raw value
+        return string;
+    }
+}
+
+/*
+ * Parse a day interval, such as:
+ * Mo-Fr
+ * Mo,We,Th-Fr
+ */
+function _translateOpeningHoursDayIntervalList(string) {
+    let splitParts = string.split(',');
+    let interval1, interval2, interval3;
+
+    switch (splitParts.length) {
+        case 1:
+            return _translateOpeningHoursDayInterval(splitParts[0].trim());
+        case 2:
+            interval1 = _translateOpeningHoursDayInterval(splitParts[0].trim());
+            interval2 = _translateOpeningHoursDayInterval(splitParts[1].trim());
+            /* Translators:
+             * This represents a format string consisting of two day interval
+             * specifications.
+             * For example:
+             * Mo-Fr,Sa
+             * where the "Mo-Fr" and "Sa" parts are replaced by the %s
+             * place holder.
+             * The separator (,) could be replaced with a translated variant or
+             * a phrase if appropriate. */
+            return C_("day interval list", "%s,%s").format(interval1, interval2);
+        case 3:
+            interval1 = _translateOpeningHoursDayInterval(splitParts[0].trim());
+            interval2 = _translateOpeningHoursDayInterval(splitParts[1].trim());
+            interval3 = _translateOpeningHoursDayInterval(splitParts[2].trim());
+            /* Translators:
+             * This represents a format string consisting of three day interval
+             * specifications.
+             * For example:
+             * Mo-We,Fr,Su
+             * where the "Mo-We", "Fr", and "Su" parts are replaced by the
+             * %s place holder.
+             * The separator (,) could be replaced with a translated variant or
+             * a phrase if appropriate. */
+            return C_("day interval list", "%s,%s,%s").
+                format(interval1, interval2, interval3);
+        default:
+            // for other formats, just return the raw string
+            return string;
+    }
+}
+
+/*
+ * Parse a day interval consisting of either a single day
+ * or a range, such as:
+ * Mo-Fr
+ * Tu
+ */
+function _translateOpeningHoursDayInterval(string) {
+    let splitString = string.split('-');
+
+    // special case: Mo-Su treated as "every day"
+    if (string === 'Mo-Su')
+        return _("every day");
+
+    switch (splitString.length) {
+        case 1:
+            return _translateOpeningHoursDay(splitString[0].trim());
+        case 2:
+            let from = splitString[0].trim();
+            let to = splitString[1].trim();
+
+            /* Translators:
+             * This represents a range of days with a starting and ending day.
+             */
+            return C_("day range", "%s-%s").format(
+                _translateOpeningHoursDay(from),
+                _translateOpeningHoursDay(to));
+        default:
+            // unknown format, just return the input
+            return string;
+    }
+}
+
+function _translateOpeningHoursDay(string) {
+    if (string === 'PH')
+        return _("public holidays");
+    if (string === 'SH')
+        return _("school holidays");
+
+    // create a dummy DateTime instance which is guaranteed to be a Monday
+    let time = GLib.DateTime.new_local(1, 1, 1, 0, 0, 0.0);
+    let days = ['Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa', 'Su'];
+
+    for (let i = 0; i < days.length; i++) {
+        if (string === days[i]) {
+            time = time.add_days(i);
+            return time.format('%a');
+        }
+    }
+
+    // unknown value, just return the input
+    return string;
+}
+
+/*
+ * Parse a time interval list, such as:
+ * 10:00-20:00
+ * 10:00-12:00,13:00-17:00
+ */
+function _translateOpeningHoursTimeIntervalList(string) {
+    let splitString = string.split(',');
+
+    switch (splitString.length) {
+        case 1:
+            return _translateOpeningHoursTimeInterval(splitString[0].trim());
+        case 2:
+            let interval1 = splitString[0].trim();
+            let interval2 = splitString[1].trim();
+
+            /* Translators:
+             * This is a list with two time intervals, such as:
+             * 09:00-12:00, 13:00-14:00
+             * The intervals are represented by the %s place holders and
+             * appropriate white space or connected phrase could be modified by
+             * the translation. The order of the arguments can be rearranged
+             * using the %n$s syntax.
+             */
+            return C_("time interval list", "%s, %s").format(
+                _translateOpeningHoursTimeInterval(interval1),
+                _translateOpeningHoursTimeInterval(interval2));
+        default:
+            // for other number of components, just return the input
+            return string;
+    }
+}
+
+/*
+ * Parse a time interval
+ */
+function _translateOpeningHoursTimeInterval(string) {
+    if (string === 'off')
+        return _("not open");
+
+    let splitString = string.split('-');
+
+    if (splitString.length == 2) {
+        let from = splitString[0].trim();
+        let to = splitString[1].trim();
+
+        /* Translators:
+         * This is a time interval with a starting and an ending time.
+         * The time values are represented by the %s place holders and
+         * appropriate white spacing or connecting phrases can be set by the
+         * translation as needed. The order of the arguments can be rearranged
+         * using the %n$s syntax.
+         */
+        return C_("time interval", "%s-%s").format(
+            _translateOpeningHoursTime(from),
+            _translateOpeningHoursTime(to));
+    } else {
+        // unknown time interval format, just return the input
+        return string;
+    }
+}
+
+/*
+ * Parse a time.
+ */
+function _translateOpeningHoursTime(string) {
+    let splitString = string.split(':');
+
+    if (splitString.length == 2) {
+        let h = splitString[0];
+        let min = splitString[1];
+
+        // if the parts aren't numbers
+        if (h % 1 !== 0 || min % 1 !== 0)
+           return string;
+
+        // if the hours or minute components are out of range
+        if (h > 24 || h < 0 || min > 59 || min < 0)
+            return string;
+
+        // should translate 24:00 to 00:00 to keep GDateTime happy
+        if (h == 24)
+            h = 0;
+
+        // create a dummy DateTime, we are just interested in the hour and
+        // minute parts
+        let time = GLib.DateTime.new_local(1, 1, 1, h, min, 0.0);
+
+        if (clockFormat === '24h')
+            return time.format('%R');
+        else
+            return time.format('%r');
+    } else {
+        // unknown format, just return input
+        return string;
+    }
+}


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