Skip to content

Latest commit

 

History

History
389 lines (307 loc) · 8.08 KB

i18n.md

File metadata and controls

389 lines (307 loc) · 8.08 KB

i18n

Simple i18n implementation with API inspired by vue-i18n

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/core";

declare module "@vue-composable/core" {
  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/core";
type json = typeof import("@/locales/en.json");
declare module "@vue-composable/core" {
  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

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",
            "Thrusday",
            "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"
            }
          },
          currentDate: "Hoje e': {day}",

          hello: "Olá {name}"
        },

        // promised based
        es: () =>
          promisedTimeout(10).then(() => ({
            locales: {
              en: "Ingles",
              pt: "Portugués",
              es: "Espanol"
            },
            input: {
              name: {
                label: "Nombre",
                placeholder: "Tu nombre"
              }
            },

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

            hello: "Holla {name}"
          }))
      }
    });

    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>