others-How to add internationalization(i18n) to your vue 3 project and how to solve ' TypeError: Cannot read properties of undefined (reading '$t') problem?

1. Purpose

In this post, I will demonstrate how to add internationalization(i18n) to a vue3 project and how to solve TypeError: Cannot read properties of undefined (reading '$t') problem when trying to use $t outside the vue tempate.



2. How to add i18n to your vue3 project

You can just follow the below steps to add i18n to your project. Before you start, you should have already installed vue3, vue-cli and npm in your machine.

Step1: create a vue project

vue create vue3-i18n-example

you will get this output:

...
📄  Generating README.md...

🎉  Successfully created project vue3-i18n-example.
👉  Get started with the following commands:

Then test the new project by running it:

cd vue3-i18n-example
npm run serve

You should get this output:

  App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://10.1.1.2:8080/

  Note that the development build is not optimized.
  To create a production build, run npm run build.

Then open your favorate web browser,visit http://localhost:8080, you will get this:

Step 2: Add i18n plugin to our project

1) What is i18n?

Internationalization (sometimes shortened to “I18N , meaning “I - eighteen letters -N”) is the process of planning and implementing products and services so that they can easily be adapted to specific local languages and cultures, a process called localization

2) What is vue i18n plugin?

The vue i18n plugin is an internationalization plugin of Vue. js. It easily integrates some localization features to your Vue. js Application.

3) How to add vue 18n plugin to our project? We can use vue cli command line to install the plugin for our project as follows:

➜  vue3-i18n-example git:(main) ✗ vue add i18n                
? Still proceed? (y/N) y
```shell
Then we got this:
```shell
📦  Installing vue-cli-plugin-i18n...


added 29 packages, and audited 1580 packages in 12s

103 packages are looking for funding
  run `npm fund` for details

42 vulnerabilities (21 moderate, 16 high, 5 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
✔  Successfully installed plugin: vue-cli-plugin-i18n

? The locale of project localization. (en) 

It ask us the default locale of our project, we can just press enter to use the default value ‘en’, and then :

? The fallback locale of project localization. (en)

The above answer will tell the i18n to use which locale when the translation for specific key is not found. We can just press enter to use the default value. And then:

? The directory where store localization messages of project. It's stored under `src` directory. (locales) 

The above question let us choose the directory where to put our locale files , press enter to use the default value, and then:

? Enable legacy API (compatible [email protected]) mode ? (y/N)

If you don’t migrate from the old i18n plugin, just choose N.

So the total questions and answers of vue add i18n is as follows:

? The locale of project localization. en
? The fallback locale of project localization. en
? The directory where store localization messages of project. It's stored under `src` directory. locales
? Enable legacy API (compatible [email protected]) mode ? No

After above selections, we got this output:

🚀  Invoking generator for vue-cli-plugin-i18n...
📦  Installing additional dependencies...


added 18 packages, changed 1 package, and audited 1598 packages in 6s

104 packages are looking for funding
  run `npm fund` for details

42 vulnerabilities (21 moderate, 16 high, 5 critical)

To address issues that do not require attention, run:
  npm audit fix

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
⚓  Running completion hooks...

✔  Successfully invoked generator for plugin: vue-cli-plugin-i18n

We can test if our project is fine by running npm run serve again. And open your favorate web browser,visit http://localhost:8080, you will get this:

It does not change anything yet.

The project’s directory structure is as follows:

➜  vue3-i18n-example git:(main) ✗ tree  . -I 'node_modules|dist' 
.
├── README.md
├── babel.config.js
├── package-lock.json
├── package.json
├── public
│   ├── favicon.ico
│   └── index.html
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   ├── HelloI18n.vue
│   │   └── HelloWorld.vue
│   ├── i18n.js
│   ├── locales
│   │   └── en.json
│   ├── main.js
│   ├── router
│   │   └── index.js
│   ├── store
│   │   └── index.js
│   └── views
│       ├── About.vue
│       └── Home.vue
├── tests
│   └── unit
│       └── example.spec.js
└── vue.config.js

10 directories, 19 files

You can see that the i18n plugin created some new directories and files:

  • src/i18n.js, this js file is responsible for create and initialize i18n settings ```js import { createI18n } from ‘vue-i18n’

/**

  • Load locale messages *
  • The loaded JSON locale messages is pre-compiled by @intlify/vue-i18n-loader, which is integrated into vue-cli-plugin-i18n.
  • See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation */ function loadLocaleMessages() { const locales = require.context(‘./locales’, true, /[A-Za-z0-9-_,\s]+.json$/i) const messages = {} locales.keys().forEach(key => { const matched = key.match(/([A-Za-z0-9-_]+)./i) if (matched && matched.length > 1) { const locale = matched[1] messages[locale] = locales(key).default } }) return messages }

export default createI18n({ legacy: false, locale: process.env.VUE_APP_I18N_LOCALE || ‘en’, fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || ‘en’, messages: loadLocaleMessages() })


- src/main.js, this file is changed to add `i18n` to the app:

import { createApp } from ‘vue’ import App from ‘./App.vue’ import router from ‘./router’ import store from ‘./store’ import i18n from ‘./i18n’

createApp(App).use(i18n).use(store).use(router).mount(‘#app’)


- locales/en.json, this file is a `en` locale translation file for our project, we can add more locale files to this directory, e.g. cn.json for Chinese translation or fr.json for French translation.
```json
{
  "message": "hello i18n !!"
}
  • package.json is changed too:
    {
    "name": "vue3-i18n-example",
    "version": "0.1.0",
    "private": true,
    "scripts": {
      "serve": "vue-cli-service serve",
      "build": "vue-cli-service build",
      "test:unit": "vue-cli-service test:unit",
      "i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\""
    },
    "dependencies": {
      "core-js": "^3.6.5",
      "vue": "^3.0.0",
      "vue-i18n": "^9.1.0",
      "vue-router": "^4.0.0-0",
      "vuex": "^4.0.0-0"
    },
    "devDependencies": {
      "@intlify/vue-i18n-loader": "^3.0.0",
      "@vue/cli-plugin-babel": "~4.5.13",
      "@vue/cli-plugin-router": "~4.5.13",
      "@vue/cli-plugin-unit-jest": "~4.5.13",
      "@vue/cli-plugin-vuex": "~4.5.13",
      "@vue/cli-service": "~4.5.13",
      "@vue/compiler-sfc": "^3.0.0",
      "@vue/test-utils": "^2.0.0-0",
      "typescript": "~3.9.3",
      "vue-cli-plugin-i18n": "~2.3.1",
      "vue-jest": "^5.0.0-0"
    },
    "browserslist": [
      "> 1%",
      "last 2 versions",
      "not dead"
    ],
    "jest": {
      "preset": "@vue/cli-plugin-unit-jest",
      "transform": {
        "^.+\\.vue$": "vue-jest"
      }
    }
    }
    

    It added "vue-i18n": "^9.1.0", to dependencies, and "vue-cli-plugin-i18n": "~2.3.1", to devDependencies.

Step 3: Add hello world translation to our project.

Open our src/App.vue, change as follows:

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png" />
    <HelloWorld :msg="$t('welcomeMessage')" />  <!-- first change -->
  </div>
</template>

<script>
// @ is an alias to /src
import HelloWorld from "@/components/HelloWorld.vue";

export default {
  name: "Home",
  data() {           //second chagne
    msg: "";
  },
  components: {
    HelloWorld,
  },
};
</script>

You can see that we make two changes to the file, the first:

<HelloWorld :msg="$t('welcomeMessage')" />

We added a colon to msg to make it a vue binding variable, then it can use vue methods $t,

$t('welcomeMessage')

The above line means that the app should find the translation by key welcomeMessage in src/locales/xxx.json.

And the second change:

  data() {           //second chagne
    return {
      msg: "",
    };
  },

It use data() function to export a variable named msg to be bound in the vue template.

Then we open src/locales/en.json to add the English locale:

{
  "message": "hello i18n !!",
  "welcomeMessage": "Welcome to our new Vue.js and i18n App"
}

Save all the above changed files and refresh your browser , you should got this:

Step 4: add a new locale and change default locale to it

Now we can add a new locale ,e.g. cn(Chinese) for test.

  • Create a src/locales/cn.json with the following content: ```json { “message”: “hello i18n !!”, “welcomeMessage”: “你好,vue” }

And then open `src/i18n.js`, change as follows:
```js
import { createI18n } from 'vue-i18n'

/**
 * Load locale messages
 *
 * The loaded `JSON` locale messages is pre-compiled by `@intlify/vue-i18n-loader`, which is integrated into `vue-cli-plugin-i18n`.
 * See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation
 */
function loadLocaleMessages() {
  const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
  const messages = {}
  locales.keys().forEach(key => {
    const matched = key.match(/([A-Za-z0-9-_]+)\./i)
    if (matched && matched.length > 1) {
      const locale = matched[1]
      messages[locale] = locales(key).default
    }
  })
  return messages
}

export default createI18n({
  legacy: false,
  locale: 'cn',
  fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
  messages: loadLocaleMessages()
})

The line that is changed as follows:

  locale: 'cn',

The above configuration means that the default locale is cn, if not found use en as the locale.

Then reboot the app again, we got this:

3. Problem and solution

Sometimes, if we want to use the $t outside the tempate as follows, we will encounter problems:

<template>
    <div>
        
    </div>
</template>
<script>
import Card from "@/components/Card.vue";

const cardInfo = { cardName: this.$t("titleBase64Encoding"), cardView: "base64" }

export default {
    data() {
        return {
            cardName: cardInfo.cardName
        }
    }
}
</script>

We got this error message:

log.js?1afd:24 [HMR] Waiting for update signal from WDS...
CardList.vue?c995:22 Uncaught TypeError: Cannot read properties of undefined (reading '$t')
    at eval (CardList.vue?c995:22:1)
    at ./node_modules/cache-loader/dist/cjs.js?!./node_modules/babel-loader/lib/index.js!./node_modules/cache-loader/dist/cjs.js?!./node_modules/vue-loader-v16/dist/index.js?!./src/components/CardList.vue?vue&type=script&lang=js (app.js:974:1)
    at __webpack_require__ (app.js:849:30)
    at fn (app.js:151:20)
    at eval (CardList.vue?1afb:1:1)
    at ./src/components/CardList.vue?vue&type=script&lang=js (app.js:1627:1)
    at __webpack_require__ (app.js:849:30)
    at fn (app.js:151:20)
    at eval (CardList.vue?c958:1:1)
    at ./src/components/CardList.vue (app.js:1615:1)

the error line of code:

const cardInfo = { cardName: this.$t("titleBase64Encoding"), cardView: "base64" }

core error:

Uncaught TypeError: Cannot read properties of undefined (reading '$t')

The “cannot read property of undefined” error occurs when you attempt to access a property or method of a variable that is undefined . You can fix it by adding an undefined check on the variable before accessing it.

reason:

The “cannot read property of undefined” error occurs when you attempt to access a property or method of a variable that is undefined . You can fix it by adding an undefined check on the variable before accessing it.

solution:

change your i18n string definition as follows:

<template>
    <div>
        ...
    </div>
</template>
<script>
import Card from "@/components/Card.vue";

const cardInfo = { cardName: "base64CardTitle", cardView: "base64" }

export default {
    data() {
        return {
            cardName: cardInfo.cardName
        }
    }
}

then in your locales/en.json, code like this:

{
   "base64CardTitle": "Base64 encoding",
}

Now it works!

4. The code

The project’s source code has been uploaded to github, you can download or clone it: https://github.com/bswen/vuejs-project/tree/main/vue3-i18n-example



5. Summary

In this post, I demonstrated how to add internationalization(i18n) to your vue 3 project and how to solve ‘ TypeError: Cannot read properties of undefined (reading ‘$t’) problem . That’s it, thanks for your reading.