Localization with vue-i18n, Vue2 composition api, typescript and vee-validate
Tags: vue, typescript, webdev, i18n
Published: 2021-10-08 07:49:17

Hi everyone, I am a, hopefully, good developer and surely a bad writer (also English is not my native language), but I want to share the struggle I had searching for a working solution using vue-i18n in a vue2 application with typescript and composition api and share the solution.

Like every good developer I search the internet for solutions, but I had trouble finding something working out of the box. Luckily, after a few hours trying a could make vue-i18n working in my vue2 + typescript + composition api app, and this is my solution.

# 1. Setup

First thing to do is to create a new vue2 app using the vue CLI

```
vue create vue2-i18n-typescript
```
The second step is to install the **right versions of vue-i18n** package. In the tutorials I only found a generic install command, which at this point will get the latest version of the package that only work for vue3. You only see that is not working at runtime, but at compilation time everything seems fine. We will also install the composition api plugin for vue2

```
npm install vue-i18n@8.26.5 @vue/composition-api
```
After that we only need [vue-i18n-composable](https://github.com/intlify/vue-i18n-composable) package before setting up the localization. Here it comes the second problem: for some reason the package does not work correctly and we have to copy the code in a file in our application. 
Create a **vue-i18n-composable.ts** file in a **utils** folder and copy the following code.

```Typescript
//utils/vue-i18n-composable.ts
import Vue from "vue";
import VueI18n from "vue-i18n";
import {
  computed,
  getCurrentInstance,
  WritableComputedRef,
} from "@vue/composition-api";
import { VueConstructor } from "vue/types/umd";

let i18nInstance: VueI18n | undefined;

export function createI18n(
  options?: VueI18n.I18nOptions,
  vue: VueConstructor = Vue
): VueI18n {
  vue.use(VueI18n);
  i18nInstance = new VueI18n(options);

  return i18nInstance;
}

export interface Composer {
  locale: WritableComputedRef<string>;
  t: typeof VueI18n.prototype.t;
  tc: typeof VueI18n.prototype.tc;
  te: typeof VueI18n.prototype.te;
  d: typeof VueI18n.prototype.d;
  n: typeof VueI18n.prototype.n;
}

export function useI18n(): Composer {
  if (!i18nInstance) throw new Error("vue-i18n not initialized");

  const i18n = i18nInstance;

  const instance = getCurrentInstance();
  const vm =
    instance?.proxy ||
    ((instance as unknown) as InstanceType<VueConstructor>) ||
    new Vue({});

  const locale = computed({
    get() {
      return i18n.locale;
    },
    set(v: string) {
      i18n.locale = v;
    },
  });

  return {
    locale,
    t: vm.$t.bind(vm),
    tc: vm.$tc.bind(vm),
    d: vm.$d.bind(vm),
    te: vm.$te.bind(vm),
    n: vm.$n.bind(vm),
  };
}
```

Now we have everything we need to setup the localization. Create a **i18n** folder and create the following files:
1. **it.json**: json file containing Italian translations
2. **en.json**: json file containing English translations
3. **index.ts**: it contains translation setup

```Typescript
// i18n/index.ts
import en from "./en.json";
import it from "./it.json";

const languages = {
  en: en,
  it: it,
};

const messages = Object.assign(languages);
const langCode = navigator.language;
import { createI18n } from "@/utils/vue-i18n-composable";
const i18n = createI18n({
  locale: langCode,
  fallbackLocale: "en",
  messages: messages,
});

export { i18n };

```
In order to import json files as modules we need to add this line to the **tsconfig.json** file
```json
...
"resolveJsonModule": true,
...
```
In this example I use the browser language as starting locale, with English as fallback language (only Italian and English supported).

Finally we setup the composition api and import our i18n module in the **main.ts** file and we can use it in ours component

```Typescript
// main.ts
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import { i18n } from "./i18n/index";
import VueCompositionAPI from "@vue/composition-api";

Vue.use(VueCompositionAPI);

Vue.config.productionTip = false;

new Vue({
  i18n,
  router,
  store,
  render: (h) => h(App),
}).$mount("#app");
```

# 2. Example

Now we are ready to use the full potential of **vue-i18n**.
First we need to create a message in ours language json files.

```json
// i18n/en.json
{
  "example": {
    "title": "Translated title"
  }
}
```

```json
// i18n/it.json
{
  "example": {
    "title": "Titolo tradotto"
  }
}
```

Then we create a **i18n-example.vue** component

```Vue
// componenst/i18n-example.vue
<template>
  <div>
    <h1>{{ t("example.title") }}</h1>
  </div>
</template>

<script lang="ts">
import { defineComponent } from "@vue/composition-api";
import { useI18n } from "@/utils/vue-i18n-composable";

export default defineComponent({
  name: "i18nExample",
  setup() {
    return {
      ...useI18n(),
    };
  },
});
</script>
```
We only need three steps in a component in order to user the translations:
1. **import { useI18n } from "@/utils/vue-i18n-composable";**. This object allows to use the **vue-i18n** functions with the composition api. (we create utils/vue-i18n-composable.ts files from the [vue-i18n-composable](https://github.com/intlify/vue-i18n-composable) package)
2. **return { ...useI18n(), };**. this makes the translations functions available in the template.
3. **t("example.title")**: **t** is the function that performs the translation. **example.title** is the key we defined in our language json files. If the key is not found the same key is returned as result



Now run the application and see the result:
![English translatio](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/5ani1sqimkka3pwqmwrv.png)

I switched my browser language and now I see the Italian title
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ihp3cwd2oq3sil4bv2zm.png)

# 3. Vee-Validate
Now that vue-i18n is working fine, we will add [vee-validate](https://vee-validate.logaretm.com/v3. First we add the package. Here again we need to add the 8.x version for vue2 (version 4.x is for vue3).

```
npm install vee-validate@3.4.5
```
Next we need to go to **i18n/index.ts** file, import vee-validate localization files, add them to the json modules containing the translations and configure the validation to use the i18n object we created previously


```Typescript
// i18n/index.ts
import en from "./en.json";
import it from "./it.json";

// import vee validate localizations
import valIt from "vee-validate/dist/locale/it.json";
import valEn from "vee-validate/dist/locale/en.json";

// eventually overwrite vee-validate rules messages
// overwriting required message for Italian
valIt.messages.required = "Il campo {_field_} è obbligatorio";

// add vee-validate messages to it and en objects
it["validations"] = valIt.messages;
en["validations"] = valEn.messages;

// rest of the code from previous setup
const languages = {
  en: en,
  it: it,
};

const messages = Object.assign(languages);
const langCode = navigator.language;
import { createI18n } from "@/utils/vue-i18n-composable";
const i18n = createI18n({
  locale: langCode,
  fallbackLocale: "en",
  messages: messages,
});

// vee-validate configuration to use i18n plugin
import { configure } from "vee-validate";
configure({
  // this will be used to generate messages.
  defaultMessage: ((field: string, values: any) => {
    values._field_ = i18n.t(`fields.${field}`);
    return i18n.t(`validations.${values._rule_}`, values);
  }) as any,
});

export { i18n };
```
Before using the validation we have to import the rules we need to use (I am not going into vee-validate details, just remember that we import only the rules we will use).
In this example we import only the **required** rule, but you can find all the rules [here](https://vee-validate.logaretm.com/v3/guide/rules.html)

```Typescript
// utils/vee-validate.ts
import { extend } from "vee-validate";
import { required } from "vee-validate/dist/rules";

extend("required", required);
```

```Typescript
// main.ts
...
// import needed rules
import "@/utils/vee-validate";
...
```
Finally we add the messages to ours json files

```json
// i18n/it.json
{
  "example": {
    "title": "Titolo tradotto"
  },
  "validations": {}, // typescript does not complain in i18n/index.ts
  "fields": {
    "firstName": "First Name"
  }
}
```
and add the validation in out test component as showed below
```Typescript
// main.ts
...
// import needed rules
import "@/utils/vee-validate";
...
```
Finally we add the messages to ours json files

```Vue
// componenst/i18n-example.vue
<template>
  <div>
    <h1>{{ t("example.title") }}</h1>
    <div>
      <ValidationProvider
        rules="required"
        name="firstName"
        vid="firstNameValidator"
        v-slot="{ errors }"
        slim
      >
        <input type="text" size="sm" ref="input" v-model="firstName" />
        <div>{{ errors[0] }}</div>
      </ValidationProvider>
    </div>
  </div>
</template>

<script lang="ts">
import { defineComponent, Ref, ref } from "@vue/composition-api";
import { useI18n } from "@/utils/vue-i18n-composable";
import { ValidationProvider } from "vee-validate";

export default defineComponent({
  name: "i18nExample",
  components: {
    ValidationProvider,
  },
  setup() {
    const firstName: Ref<string> = ref("");
    return { firstName, ...useI18n() };
  },
});
```
As you can see everything is working properly and **vee-validate** is using our custom message for the **required** rule

![Alt Text](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/e84re0x94yc3gtdkqy5a.png)


You can find the complete repo [Here](https://github.com/pippo46/vue2-i18n-typescript)