DEV Community

Cover image for Localization with vue-i18n, Vue2 composition api, typescript and vee-validate
pippo46
pippo46

Posted on

Localization with vue-i18n, Vue2 composition api, typescript and vee-validate

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
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

After that we only need 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.

//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),
  };
}
Enter fullscreen mode Exit fullscreen mode

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
// 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 };

Enter fullscreen mode Exit fullscreen mode

In order to import json files as modules we need to add this line to the tsconfig.json file

...
"resolveJsonModule": true,
...
Enter fullscreen mode Exit fullscreen mode

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

// 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");
Enter fullscreen mode Exit fullscreen mode

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.

// i18n/en.json
{
  "example": {
    "title": "Translated title"
  }
}
Enter fullscreen mode Exit fullscreen mode
// i18n/it.json
{
  "example": {
    "title": "Titolo tradotto"
  }
}
Enter fullscreen mode Exit fullscreen mode

Then we create a i18n-example.vue component

// 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>
Enter fullscreen mode Exit fullscreen mode

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 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

I switched my browser language and now I see the Italian title
Alt Text

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
Enter fullscreen mode Exit fullscreen mode

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

// 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 };
Enter fullscreen mode Exit fullscreen mode

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

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

extend("required", required);
Enter fullscreen mode Exit fullscreen mode
// main.ts
...
// import needed rules
import "@/utils/vee-validate";
...
Enter fullscreen mode Exit fullscreen mode

Finally we add the messages to ours json files

// i18n/it.json
{
  "example": {
    "title": "Titolo tradotto"
  },
  "validations": {}, // typescript does not complain in i18n/index.ts
  "fields": {
    "firstName": "First Name"
  }
}
Enter fullscreen mode Exit fullscreen mode

and add the validation in out test component as showed below

// main.ts
...
// import needed rules
import "@/utils/vee-validate";
...
Enter fullscreen mode Exit fullscreen mode

Finally we add the messages to ours json files

// 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() };
  },
});
Enter fullscreen mode Exit fullscreen mode

As you can see everything is working properly and vee-validate is using our custom message for the required rule

Alt Text

You can find the complete repo Here

Top comments (0)