Ver código fonte

feat: i18n by @musnow

Sendya 5 anos atrás
pai
commit
8c45238d3f

+ 1 - 0
package.json

@@ -27,6 +27,7 @@
     "vue": "^2.6.10",
     "vue-clipboard2": "^0.2.1",
     "vue-cropper": "0.4.9",
+    "vue-i18n": "^8.14.0",
     "vue-ls": "^3.2.1",
     "vue-quill-editor": "^3.0.6",
     "vue-router": "^3.1.2",

+ 46 - 0
src/components/tools/LangSelect.vue

@@ -0,0 +1,46 @@
+<template>
+  <a-dropdown>
+    <span class="action global-lang">
+      <a-icon type="global" style="font-size: 16px"/>
+    </span>
+    <a-menu slot="overlay" style="width: 150px;" @click="SwitchLang">
+      <a-menu-item key="zh-CN">
+        <a rel="noopener noreferrer">
+          <span role="img" aria-label="简体中文">🇨🇳</span> 简体中文
+        </a>
+      </a-menu-item>
+      <a-menu-item key="zh-TW">
+        <a rel="noopener noreferrer">
+          <span role="img" aria-label="繁体中文">🇭🇰</span> 繁体中文
+        </a>
+      </a-menu-item>
+      <a-menu-item key="en-US">
+        <a rel="noopener noreferrer">
+          <span role="img" aria-label="English">🇬🇧</span> English
+        </a>
+      </a-menu-item>
+      <a-menu-item key="pt-BR">
+        <a rel="noopener noreferrer">
+          <span role="img" aria-label="Português">🇧🇷</span> Português
+        </a>
+      </a-menu-item>
+    </a-menu>
+  </a-dropdown>
+</template>
+
+<script>
+import { mixin as langMixin } from '@/store/i18n-mixin'
+
+export default {
+  name: 'LangSelect',
+  mixins: [langMixin],
+  data () {
+    return {}
+  },
+  methods: {
+    SwitchLang (row) {
+      this.setLang(row.key)
+    }
+  }
+}
+</script>

+ 3 - 0
src/components/tools/UserMenu.vue

@@ -38,6 +38,7 @@
           </a-menu-item>
         </a-menu>
       </a-dropdown>
+      <lang-select />
     </div>
   </div>
 </template>
@@ -45,10 +46,12 @@
 <script>
 import NoticeIcon from '@/components/NoticeIcon'
 import { mapActions, mapGetters } from 'vuex'
+import LangSelect from '@/components/tools/LangSelect'
 
 export default {
   name: 'UserMenu',
   components: {
+    LangSelect,
     NoticeIcon
   },
   computed: {

+ 61 - 0
src/locales/index.js

@@ -0,0 +1,61 @@
+/**
+ * Vue i18n loader
+ * created by @musnow
+ * https://github.com/musnow
+ */
+import Vue from 'vue'
+import VueI18n from 'vue-i18n'
+// default language
+import enUS from './lang/en-US'
+// change default accept-language
+import { axios } from '@/utils/request'
+
+Vue.use(VueI18n)
+
+export const defaultLang = 'en-US'
+
+const messages = {
+  'en-US': {
+    ...enUS
+  }
+}
+
+const i18n = new VueI18n({
+  locale: defaultLang,
+  fallbackLocale: defaultLang,
+  messages
+})
+
+export default i18n
+
+const loadedLanguages = [defaultLang]
+
+// 从缓存設置中加载当前语言
+// if (Vue.ls.get('lang') !== null && defaultLang !== Vue.ls.get('lang')) {
+//   loadLanguageAsync(localStorage.lang)
+// }
+
+function setI18nLanguage (lang) {
+  i18n.locale = lang
+  axios.defaults.headers.common['Accept-Language'] = lang
+  document.querySelector('html').setAttribute('lang', lang)
+  return lang
+}
+
+export function loadLanguageAsync (lang = defaultLang) {
+  return new Promise(resolve => {
+    // 缓存语言设置
+    Vue.ls.set('lang', lang)
+    if (i18n.locale !== lang) {
+      if (!loadedLanguages.includes(lang)) {
+        return import(/* webpackChunkName: "lang-[request]" */ `./lang/${lang}`).then(msg => {
+          i18n.setLocaleMessage(lang, msg.default)
+          loadedLanguages.push(lang)
+          return setI18nLanguage(lang)
+        })
+      }
+      return resolve(setI18nLanguage(lang))
+    }
+    return resolve(lang)
+  })
+}

+ 9 - 0
src/locales/lang/en-US.js

@@ -0,0 +1,9 @@
+export default {
+  dashboard: {
+    workplace: {
+      project: 'Project Count',
+      teamRank: 'Team rank',
+      views: 'Views'
+    }
+  }
+}

+ 9 - 0
src/locales/lang/pt-BR.js

@@ -0,0 +1,9 @@
+export default {
+  dashboard: {
+    workplace: {
+      project: '...',
+      teamRank: '...',
+      views: '...'
+    }
+  }
+}

+ 9 - 0
src/locales/lang/zh-CN.js

@@ -0,0 +1,9 @@
+export default {
+  dashboard: {
+    workplace: {
+      project: '项目数',
+      teamRank: '团队排名',
+      views: '项目访问'
+    }
+  }
+}

+ 9 - 0
src/locales/lang/zh-TW.js

@@ -0,0 +1,9 @@
+export default {
+  dashboard: {
+    workplace: {
+      project: '項目數',
+      teamRank: '團隊排名',
+      views: '訪問量'
+    }
+  }
+}

+ 2 - 0
src/main.js

@@ -14,6 +14,7 @@ import bootstrap from './core/bootstrap'
 import './core/use'
 import './permission' // permission control
 import './utils/filter' // global filter
+import i18n from './locales'
 
 Vue.config.productionTip = false
 
@@ -23,6 +24,7 @@ Vue.use(VueAxios)
 new Vue({
   router,
   store,
+  i18n,
   created: bootstrap,
   render: h => h(App)
 }).$mount('#app')

+ 2 - 1
src/store/getters.js

@@ -9,7 +9,8 @@ const getters = {
   roles: state => state.user.roles,
   userInfo: state => state.user.info,
   addRouters: state => state.permission.addRouters,
-  multiTab: state => state.app.multiTab
+  multiTab: state => state.app.multiTab,
+  lang: state => state.i18n.lang
 }
 
 export default getters

+ 16 - 0
src/store/i18n-mixin.js

@@ -0,0 +1,16 @@
+import { mapState } from 'vuex'
+
+const mixin = {
+  computed: {
+    ...mapState({
+      currentLang: state => state.app.lang
+    })
+  },
+  methods: {
+    setLang (lang) {
+      this.$store.dispatch('SetLang', lang)
+    }
+  }
+}
+
+export { mixin }

+ 2 - 0
src/store/index.js

@@ -3,6 +3,7 @@ import Vuex from 'vuex'
 
 import app from './modules/app'
 import user from './modules/user'
+import i18n from './modules/i18n'
 import permission from './modules/permission'
 import getters from './getters'
 
@@ -12,6 +13,7 @@ export default new Vuex.Store({
   modules: {
     app,
     user,
+    i18n,
     permission
   },
   state: {

+ 24 - 0
src/store/modules/i18n.js

@@ -0,0 +1,24 @@
+import { loadLanguageAsync } from '@/locales'
+
+const i18n = {
+  state: {
+    lang: 'en-US'
+  },
+  mutations: {
+    SET_LANG: (state, lang) => {
+      state.lang = lang
+    }
+  },
+  actions: {
+    // 设置界面语言
+    SetLang ({ commit }, lang) {
+      return new Promise(resolve => {
+        commit('SET_LANG', lang)
+        loadLanguageAsync(lang)
+        resolve()
+      })
+    }
+  }
+}
+
+export default i18n

+ 3 - 3
src/views/dashboard/Workplace.vue

@@ -7,13 +7,13 @@
     <div slot="extra">
       <a-row class="more-info">
         <a-col :span="8">
-          <head-info title="项目数" content="56" :center="false" :bordered="false"/>
+          <head-info :title="$t('dashboard.workplace.project')" content="56" :center="false" :bordered="false"/>
         </a-col>
         <a-col :span="8">
-          <head-info title="团队排名" content="8/24" :center="false" :bordered="false"/>
+          <head-info :title="$t('dashboard.workplace.teamRank')" content="8/24" :center="false" :bordered="false"/>
         </a-col>
         <a-col :span="8">
-          <head-info title="项目访问" content="2,223" :center="false" />
+          <head-info :title="$t('dashboard.workplace.views')" content="2,223" :center="false" />
         </a-col>
       </a-row>
     </div>

+ 13 - 8
yarn.lock

@@ -713,10 +713,10 @@
     "@babel/helper-regex" "^7.4.4"
     regexpu-core "^4.5.4"
 
-"@babel/polyfill@^7.2.5":
+"@babel/polyfill@7.2.5":
   version "7.2.5"
-  resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
-  integrity sha512-8Y/t3MWThtMLYr0YNC/Q76tqN1w30+b0uQMeFUYauG2UGTR19zyUtFrAzT23zNtBxPp+LbE5E/nwV/q/r3y6ug==
+  resolved "https://registry.npm.taobao.org/@babel/polyfill/download/@babel/polyfill-7.2.5.tgz?cache=0&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2F%40babel%2Fpolyfill%2Fdownload%2F%40babel%2Fpolyfill-7.2.5.tgz#6c54b964f71ad27edddc567d065e57e87ed7fa7d"
+  integrity sha1-bFS5ZPca0n7d3FZ9Bl5X6H7X+n0=
   dependencies:
     core-js "^2.5.7"
     regenerator-runtime "^0.12.0"
@@ -3061,6 +3061,11 @@ copy-webpack-plugin@^4.6.0:
     p-limit "^1.0.0"
     serialize-javascript "^1.4.0"
 
+core-js@2.6.9:
+  version "2.6.9"
+  resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
+  integrity sha1-a0shRiDINBUuF5Mjcn/Bl0GwhPI=
+
 core-js@^2.4.0, core-js@^2.5.0, core-js@^2.5.7:
   version "2.6.8"
   resolved "https://registry.npmjs.org/core-js/-/core-js-2.6.8.tgz#dc3a1e633a04267944e0cb850d3880f340248139"
@@ -3071,11 +3076,6 @@ core-js@^2.6.5:
   resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895"
   integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==
 
-core-js@^2.6.9:
-  version "2.6.9"
-  resolved "https://registry.npm.taobao.org/core-js/download/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
-  integrity sha1-a0shRiDINBUuF5Mjcn/Bl0GwhPI=
-
 core-util-is@1.0.2, core-util-is@~1.0.0:
   version "1.0.2"
   resolved "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
@@ -10776,6 +10776,11 @@ vue-hot-reload-api@^2.3.0:
   resolved "https://registry.npmjs.org/vue-hot-reload-api/-/vue-hot-reload-api-2.3.3.tgz#2756f46cb3258054c5f4723de8ae7e87302a1ccf"
   integrity sha512-KmvZVtmM26BQOMK1rwUZsrqxEGeKiYSZGA7SNWE6uExx8UX/cj9hq2MRV/wWC3Cq6AoeDGk57rL9YMFRel/q+g==
 
+vue-i18n@^8.14.0:
+  version "8.14.0"
+  resolved "https://registry.npm.taobao.org/vue-i18n/download/vue-i18n-8.14.0.tgz?cache=0&sync_timestamp=1565612880763&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Fvue-i18n%2Fdownload%2Fvue-i18n-8.14.0.tgz#613cbbc21d71dc608cd085f8a94ea3a40badcd33"
+  integrity sha1-YTy7wh1x3GCM0IX4qU6jpAutzTM=
+
 vue-jest@^3.0.4:
   version "3.0.4"
   resolved "https://registry.npmjs.org/vue-jest/-/vue-jest-3.0.4.tgz#b6a2b0d874968f26fa775ac901903fece531e08b"