mirror of
https://github.com/penpot/penpot.git
synced 2025-01-26 00:19:07 -05:00
🎉 Add browser language detection.
This commit is contained in:
parent
344a7dfbaa
commit
299b29b66f
6 changed files with 102 additions and 59 deletions
|
@ -33,7 +33,7 @@
|
||||||
|
|
||||||
(s/def ::email ::us/email)
|
(s/def ::email ::us/email)
|
||||||
(s/def ::fullname ::us/not-empty-string)
|
(s/def ::fullname ::us/not-empty-string)
|
||||||
(s/def ::lang ::us/not-empty-string)
|
(s/def ::lang (s/nilable ::us/not-empty-string))
|
||||||
(s/def ::path ::us/string)
|
(s/def ::path ::us/string)
|
||||||
(s/def ::profile-id ::us/uuid)
|
(s/def ::profile-id ::us/uuid)
|
||||||
(s/def ::password ::us/not-empty-string)
|
(s/def ::password ::us/not-empty-string)
|
||||||
|
|
|
@ -99,6 +99,10 @@
|
||||||
(mf/unmount (dom/get-element "modal"))
|
(mf/unmount (dom/get-element "modal"))
|
||||||
(init-ui))
|
(init-ui))
|
||||||
|
|
||||||
|
(add-watch i18n/locale "locale" (fn [_ _ o v]
|
||||||
|
(when (not= o v)
|
||||||
|
(reinit))))
|
||||||
|
|
||||||
(defn ^:dev/after-load after-load
|
(defn ^:dev/after-load after-load
|
||||||
[]
|
[]
|
||||||
(reinit))
|
(reinit))
|
||||||
|
|
|
@ -119,7 +119,7 @@
|
||||||
ptk/EffectEvent
|
ptk/EffectEvent
|
||||||
(effect [_ state s]
|
(effect [_ state s]
|
||||||
(reset! storage {})
|
(reset! storage {})
|
||||||
(i18n/set-default-locale!))))
|
(i18n/reset-locale))))
|
||||||
|
|
||||||
(defn logout
|
(defn logout
|
||||||
[]
|
[]
|
||||||
|
|
|
@ -32,7 +32,7 @@
|
||||||
(s/def ::fullname ::us/string)
|
(s/def ::fullname ::us/string)
|
||||||
(s/def ::email ::us/email)
|
(s/def ::email ::us/email)
|
||||||
(s/def ::password ::us/string)
|
(s/def ::password ::us/string)
|
||||||
(s/def ::lang ::us/string)
|
(s/def ::lang (s/nilable ::us/string))
|
||||||
(s/def ::theme ::us/string)
|
(s/def ::theme ::us/string)
|
||||||
(s/def ::created-at ::us/inst)
|
(s/def ::created-at ::us/inst)
|
||||||
(s/def ::password-1 ::us/string)
|
(s/def ::password-1 ::us/string)
|
||||||
|
@ -50,17 +50,13 @@
|
||||||
;; --- Profile Fetched
|
;; --- Profile Fetched
|
||||||
|
|
||||||
(defn profile-fetched
|
(defn profile-fetched
|
||||||
([data] (profile-fetched nil data))
|
[{:keys [fullname] :as data}]
|
||||||
([on-success {:keys [fullname] :as data}]
|
|
||||||
(us/verify ::profile data)
|
(us/verify ::profile data)
|
||||||
(ptk/reify ::profile-fetched
|
(ptk/reify ::profile-fetched
|
||||||
ptk/UpdateEvent
|
ptk/UpdateEvent
|
||||||
(update [_ state]
|
(update [_ state]
|
||||||
(assoc state :profile
|
(assoc state :profile
|
||||||
(cond-> data
|
(cond-> data
|
||||||
(nil? (:lang data))
|
|
||||||
(assoc :lang cfg/default-language)
|
|
||||||
|
|
||||||
(nil? (:theme data))
|
(nil? (:theme data))
|
||||||
(assoc :theme cfg/default-theme))))
|
(assoc :theme cfg/default-theme))))
|
||||||
|
|
||||||
|
@ -68,25 +64,22 @@
|
||||||
(effect [_ state stream]
|
(effect [_ state stream]
|
||||||
(let [profile (:profile state)]
|
(let [profile (:profile state)]
|
||||||
(swap! storage assoc :profile profile)
|
(swap! storage assoc :profile profile)
|
||||||
(i18n/set-current-locale! (:lang profile))
|
(i18n/set-locale! (:lang profile))
|
||||||
(theme/set-current-theme! (:theme profile))
|
(theme/set-current-theme! (:theme profile))))))
|
||||||
(when on-success
|
|
||||||
(on-success)))))))
|
|
||||||
|
|
||||||
;; --- Fetch Profile
|
;; --- Fetch Profile
|
||||||
|
|
||||||
(defn fetch-profile
|
(defn fetch-profile
|
||||||
([] (fetch-profile nil))
|
[]
|
||||||
([on-success]
|
|
||||||
(reify
|
(reify
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state s]
|
||||||
(->> (rp/query! :profile)
|
(->> (rp/query! :profile)
|
||||||
(rx/map (partial profile-fetched on-success))
|
(rx/map profile-fetched)
|
||||||
(rx/catch (fn [error]
|
(rx/catch (fn [error]
|
||||||
(if (= (:type error) :not-found)
|
(if (= (:type error) :not-found)
|
||||||
(rx/of (rt/nav :auth-login))
|
(rx/of (rt/nav :auth-login))
|
||||||
(rx/empty)))))))))
|
(rx/empty))))))))
|
||||||
|
|
||||||
;; --- Update Profile
|
;; --- Update Profile
|
||||||
|
|
||||||
|
@ -95,15 +88,19 @@
|
||||||
(us/assert ::profile data)
|
(us/assert ::profile data)
|
||||||
(ptk/reify ::update-profile
|
(ptk/reify ::update-profile
|
||||||
ptk/WatchEvent
|
ptk/WatchEvent
|
||||||
(watch [_ state s]
|
(watch [_ state stream]
|
||||||
(let [mdata (meta data)
|
(let [mdata (meta data)
|
||||||
on-success (:on-success mdata identity)
|
on-success (:on-success mdata identity)
|
||||||
on-error (:on-error mdata identity)
|
on-error (:on-error mdata identity)]
|
||||||
handle-error #(do (on-error (:payload %))
|
(rx/merge
|
||||||
(rx/empty))]
|
|
||||||
(->> (rp/mutation :update-profile data)
|
(->> (rp/mutation :update-profile data)
|
||||||
(rx/map (constantly (fetch-profile on-success)))
|
(rx/map fetch-profile)
|
||||||
(rx/catch rp/client-error? handle-error))))))
|
(rx/catch on-error))
|
||||||
|
(->> stream
|
||||||
|
(rx/filter (ptk/type? ::profile-fetched))
|
||||||
|
(rx/take 1)
|
||||||
|
(rx/tap on-success)
|
||||||
|
(rx/ignore)))))))
|
||||||
|
|
||||||
;; --- Request Email Change
|
;; --- Request Email Change
|
||||||
|
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
(ns app.main.ui.settings.options
|
(ns app.main.ui.settings.options
|
||||||
(:require
|
(:require
|
||||||
[app.common.spec :as us]
|
[app.common.spec :as us]
|
||||||
|
[app.common.data :as d]
|
||||||
[app.main.data.messages :as dm]
|
[app.main.data.messages :as dm]
|
||||||
[app.main.data.users :as du]
|
[app.main.data.users :as du]
|
||||||
[app.main.refs :as refs]
|
[app.main.refs :as refs]
|
||||||
|
@ -21,7 +22,7 @@
|
||||||
[cljs.spec.alpha :as s]
|
[cljs.spec.alpha :as s]
|
||||||
[rumext.alpha :as mf]))
|
[rumext.alpha :as mf]))
|
||||||
|
|
||||||
(s/def ::lang (s/nilable ::us/not-empty-string))
|
(s/def ::lang (s/nilable ::us/string))
|
||||||
(s/def ::theme (s/nilable ::us/not-empty-string))
|
(s/def ::theme (s/nilable ::us/not-empty-string))
|
||||||
|
|
||||||
(s/def ::options-form
|
(s/def ::options-form
|
||||||
|
@ -38,6 +39,9 @@
|
||||||
(defn- on-submit
|
(defn- on-submit
|
||||||
[form event]
|
[form event]
|
||||||
(let [data (:clean-data @form)
|
(let [data (:clean-data @form)
|
||||||
|
data (cond-> data
|
||||||
|
(empty? (:lang data))
|
||||||
|
(assoc :lang nil))
|
||||||
mdata {:on-success (partial on-success form)
|
mdata {:on-success (partial on-success form)
|
||||||
:on-error (partial on-error form)}]
|
:on-error (partial on-error form)}]
|
||||||
(st/emit! (du/update-profile (with-meta data mdata)))))
|
(st/emit! (du/update-profile (with-meta data mdata)))))
|
||||||
|
@ -54,12 +58,10 @@
|
||||||
[:h2 (t locale "labels.language")]
|
[:h2 (t locale "labels.language")]
|
||||||
|
|
||||||
[:div.fields-row
|
[:div.fields-row
|
||||||
[:& fm/select {:options [{:label "English" :value "en"}
|
[:& fm/select {:options (d/concat [{:label "Auto (browser)" :value ""}]
|
||||||
{:label "Français" :value "fr"}
|
i18n/supported-locales)
|
||||||
{:label "Español" :value "es"}
|
|
||||||
{:label "Русский" :value "ru"}]
|
|
||||||
:label (t locale "dashboard.select-ui-language")
|
:label (t locale "dashboard.select-ui-language")
|
||||||
:default "en"
|
:default ""
|
||||||
:name :lang}]]
|
:name :lang}]]
|
||||||
|
|
||||||
[:h2 (t locale "dashboard.theme-change")]
|
[:h2 (t locale "dashboard.theme-change")]
|
||||||
|
|
|
@ -2,8 +2,10 @@
|
||||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
;;
|
;;
|
||||||
;; Copyright (c) 2015-2016 Juan de la Cruz <delacruzgarciajuan@gmail.com>
|
;; This Source Code Form is "Incompatible With Secondary Licenses", as
|
||||||
;; Copyright (c) 2015-2019 Andrey Antukh <niwi@niwi.nz>
|
;; defined by the Mozilla Public License, v. 2.0.
|
||||||
|
;;
|
||||||
|
;; Copyright (c) 2020 UXBOX Labs SL
|
||||||
|
|
||||||
(ns app.util.i18n
|
(ns app.util.i18n
|
||||||
"A i18n foundation."
|
"A i18n foundation."
|
||||||
|
@ -17,9 +19,40 @@
|
||||||
[app.util.storage :refer [storage]]
|
[app.util.storage :refer [storage]]
|
||||||
[app.util.transit :as t]))
|
[app.util.transit :as t]))
|
||||||
|
|
||||||
(defonce locale (l/atom (or (get storage ::locale)
|
(def supported-locales
|
||||||
cfg/default-language)))
|
[{:label "English" :value "en"}
|
||||||
|
{:label "Español" :value "es"}
|
||||||
|
{:label "Français (community)" :value "fr"}
|
||||||
|
{:label "Русский (community)" :value "ru"}
|
||||||
|
{:label "简体中文 (community)" :value "zh_cn"}])
|
||||||
|
|
||||||
|
(defn- parse-locale
|
||||||
|
[locale]
|
||||||
|
(let [locale (-> (.-language js/navigator)
|
||||||
|
(str/lower)
|
||||||
|
(str/replace "-" "_"))]
|
||||||
|
(cond-> [locale]
|
||||||
|
(str/includes? locale "_")
|
||||||
|
(conj (subs locale 0 2)))))
|
||||||
|
|
||||||
|
(def ^:private browser-locales
|
||||||
|
(delay
|
||||||
|
(-> (.-language js/navigator)
|
||||||
|
(parse-locale))))
|
||||||
|
|
||||||
|
(defn- autodetect
|
||||||
|
[]
|
||||||
|
(let [supported (into #{} (map :value supported-locales))]
|
||||||
|
(loop [locales (seq @browser-locales)]
|
||||||
|
(if-let [locale (first locales)]
|
||||||
|
(if (contains? supported locale)
|
||||||
|
locale
|
||||||
|
(recur (rest locales)))
|
||||||
|
cfg/default-language))))
|
||||||
|
|
||||||
(defonce translations #js {})
|
(defonce translations #js {})
|
||||||
|
(defonce locale (l/atom (or (get storage ::locale)
|
||||||
|
(autodetect))))
|
||||||
|
|
||||||
;; The traslations `data` is a javascript object and should be treated
|
;; The traslations `data` is a javascript object and should be treated
|
||||||
;; with `goog.object` namespace functions instead of a standart
|
;; with `goog.object` namespace functions instead of a standart
|
||||||
|
@ -31,14 +64,21 @@
|
||||||
[data]
|
[data]
|
||||||
(set! translations data))
|
(set! translations data))
|
||||||
|
|
||||||
(defn set-current-locale!
|
(defn set-locale!
|
||||||
[v]
|
[lang]
|
||||||
(swap! storage assoc ::locale v)
|
(if lang
|
||||||
(reset! locale v))
|
(do
|
||||||
|
(swap! storage assoc ::locale lang)
|
||||||
|
(reset! locale lang))
|
||||||
|
(do
|
||||||
|
(reset! locale (autodetect)))))
|
||||||
|
|
||||||
(defn set-default-locale!
|
(defn reset-locale
|
||||||
|
"Set the current locale to the browser detected one if it is
|
||||||
|
supported or default locale if not."
|
||||||
[]
|
[]
|
||||||
(set-current-locale! cfg/default-language))
|
(swap! storage dissoc ::locale)
|
||||||
|
(reset! locale (autodetect)))
|
||||||
|
|
||||||
(deftype C [val]
|
(deftype C [val]
|
||||||
IDeref
|
IDeref
|
||||||
|
|
Loading…
Add table
Reference in a new issue