From 4a08d2a818434a90b126dbf60bf8c8b485ec3b6f Mon Sep 17 00:00:00 2001 From: Mikhail Gusarov Date: Thu, 4 Jan 2018 02:13:12 +0100 Subject: [PATCH] [FIX #1048] Localize timestamps in chat history If current locale is xx-YY, looks up xx_YY first, xx then and finally falls back to us. goog.i18n.DateTimeSymbols database is used for localization. Signed-off-by: Eric Dvorsak --- src/status_im/utils/datetime.cljs | 62 ++++++++++++++------ test/cljs/status_im/test/runner.cljs | 6 +- test/cljs/status_im/test/utils/datetime.cljs | 51 ++++++++++++++++ 3 files changed, 100 insertions(+), 19 deletions(-) create mode 100644 test/cljs/status_im/test/utils/datetime.cljs diff --git a/src/status_im/utils/datetime.cljs b/src/status_im/utils/datetime.cljs index d00b4b5e4d..d7d84b8521 100644 --- a/src/status_im/utils/datetime.cljs +++ b/src/status_im/utils/datetime.cljs @@ -6,7 +6,10 @@ unparse]] [status-im.i18n :refer [label label-pluralize]] [goog.string :as gstring] - goog.string.format)) + goog.string.format + goog.i18n.DateTimeFormat + [clojure.string :as s] + [goog.object :refer [get]])) (defn now [] (t/now)) @@ -21,18 +24,47 @@ (def time-zone-offset (hours (- (/ (.getTimezoneOffset (js/Date.)) 60)))) -(defn to-short-str - ([ms] - (to-short-str ms #(unparse (formatters :hour-minute) %))) - ([ms today-format-fn] - (let [date (from-long ms) - local (plus date time-zone-offset) - today (t/today-at-midnight) - yesterday (plus today (days -1))] - (cond - (before? date yesterday) (unparse (formatter "dd MMM hh:mm") local) - (before? date today) (label :t/datetime-yesterday) - :else (today-format-fn local))))) +;; xx-YY locale, xx locale or en fallback +(defn- locale-symbols [locale-name] + (if-let [loc (get goog.i18n (str "DateTimeSymbols_" locale-name))] + loc + (let [name-first (s/replace (or locale-name "") #"-.*$" "") + loc (get goog.i18n (str "DateTimeSymbols_" name-first))] + (or loc goog.i18n.DateTimeSymbols_en)))) + +;; Closure does not have an enum for datetime formats +(def short-date-time-format 10) +(def short-date-format 2) + +(defn mk-fmt [locale format] + (goog.i18n.DateTimeFormat. format (locale-symbols locale))) + +(def date-time-fmt + (mk-fmt status-im.i18n/locale short-date-time-format)) +(def date-fmt + (mk-fmt status-im.i18n/locale short-date-format)) + +(defn- to-str [ms old-fmt-fn yesterday-fmt-fn today-fmt-fn] + (let [date (from-long ms) + local (plus date time-zone-offset) + today (t/today-at-midnight) + yesterday (plus today (days -1))] + (cond + (before? date yesterday) (old-fmt-fn local) + (before? date today) (yesterday-fmt-fn local) + :else (today-fmt-fn local)))) + +(defn to-short-str [ms] + (to-str ms + #(.format date-fmt %) + #(label :t/datetime-yesterday) + #(unparse (formatters :hour-minute) %))) + +(defn day-relative [ms] + (to-str ms + #(.format date-time-fmt %) + #(label :t/datetime-yesterday) + #(label :t/datetime-today))) (defn timestamp->mini-date [ms] (unparse (formatter "dd MMM") (-> ms @@ -55,10 +87,6 @@ from-long (plus time-zone-offset))))) -(defn day-relative [ms] - (when (pos? ms) - (to-short-str ms #(label :t/datetime-today)))) - (defn format-time-ago [diff unit] (let [name (label-pluralize diff (:name unit))] (label :t/datetime-ago-format {:ago (label :t/datetime-ago) diff --git a/test/cljs/status_im/test/runner.cljs b/test/cljs/status_im/test/runner.cljs index d3c806507a..21d46bec50 100644 --- a/test/cljs/status_im/test/runner.cljs +++ b/test/cljs/status_im/test/runner.cljs @@ -21,7 +21,8 @@ [status-im.test.utils.gfycat.core] [status-im.test.utils.signing-phrase.core] [status-im.test.utils.transducers] - [status-im.test.utils.async])) + [status-im.test.utils.async] + [status-im.test.utils.datetime])) (enable-console-print!) @@ -53,4 +54,5 @@ 'status-im.test.utils.random 'status-im.test.utils.gfycat.core 'status-im.test.utils.signing-phrase.core - 'status-im.test.utils.transducers) + 'status-im.test.utils.transducers + 'status-im.test.utils.datetime) diff --git a/test/cljs/status_im/test/utils/datetime.cljs b/test/cljs/status_im/test/utils/datetime.cljs new file mode 100644 index 0000000000..17ae269448 --- /dev/null +++ b/test/cljs/status_im/test/utils/datetime.cljs @@ -0,0 +1,51 @@ +(ns status-im.test.utils.datetime + (:require [cljs.test :refer-macros [deftest is]] + [status-im.utils.datetime :as d] + [cljs-time.core :as t])) + +(defn match [name symbols] + (is (identical? (.-dateTimeSymbols_ (d/mk-fmt name d/short-date-format)) + symbols))) + +(deftest date-time-formatter-test + (match "en-US" goog.i18n.DateTimeSymbols_en_US) + (match "en-ZZ" goog.i18n.DateTimeSymbols_en) + (match "en" goog.i18n.DateTimeSymbols_en) + (match "nb-NO" goog.i18n.DateTimeSymbols_nb) + (match "nb" goog.i18n.DateTimeSymbols_nb) + (match "whoa-WHOA" goog.i18n.DateTimeSymbols_en) + (match "whoa" goog.i18n.DateTimeSymbols_en)) + +;; 1970-01-01 00:00:00 UTC +(def epoch 0) +;; 1970-01-03 00:00:00 UTC +(def epoch-plus-3d 172800000) + +(deftest to-short-str-today-test + (with-redefs [t/*ms-fn* (constantly epoch-plus-3d) + d/time-zone-offset (t/period :hours 0)] + (is (= (d/to-short-str epoch-plus-3d) "00:00")))) + +(deftest to-short-str-before-yesterday-us-test + (with-redefs [t/*ms-fn* (constantly epoch-plus-3d) + d/time-zone-offset (t/period :hours 0) + d/date-fmt (d/mk-fmt "us" d/short-date-format)] + (is (= (d/to-short-str epoch) "Jan 1, 1970")))) + +(deftest to-short-str-before-yesterday-nb-test + (with-redefs [d/time-zone-offset (t/period :hours 0) + d/date-fmt (d/mk-fmt "nb-NO" d/short-date-format) + t/*ms-fn* (constantly epoch-plus-3d)] + (is (= (d/to-short-str epoch) "1. jan. 1970")))) + +(deftest day-relative-before-yesterday-us-test + (with-redefs [t/*ms-fn* (constantly epoch-plus-3d) + d/time-zone-offset (t/period :hours 0) + d/date-time-fmt (d/mk-fmt "us" d/short-date-time-format)] + (is (= (d/day-relative epoch) "Jan 1, 1970, 12:00:00 AM")))) + +(deftest day-relative-before-yesterday-nb-test + (with-redefs [t/*ms-fn* (constantly epoch-plus-3d) + d/time-zone-offset (t/period :hours 0) + d/date-time-fmt (d/mk-fmt "nb-NO" d/short-date-time-format)] + (is (= (d/day-relative epoch) "1. jan. 1970, 00:00:00"))))