# i18n

Simple i18n implementation with API inspired by vue-i18n (opens new window)

# Parameters

import { useI18n, buildI18n, setI18n } from "vue-composable";

interface i18nOption {
  locale: string; // start locale
  fallback?: string; // fallback locale


  // override the default resolver
  resolve?: (
    i18n: i18n, // current locale messages
    path: Readonly<RefTyped<string>>,
    args: RefTyped<FormatObject> | Array<FormatValue> |  undefined
  ) => RefTyped<string>;

  notFoundFallback?: boolean, // fallback message if the key not found in the current locale `default:true`

    /*
   localized messages, the first level property is the locale name
  */
  messages: {
    en: {
      /*object based messages*/
    },
    'en-GB': {
      /*object based messages*/
    },
    pt: ()=> import('/locale/pt').default, // also supports lazy loading
  },
}

useI18n(options);


// dependency injection i18n
setI18n(options);

// inject i18n if no arguments passed
useI18n(); // retrieves the i18n


// retrieves i18n object without injecting
buildI18n(options);

Parameters Type Required Description
option i18nOption false i18n options

# Typescript

You can specify the i18n types in multiple ways

# Options

If options are passed the i18n result will be based on the definition locale and the messages[locale].

const { i18n } = useI18n({
  locale: "en",
  fallback: "en",
  messages: {
    en: {
      hello: "hello world",
    },
    pt: {
      hello2: "Olá mundo",
    },
  },
});

i18n.hello; // correct, because is the selected locale
i18n.hello2; // typescript error, it doesn't exist in the type

# Templated

If the i18n definition is injected in the parent component, you can pass the messages using templated overload

// types.d.ts
interface I18nMessages {
  hello: string;
}

// component.vue
import { I18nMessages } from "./types.d.ts";

const { i18n } = useI18n<I18nMessages>();

# Global definition

If you don't want to import the type on every component, you can describe it globally

// types.d.ts
import { i18n } from "vue-composable";

declare module "vue-composable" {
  interface i18n {
    hello: string;
  }
}

// component.vue
const { i18n } = useI18n();

i18n.hello; // typescript has the definition

# JSON file

You may have the translations in a json file, you can use the types from that json file.

// tsconfig.json
{
  "compilerOptions": {
    "resolveJsonModule": true,
  }
}

// locales/en.json
{
  "hello": "Hello"
}

// types.d.ts

import { i18n } from "vue-composable";
type json = typeof import("@/locales/en.json");
declare module "vue-composable" {
  interface i18n extends json {}
}

// App.vue

import {setI18n} from 'vue-composable'
import en from  '@/locales/en.json',

setup(){
  setI18n({
    locale: 'en',
    messages: {
      en,
      es: ()=> import('@/locales/es.json'),
      // etc...
    }
  })
}


// Child.vue

const { i18n } = useI18n();

i18n.hello; // typescript has the definition
i18n.hello2; // type error

# State

The useI18n function exposes the following reactive state:

import { useI18n } from "vue-composable";

const { locale, locales, i18n } = useI18n();
State Type Description
locale Ref<String> Current locale
locales Ref<String> Array of available locales
i18n Ref<i18n> Object based i18n access

# Methods

The useI18n function exposes the following methods:

import { useI18n } from "vue-composable";

const { $t, $ts, addLocale, removeLocale } = useI18n();
Signature Description
$t(path, args?) Retrieve localised message
$ts(path, args?) Same as $t but returns string instead of ref<string>
addLocale(locale, messages) add new locale
removeLocale(locale) remove locale

# Example

Hello

Today is: Saturday

Today is: Saturday


Custom locale

# Code

<template>
  <div>
    <div>
      <select v-model="locale">
        <option v-for="l in locales" :key="l" :value="l">
          {{ i18n.locales[l] }}
        </option>
      </select>
      <div>
        <label for="name">{{ i18n.input.name.label }}</label>
        <input
          name="name"
          v-model="name"
          :placeholder="i18n.input.name.placeholder"
        />
      </div>
    </div>

    <h1>{{ i18n.title }}</h1>
    <h5>{{ $t("hello", { name }).value }}</h5>

    <p>
      {{
        $t("currentDate", { day: $t(`weekDays[${new Date().getDay()}]`) }).value
      }}
    </p>
    <p>
      {{ $t("currentDate", { day: i18n.weekDays[new Date().getDay()] }).value }}
    </p>

    <hr />

    <h3>Custom locale</h3>

    <div>
      <button v-if="locales.indexOf('custom') < 0" @click="addCustomLocale">
        Add custom locale
      </button>
      <button v-else @click="removeCustomLocale">Remove custom locale</button>
    </div>

    <textarea v-model="customLocaleJson"></textarea>
  </div>
</template>

<script>
import Vue from "vue";
import { useI18n, promisedTimeout } from "vue-composable";
import { ref, computed, watch, i18n } from "@vue/composition-api";

export default {
  setup() {
    const name = ref("");
    const i18n = useI18n({
      locale: "en",
      fallback: "en",
      messages: {
        en: {
          locales: {
            en: "English",
            pt: "Portuguese",
            es: "Spanish",
            custom: "YourLocale"
          },

          weekDays: [
            "Sunday",
            "Monday",
            "Tuesday",
            "Wednesday",
            "Thursday",
            "Friday",
            "Saturday"
          ],

          input: {
            name: {
              label: "Name",
              placeholder: "Your name"
            }
          },
          hello: "Hello {name}",
          currentDate: "Today is: {day}"
        },
        pt: {
          locales: {
            en: "Inglês",
            pt: "Português",
            es: "Espanhol"
          },

          weekDays: [
            "Domingo",
            "Segunda-feira",
            "Terca-feira",
            "Quarta-feira",
            "Quinta-feira",
            "Sexta-feira",
            "Sábado"
          ],

          input: {
            name: {
              label: "Nome",
              placeholder: "O teu nome"
            }
          },
          hello: "Olá {name}"
          currentDate: "Hoje e': {day}",
        },

        // promised based
        es: () =>
          promisedTimeout(10).then(() => ({
            locales: {
              en: "Ingles",
              pt: "Portugués",
              es: "Espanol"
            },

            weekDays: [
              "Domingo",
              "Lunes",
              "Martes",
              "Miércoles",
              "Jueves",
              "Viernes",
              "Sábado"
            ],

            input: {
              name: {
                label: "Nombre",
                placeholder: "Tu nombre"
              }
            },
            hello: "Holla {name}"
            currentDate: "Hoy es: {day}",
          }))
      }
    });

    const customLocale = ref({
      locales: {
        custom: "Awesome"
      },
      hello: "H3Y"
    });
    const customLocaleJson = ref(JSON.stringify(customLocale.value));

    const addCustomLocale = () => {
      i18n.addLocale("custom", customLocale.value);
      i18n.locale.value = "custom";
    };
    const removeCustomLocale = () => i18n.removeLocale("custom");

    watch(
      customLocaleJson,
      json => {
        try {
          customLocale.value = JSON.parse(json);
        } catch (e) {
          // err
        }
      },
      { lazy: true }
    );

    return {
      ...i18n,
      name,

      customLocaleJson,

      addCustomLocale,
      removeCustomLocale
    };
  }
};
</script>