clojureScript页面国际化(i18n多语言)实现

2020-04-04  本文已影响0人  小马将过河

先看下效果(证明是实践过的)

中文
英文

国际化方案比较多,页面上的国际化一般比较简单,麻烦的还是数据库的数据的国际化。
本地cljs里的国际化采用前端db的atom控制当前语言,所有可见的翻译分为页面部分和数据库部分,最后在通过接口拿到数据库的翻译后跟页面的进行merge。

方案原理

  1. 将当前用户设定的语言保存在本地localstorage,并且保存在页面db中。
  2. 切换语言时,每个需要国际化的文案前增加i18n-str函数调用,实时获取目标文案对应的i18n文案。
  3. 浏览器被刷新时从localstorage中回复已经选择的语音类型。
  4. 可视的多语言文案,分为页面部分数数据库部分,在前端进行merge处理,保存成一个。

前提

前端使用re-frame、kee-frame、shadow-cljs、antd框架

当前方案核心代码

分页页面部分,切换语言部分(保存db,保存localstorage,防止手动刷新页面时数据还原)。

1. 页面切换代码

继续使用antd组件

(def lang (rf/subscribe [:i18n/lang]))

;; 语言
(def ^:private language
  {:zh-cn "中文"
   :en-us "English"
   :ja-jp "日本語"})

;; 语言菜单
(defn- dropdown-menu []
  [:> ant/Menu
   {:className "menu"
    :onClick   (fn [menu]
                 (let [value (js->clj menu :keywordize-keys true)]
                   (rf/dispatch [:i18n/change-lang (keyword (:key value))])))}
   [:> MenuItem {:key "zh-cn" :title "中文"}
    [:span (i18n-str "中文")]]
   [:> MenuItem {:key "en-us" :title "英文"}
    [:span (i18n-str "英文")]]
   [:> MenuItem {:key "ja-jp" :title "日文"}
    [:span (i18n-str "日文")]]])

[:div {:style {:margin-left 20
                         :font-size   "14px"
                         :font-family "PingFangSC-Medium,PingFang SC"
                         :font-weight 500
                         :color       "rgba(0,0,0,1)"}}
           [:> ant/Dropdown {:overlay (reagent.core/as-element [dropdown-menu])}
            [:span (i18n-str (or (get language @lang) "中文"))]]]

2. 切换和保存当前语言

;;通过key设置和获取localstorage里的数据
(defn set-local-storage [key value]
  (.setItem js/localStorage key value))

(defn get-local-storage [key]
  (.getItem js/localStorage key))

;;只要路由变化,就要触发获取当前语言的逻辑
(kf/reg-controller
 :lang-controller
 {:params (constantly true)
  :start  [::set-lang-by-local]})

;;如果页面刷新的话从localstorage里获取
(kf/reg-event-fx
 ::set-lang-by-local
 (fn [_ [_ _]]
   (when-not @(rf/subscribe [:i18n/lang])
     (if (get-current-lang)
       (rf/dispatch [:i18n/change-lang (get-current-lang)])
       (rf/dispatch [:i18n/change-lang :zh-cn])))
   {:dispatch [:request/get {:url (:get-lang-map mutil-lang)
                             :params {:hostname (.. js/window -location -hostname)}   ;;此处根据当前域名获取该域名的对应租户的多语言文案
                             :callback-event ::save-db-lang}]}))

(kf/reg-event-fx
 ::save-db-lang
 (fn [{:keys [db]} [db-lang-map]]
   {:db (-> db
            (assoc-in [:db-lang-map] db-lang-map))}))

(rf/reg-event-fx
 :i18n/change-lang
 (fn [{:keys [db]} [_ data]]
   (js/console.log "切换语言到:" data)
   (set-current-lang data)
   {:db (assoc-in db [:global :lang] data)}))

(rf/reg-sub
 :i18n/lang
 (fn [data]
   (get-in data [:global :lang])))

(rf/reg-sub
 :i18n/db-lang-map
 (fn [db]
   (get-in db [:db-lang-map])))

(defn- merge-lang-map
  "对页面上的文案和db里的文案进行一次merge"
  [page-lang-map db-lang-map]
  (if db-lang-map
    (merge page-lang-map
           (#(zipmap (map :key %) (map :value %))
            db-lang-map))
    page-lang-map))

;;返回当前语言的关键字
(defn i18n-str [s]
  (let [lang (rf/subscribe [:i18n/lang])
        db-lang-map (rf/subscribe [:i18n/db-lang-map])]
    (get-in (merge-lang-map language-map @db-lang-map)
            [s @lang] s)))

3. 页面文案翻译

上面代码里用到的language-map类似如下结构:

(def language-map
  { "切换语言"                      {:en-us "Switch language"  :ja-jp "言語を切り替え"}
     "中文"                        {:en-us "Chinese"           :ja-jp "中国語"}
     "英文"                        {:en-us "English"           :ja-jp "英語"}
     "日文"                        {:en-us "Japanese"          :ja-jp "日本語"}
     "体验门店"                     {:en-us "Experience Store"  :ja-jp "店を体験する"}
     "返回首页"                     {:en-us "Back to Home"      :ja-jp "ホームを戻す"}}
  )

4. 数据库返回的文案

即上文中:i18n/db-lang-map这个event从db中获取的对象,从接口获取的存在前端db中数据结构如下:

 [
        {
            "key": "双排六粒",
            "value": {
                "en-us": "Double six buttons",
                "ja-jp": "w6*3"
            }
        },
        {
            "key": "下摆(成衣)",
            "value": {
                "en-us": "Bottom(garment)",
                "ja-jp": "蹴廻し(上がり寸法)"
            }
        },
        {
            "key": "平钉纽扣",
            "value": {
                "en-us": "Level buttons",
                "ja-jp": "平钉钮釦"
            }
        }
]

这样将数据库中的和页面上的进行merge后使用。
当然,我们产品是因为对多个租户,各租户的翻译不同,所以页面上没有往DB里重复保存,采用merge两端的形式。简单的可以只在数据库维护。

改进点

+----------------+--------------+------+-----+-------------------+-------+
| Field          | Type         | Null | Key | Default           | Extra |
+----------------+--------------+------+-----+-------------------+-------+
| id             | varchar(40)  | NO   | PRI | NULL              |       |
| company_id     | varchar(40)  | NO   |     | NULL              |       |
| lang_key       | varchar(255) | NO   |     | NULL              |       |
| lang_value     | varchar(255) | NO   |     | NULL              |       |
| lang           | varchar(40)  | NO   |     | NULL              |       |
| delete_flag    | varchar(4)   | YES  |     | 0                 |       |
| create_time    | timestamp    | NO   |     | CURRENT_TIMESTAMP |       |
| create_user_id | varchar(40)  | YES  |     | NULL              |       |
| update_time    | timestamp    | NO   |     | CURRENT_TIMESTAMP |       |
| update_user_id | varchar(40)  | YES  |     | NULL              |       |
+----------------+--------------+------+-----+-------------------+-------+

一个文案的翻译数据如下:

INSERT INTO  `t_store_language`(`id`, `company_id`, `lang_key`, `lang_value`, `lang`, `delete_flag`, `create_time`, `create_user_id`, `update_time`, `update_user_id`) VALUES ('611348', '61', '常规(9个工作日)', 'Regular (9 working days)', 'en-us', '0', '2020-04-01 00:00:00', NULL, '2020-04-01 00:00:00', NULL);
INSERT INTO  `t_store_language`(`id`, `company_id`, `lang_key`, `lang_value`, `lang`, `delete_flag`, `create_time`, `create_user_id`, `update_time`, `update_user_id`) VALUES ('611359', '61', '常规(9个工作日)', '普通(9稼動日)', 'ja-jp', '0', '2020-04-01 00:00:00', NULL, '2020-04-01 00:00:00', NULL);

这个是便于扩展的,而页面上就不是那样的,如同上面的language-map,如果再增加一门比如韩语的话,需要逐项在原来的数据上修改,不利于扩展。

改进方向:
一个语音一个map,最后将多个语言的文案进行合并

仓促下没有考虑太多,如有更好的方案,欢迎交流。QQ:389709260

上一篇下一篇

猜你喜欢

热点阅读