Nuxt3+Ant-design-vue internationalization, custom theme

Some thoughts on using the Nuxt3 version (“nuxt”: “^3.2.3”)

Some basic things to know such as

Page layout layout folder and app.vue file in the root directory

File-based routing directory pages file loading, routing nesting, parameter passing, etc.

Custom component components file loading (no need to introduce when using it, if you want to close it, you can close it in nuxt.config.ts)

//If you want to turn off automatic import, you can set imports.autoImport to false.

exportdefaultdefineNuxtConfig({
  imports: {
    autoImport: false
  }
})

Composable API Composables folder is to customize some of your own hooks

Nuxt has some built-in functions such as useCookie, useRoute, useRouter, useState, useHead, useFetch,,, specific official website view

The middleware middleware directory, like in Vue3, we do some routing guards, so we can write them here

Some third-party components used by the plugins plugin directory can be used here

At present, only these are used in the project. The specific use and description are still subject to the official website, which are just some of my own opinions. . .

The plugins currently used by the current project are as follows

"dependencies": {
    "@ant-design/icons-vue": "^6.1.0",
    "@pinia/nuxt": "^0.4.7",
    "ant-design-vue": "^3.2.15",
    "less": "^4.1.3",
    "pinia": "^2.0.33",
    "sass": "^1.59.3",
    "vue-i18n": "^9.2.2"
}

1. Install ant-design-vue

To use a third-party component library in Nuxt, you need to create a plugins folder in the root directory, and create your own plugin name in the folder (no need to introduce Nuxt2 in the nuxt.config.ts file and manually import it)

For example, create ant-design-vue.ts file under the plugins folder, the content is as follows, you can choose to import globally or load on demand

// import Antd from ‘ant-design-vue’; global import

import { Button,Layout } from'ant-design-vue';
exportdefaultdefineNuxtPlugin((nuxtApp) => {
    // nuxtApp.vueApp.use(Antd); global use
    nuxtApp.vueApp
    .use(Button)
    .use(Layout)
});

After the components are introduced, the style is introduced next, because the style of antd uses Less as the development language, so here I use the less file and need to download less

Of course, you can also use css style files to import style files (note that the style files introduced here will be loaded globally)

In the nuxt.config.ts file

exportdefaultdefineNuxtConfig({
    css:[
        // 'ant-design-vue/dist/antd.min.css',
        'ant-design-vue/dist/antd.less',
        '@/assets/css/animate.min.css',
      ],
})

Now you can use the component in the page

<template>
  <div>
    <a-layout>
      <a-layout-header>
        Header I am the head
        <a-space>
          <a-buttontype="primary">Simplified Chinese</a-button>
          <a-buttontype="primary">English</a-button>
        </a-space>
      </a-layout-header>
      <a-layout-content>
        Content I am content
      </a-layout-content>
      <a-layout-footer>Footer I am the bottom</a-layout-footer>
    </a-layout>
  </div>
</template>
?
<scriptlang="ts" setup>
</script>

2. Use vue-i18n to install first

Also like using ant-design-vue, you need to register in the plugins folder

Create a locales folder in the root directory

locales
 | - lang
 | --- ZH //Chinese language folder, you can create other folders or files in it. I like to use routing as folder creation.
 | ----- app.ts
 | --- zh.ts // Introduce all the definitions in the ZH folder in the above Chinese here, and the same goes for the EN folder below. If there is a third language, it can also be created in this way
 | --- EN
 | ----- app.ts
 | --- en.ts
 | - index.ts imports files zh.ts en.ts and exports

Take the above Chinese as an example

locales/lang/zh.ts

import app from './ZH/app'
importpaymentfrom'./ZH/payment'//Here is a folder I created with an index.ts file in it
?
exportdefault {
  ... app,
  ... payment
}

locales/lang/ZH/app.ts

exportdefault {
  app:{//Customization For example, the index of the payment folder above can use the name of the folder payment:{} to avoid duplication
    title:'Title',
    auth: 'This page requires login, please log in and try again'
  }
}

locales/index.ts

importzhLocalefrom'./lang/zh'; //Import local language locales/lang/zh.ts
importenLocalefrom'./lang/en'
let zh = {
  ...zhLocale,
}
let en = {
  ...enLocale,
}
export { zh,en }//export

Create a vue-i18n.ts file in the plugins folder

import { createI18n } from'vue-i18n';
import { zh,en } from '@/locales/index';//local language library
constmessage = {
  en,
  en,
}
exportdefaultdefineNuxtPlugin((nuxtApp) => {
  constCookie=useCookie('lang');
   //The built-in api is used here to quickly set and get cookies. Of course, it can also be set to use in localStorage without using useCookie
  Cookie.value=Cookie.value||'zh'
  consti18n = createI18n({
    legacy: false, //Must be set to FALSE when using vue3 combined API
    locale:Cookie.value||'zh',
    fallbackLocale: 'zh', // set fallback locale
    messages: message,
    globalInjection: true, //globally inject $t function
  })
  nuxtApp.vueApp.use(i18n)
});

Next is the language switch

Language switching can be performed on any page or component

<template>
  <div>
    <a-buttontype="primary"@click="switchLang('zh')">Simplified Chinese</a-button>
    <a-buttontype="primary"@click="switchLang('en')">English</a-button>
  </div>
</template>
<scriptlang="ts" setup>
  import { useI18n } from 'vue-i18n';
  const { locale:renameLocale } = useI18n();
    
  constcurrentLang=useCookie('lang');//Get the lang in the cookie
  constswitchLang= (val:string)=>{
    renameLocale.value=val;//Language used locally
    currentLang.value=val;//Change the value in the cookie
  }
</script>

After completing the above configuration, it can be used in the page

There are two ways to use it in the page and in JS.

<template>
  <section>
    <p>{<!-- -->{ $t('app. title') }}</p><!-- Title -->
  </section>
</template>
<scriptlang="ts" setup>
  import { useI18n } from 'vue-i18n';
  const { t } = useI18n();
  console.log(t('app.title'))//title
</script>

At this point, the basic operations have been completed, but we need to confirm the language used when the page is initialized

Then combined with the internationalization of ant-design-vue component library according to the official document configuration

ant-design-vue provides a Vue component ConfigProvider for global configuration of internationalized copywriting

In the app.vue file in the root directory, of course, don’t forget to introduce the price in the plugins/ant-design-vue.ts file

<template>
  <a-config-provider:locale="currentLang">
    .........
  </a-config-provider>
</template>
<scriptlang="ts" setup>
importzhfrom'ant-design-vue/es/locale/zh_CN';
importenfrom'ant-design-vue/es/locale/en_US';
import dayjs from 'dayjs';
// ConfigProvider does not include the internationalization of time components, you need to additionally import the internationalization files of time libraries (dayjs, momentjs, date-fns, etc.)
import 'dayjs/locale/zh-cn';
dayjs.locale('zh-cn');
constcurrentLang=ref(en) ;//zh en
</script>

But at this time, our antdesignvue will not change as we switch languages, because the switch button is not in the current app.vue file. If it is on the same page, you can create responsive data and use currentLang directly, but it is really a switch in the project. The language button is often just a simple switch and not together with other components

When we click the switch button, change the local language and change the lang field in the cookie. At this time, if app.vue can monitor and obtain the changed value in real time, then you can change the value of currentLang

Here I am using pinia. Like vuex, the data of pinia is also responsive.

First install pinia @pinia/nuxt

Create a folder store under the root directory. I have created two modules here to facilitate future data storage.

store
 | - modules
 | --- app.ts
 | --- user.ts
 |-index.ts

store/index.ts

import {userStore} from './modules/user';
import {appStore} from './modules/app'
exportconstuseStore= ()=>({
  userStore: userStore(),
  appStore:appStore()
})

store/modules/app.ts

?

import { defineStore } from 'pinia';
// The defineStore method has two parameters, the first parameter is the module name
// The second parameter is an option, there are three properties in the object, one less mutations than vuex.
exportconstappStore=defineStore('app', {
  state(){ // stores the variables of the module
    return {
      lang:useCookie('lang') ||'zh',
    }
  },
  getters: {
    getStateLang(state){// Used multiple times in the page, it will only be executed once here, and then cached
      returnstate.lang?state.lang:'zh'
    },
  },
  actions: {
    changeLang(lang:string) {
      this.lang=lang;
    },
  }
})

Next change the code in app.vue

We need to get the value in the current local cookie for the first time. If the store cannot get the default zh

<template>
  <a-config-provider:locale="currentLang">
    .........
  </a-config-provider>
</template>
<scriptlang="ts" setup>
importzhfrom'ant-design-vue/es/locale/zh_CN';
importenfrom'ant-design-vue/es/locale/en_US';
import dayjs from 'dayjs';
    
import { useStore } from '@/store'
import { storeToRefs } from 'pinia'
conststore = useStore();
    
//The first load to get the default zh
constcurrentLang=ref(switchLangFun(store.appStore.getStateLang))
//internationalized time component
watch(currentLang, val => {
  dayjs.locale(val.locale);
});
    
// Get the language in the store. storeToRefs can create a responsive data. The specific usage method still needs official documents
const { lang } = storeToRefs(store. appStore);
watch(lang,val=>{
  if(val) currentLang. value = switchLangFun(val)
})
// zh in the local cookie is a string
functionswitchLangFun(val:string){
  switch(val){
    case'zh':
      return
    case 'en':
      return
    case'hk':
      return hk
    case 'ar':
      return
    default:
      return
  }
}
</script>

Don’t forget that there is another operation here, that is, when we click to switch, we need to change the value of lang in the store

Change the component you just switched the language to

<template>
  <div>
    <a-buttontype="primary"@click="switchLang('zh')">Simplified Chinese</a-button>
    <a-buttontype="primary"@click="switchLang('en')">English</a-button>
  </div>
</template>
<scriptlang="ts" setup>
  import { useI18n } from 'vue-i18n';
  const { locale:renameLocale } = useI18n();
    
  constcurrentLang=useCookie('lang');//Get the lang in the cookie
  constswitchLang= (val:string)=>{
    renameLocale.value=val;//Language used locally
    currentLang.value=val;//Change the value in the cookie
      
      
    //Change the store
  store.appStore.changeLang(val) ///changeLang is the actions method in the store
  }
</script>

So far, the use of vue-i18n in this project has been completed. . . . . . .

But,,,, when I feel that everything is done, I found another problem

Because it is inevitable to encounter routing guards in vue projects

Through the understanding of Nuxt, we know that we can use routing guards in middleware middleware in Nuxt projects. Just create the file directly under the middleware folder. Here are global middleware and middleware that act on files How to use is subject to the official website

Create auth.global.ts file containing .global is global middleware. Create this middleware mainly to intercept pages that require token. If token does not exist, redirect login or prompt information (even if this middleware does not prompt information) Only do redirection, but there will definitely be other middleware that will prompt information to intercept, for example, a certain page requires certain administrator rights to enter, then you cannot redirect a certain page, you can only prompt and then terminate the navigation)

auth.global.ts

?

import { message asMessage,Modal } from'ant-design-vue'
import { useI18n } from 'vue-i18n';
exportdefaultdefineNuxtRouteMiddleware((to, from) => {
  if(process.client){//Only displayed on the client side Please refer to the official website for details
    const {t} = useI18n();
    consttoken=useCookie('app-token');
    //Some specific pages do not need token login register reset
    constnoTokenPath = ['/login', '/register', '/reset']
    if(!noTokenPath.includes(to.path)){
      if(!token. value){
        // Message. error('This is an error message');
        // return abortNavigation('500')
        Modal. error({
          title: 'Tip',
          content: t('app.auth'), //This page needs to be logged in, please log in and try again
        });
      }
    }
  }
?
})

Because the prompt information must also be internationalized and imported according to normal operations

import { useI18n } from ‘vue-i18n’; const { t } = useI18n();

But when we use t(‘app.auth’) the system will report an error Must be called at the top of a setup function because vue-i18n must be used in setup.

At this time we need the help of plugins

Change plugins/vue-i18n.ts file

import { createI18n } from'vue-i18n';
import { zh,en } from '@/locales/index';//local language library
constmessage = {
  en,
  en,
}
exportdefaultdefineNuxtPlugin((nuxtApp) => {
  constCookie=useCookie('lang');
   //The built-in api is used here to quickly set and get cookies. Of course, it can also be set to use in localStorage without using useCookie
  Cookie.value=Cookie.value||'zh'
  consti18n = createI18n({
    legacy: false, //Must be set to FALSE when using vue3 combined API
    locale:Cookie.value||'zh',
    fallbackLocale: 'zh', // set fallback locale
    messages: message,
    globalInjection: true, //globally inject $t function
  })
  nuxtApp.vueApp.use(i18n)
   //Added to provide a helper function and then call it in the middleware auth.global.ts
  return {
    provide:{
      myi18n:()=>i18n
    }
  }
});

Change middleware/auth.global.ts

import { message asMessage,Modal } from'ant-design-vue'
//import { useI18n } from 'vue-i18n';
exportdefaultdefineNuxtRouteMiddleware((to, from) => {
   
   ///Added
  const { $myi18n } = useNuxtApp()
  const { t } = $myi18n().global
    
  if(process.client){//Only displayed on the client side Please refer to the official website for details
    //const {t} = useI18n();
    consttoken=useCookie('app-token');
    //Some specific pages do not need token login register reset
    constnoTokenPath = ['/login', '/register', '/reset']
    if(!noTokenPath.includes(to.path)){
      if(!token. value){
        // Message. error('This is an error message');
        // return abortNavigation('500')
        Modal. error({
          title: 'Tip',
          content: t('app.auth'), //This page needs to be logged in, please log in and try again
        });
      }
    }
  }
?
})

Don’t forget to add and remove the vue-i18n console warning in nuxt.config.ts

exportdefaultdefineNuxtConfig({
    ...
    vite:{
        ...
        resolve: {
          alias: {
            'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js'
          }
        }
        ...
    }
    ...
})

So far, the use of vue-i18n has basically been completed, there is no problem with local testing, and there is no problem with packaging local testing. end. . . . . . . . . . . . .

How to customize the theme in combination with Ant-design-vue in Nuxt3.

I searched for information on the Internet for many days, but I couldn’t solve this problem. Finally, it was solved by combining the previous vue3 project. The original starting point was incorrect. I always thought that the starting point of the solution should be solved in nuxt. The final solution is solved in the Vite plug-in configuration. . Need to install plugin vite-plugin-style-import yarn add vite-plugin-style-import -D

import { createStyleImportPlugin,AntdResolve } from 'vite-plugin-style-import'
exportdefaultdefineNuxtConfig({
    ...
    vite:{
        plugins:[
          createStyleImportPlugin({
            resolves: [AntdResolve()],
          }),
        ],
        css: {
            preprocessorOptions: {
                less: {
                  modifyVars: {// change the theme
                    // hack: `true; @import (reference) "${path.resolve('src/assets/less/global.less')}";`,
                    'primary-color': '#2096f3', // global primary color
                    'link-color': '#2096f3', // link color
                    'success-color': '#00B86B', // success color
                    'warning-color': '#FF9500', // warning color
                  },
                  javascriptEnabled: true,
                },
              },
        }
    }
    ...
})