美文网首页
clojureScript页面国际化(i18n多语言)实现

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

作者: 小马将过河 | 来源:发表于2020-04-04 21:55 被阅读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

    相关文章

      网友评论

          本文标题:clojureScript页面国际化(i18n多语言)实现

          本文链接:https://www.haomeiwen.com/subject/qyesphtx.html