@cepharum/vue3-i18n
v0.5.4
Published
a simple i18n library for Vue3 applications
Downloads
24
Readme
@cepharum/vue3-i18n
a simple i18n library for Vue3 applications
License
About
This library implements a component and helper functions for reactively mapping internationalized keys into localized strings based on hierarchical trees of translations per locale, each implemented as regular Javascript objects e.g. read from JSON files.
It supports data interpolation and some limited Markdown syntax for styling translations.
Due to its reactive approach, all translations get updated automatically e.g. on switching the locale and fetching its set of translations. The locale to use is read from current document or browser automatically. A custom locale can be selected explicitly to override locale read from document or browser.
Install
In a consuming Vue3 project, this library is installed using npm like this:
npm install @cepharum/vue3-i18n
Usage
A loader for your translations must be provided. It should be set up as early as possible, e.g. on mounting your project's root view in src/App.vue component.
<script setup>
import { useL10n } from "@cepharum/vue3-i18n";
useL10n().setLoader( locale => import(`./l10n/${locale}.json`) );
</script>
Create a folder src/l10n in your project and put a file name en.json there. It is going to provide all the translations like this:
{
"COMMON": {
"confirm": "Okay",
"cancel": "Cancel"
},
"FEATURE": {
"title": "Info",
"text": "This is the feature!"
}
}
Use this library's component <L10n>
to render text:
<script setup>
import { L10n } from "@cepharum/vue3-i18n";
</script>
<template>
<h1><L10n i18n="FEATURE.title" /></h1>
<p><L10n i18n="FEATURE.text" /></p>
<button><L10n i18n="COMMON.confirm" /></button>
</template>
VitePress
In VitePress, the default theme must be extended or a custom theme must be set up for registering vue3-i18n. In file .vitepress/theme/index.js, the method enhanceApp must be extended:
import { L10n, useL10n } from "@cepharum/vue3-i18n";
enhanceApp( ctx ) {
const l10n = useL10n();
// set up loader for fetching translations per locale
l10n.setLoader( locale => import( ( `./l10n/${locale}.json` ) ) );
// globally register L10n component for use in content pages
ctx.app.component( "L10n", L10n );
}
Translation files per locale are expected to reside in folder .vitepress/theme/l10n here.
In versions prior to v0.5.0, an observer for the document's locale had to be set up explicitly. Starting with v0.5.0, the observer is set up automatically.
Plugins
Starting with v0.4.1, separate loaders can be registered to provide translations for dedicated namespaces. This enables internationalized plugins for a Vue3-based application providing their own subset of translations.
// in a plugin's setup code:
import { L10n, useL10n } from "@cepharum/vue3-i18n";
const l10n = useL10n();
// set up loader for fetching plugin's translations per locale
l10n.setNamespaceLoader( "@my-plugin", locale => import( ( `./l10n/${locale}.json` ) ) );
In this, a namespace is a non-empty string of non-whitespace characters prefixed with @
. Keys of all translations fetched by a namespace's loader are automatically prefixed with that namespace. For the example given above, a translation file could look like this:
{
"FOO": {
"bar": "translated"
}
}
For looking it up, the namespace must be prepended in plugin's code:
const translation = useL10n().lookup( "@my-plugin.FOO.bar" );
The regular loader selected via setLoader()
isn't affected by this automatic prefixing. Thus, it is capable of providing custom translations for either namespace, too. For that, the regular loader is always processed last on compiling a locale's set of translations.
Interpolation
You can use additional markup in your translations to be replaced with values extracted from an additionally provided data object. Translations can look like this:
{
"COMMON": {
"confirm": "Okay",
"cancel": "Cancel"
},
"FEATURE": {
"title": "About {mode.label}",
"text": "This is the feature!"
}
}
<L10n>
component supports another property data
accepting some object with properties matching names wrapped in curly braces above:
<script setup>
import { ref } from "vue";
import { L10n } from "@cepharum/vue3-i18n";
const data = ref({
mode: {
label: "Interpolation",
},
});
</script>
<template>
<h1><L10n i18n="FEATURE.title" :data="data" /></h1>
<p><L10n i18n="FEATURE.text" /></p>
<button><L10n i18n="COMMON.confirm" /></button>
</template>
data
of <L10n>
accepts a function, too, which gets invoked on every update to fetch actual data:
<script setup>
import { L10n } from "@cepharum/vue3-i18n";
const data = () => ({
mode: {
label: "Interpolation",
},
});
</script>
<template>
<h1><L10n i18n="FEATURE.title" :data="data" /></h1>
<p><L10n i18n="FEATURE.text" /></p>
<button><L10n i18n="COMMON.confirm" /></button>
</template>
The component renders the resulting translation without any HTML element unless markdown support has been enabled. By setting optional property tag
to the name of an HTML element, the translation is always wrapped in an element with that name. The following example results in same output as the previous one:
<script setup>
import { L10n } from "@cepharum/vue3-i18n";
const data = () => ({
mode: {
label: "Interpolation",
},
});
</script>
<template>
<L10n i18n="FEATURE.title" :data="data" tag="h1" />
<L10n i18n="FEATURE.text" tag="p"/>
<L10n i18n="COMMON.confirm" tag="button" />
</template>
Markdown
There is support for some basic Markdown in translations.
{
"COMMON": {
"confirm": "Okay",
"cancel": "Cancel"
},
"FEATURE": {
"title": "About",
"text": "This is **the** feature!"
}
}
<L10n>
ignores any Markdown by default. By setting markdown
property, supported Markdown is converted to HTML, though. But any HTML additionally found in translations is escaped.
<script setup>
import { L10n } from "@cepharum/vue3-i18n";
</script>
<template>
<h1><L10n i18n="FEATURE.title" markdown /></h1>
<p><L10n i18n="FEATURE.text" /></p>
<button><L10n i18n="COMMON.confirm" /></button>
</template>
Additional API features
The context fetched with useL10n()
exposes additional properties and actions:
locale
reactively provides current locale as string, e.g.en
orde
. It is derived from browser's locale by default.- The locale of current
document
is preferred. - If locale of
document
is missing or if there is nodocument
, the locale of current browser as exposed innavigator.language
is used. - If locale of browser is missing or there is no browser, a default locale is used as fallback which is
en
initially.
- The locale of current
Method
setLocale( locale )
selects a custom locale to prefer as current locale over any locale read from other sources as described above. It causes that locale's translations to be fetched and all currently used<L10n>
components to be updated.<script setup> import { useL10n } from "@cepharum/vue3-i18n"; useL10n().setLocale( "fr" ); </script>
The method
setLocale()
can be used to drop any previously selected custom locale and to reinstate the discovery of current locale as described above.<script setup> import { useL10n } from "@cepharum/vue3-i18n"; useL10n().setLocale(); </script>
defaultLocale
reactively exposes the fallback locale used in last step described above. The methodsetDefaultLocale()
can be used to adjust it.<script setup> import { useL10n } from "@cepharum/vue3-i18n"; useL10n().setDefaultLocale( "de" ); </script>
In addition to serving as a fallback on discovering a current locale, this default locale is considered on looking up a translation for an i18n key when there is no translation provided for the current locale.
You can fetch reactive translations using method
lookup()
.<script setup> import { useL10n } from "@cepharum/vue3-i18n"; const translation = useL10n().lookup( "SOME.KEY.FOR.firstField" ); </script>
The resulting translation isn't interpolated, but extracted from fetched translations as-is. Provided key may address a whole thread of translations, too.
Moreover, you can fetch multiple reactive translations using method
translate()
with optional interpolation and Markdown support.<script setup> import { ref } from "@vue/reactivity"; import { useL10n } from "@cepharum/vue3-i18n"; const { translate } = useL10n(); const data = ref( { count: 23, foo: "test" } ); // translates and interpolates single string supporting Markdown, too const single = translate( "SOME.KEY.FOR.firstField", data, true ); // translates multiple strings at once and returns object with // same properties each providing related key's translation const multi = translate( { first: "SOME.KEY.FOR.firstField", second: "SOME.KEY.FOR.secondField", }, data ); </script>
By default, translations are looked up for current locale selected as described above. However, a custom locale can be chosen explicitly to look up translations for that particular locale with
lookup()
andtranslate()
.<script setup> import { ref } from "@vue/reactivity"; import { useL10n } from "@cepharum/vue3-i18n"; const { translate, lookup } = useL10n(); const data = ref( { count: 23, foo: "test" } ); // translates single string to French explicitly const translation = lookup( "SOME.KEY.FOR.firstField", null, "fr" ); // translates multiple strings at once to French const multi = translate( { first: "SOME.KEY.FOR.firstField", second: "SOME.KEY.FOR.secondField", }, data, false, "fr" ); </script>
Same works with
locale
property ofL10n
component.Considering a document's language can be disabled. (Prior to v0.5.0, this has been the default and discovery of current local has been working in a simpler way.)
import { useL10n } from "@cepharum/vue3-i18n"; useL10n().unobserveDocument(); </script>
Considering document's language can be re-enabled later:
import { useL10n } from "@cepharum/vue3-i18n"; useL10n().observeDocument(); </script>
Additional documents can be observed, too. However, it is controlling custom locale usually selected with
import { useL10n } from "@cepharum/vue3-i18n"; useL10n().observeDocument( customDocument ); </script>
However, this isn't replacing the monitoring of current document's locale, but selects given document's locale as custom locale just like invoking
setLocale( locale )
method every time that given document's locale is changing. Thus, observing multiple additional documents is possible, but does not make much sense.Stopping observation of an additional document works accordingly:
import { useL10n } from "@cepharum/vue3-i18n"; useL10n().unobserveDocument( customDocument ); </script>
This will reinstate the process of discovering current locale as described in first item above. Any selected custom locale gets dropped.