Skip to content

Instantly share code, notes, and snippets.

@adrai
Created June 5, 2021 22:30
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save adrai/a2587dbc9f9e87d05881f55fa35aaae3 to your computer and use it in GitHub Desktop.
Save adrai/a2587dbc9f9e87d05881f55fa35aaae3 to your computer and use it in GitHub Desktop.
vue-i18next for vue v3
const i18nextPromise = i18next.use(i18nextHttpBackend).init({
debug: true,
lng: 'en',
fallbackLng: 'en',
backend: {
loadPath: './locales/{{lng}}/{{ns}}.json'
}
});
const app = Vue.createApp({});
const i18n = VueI18next.createI18n(i18next);
app.use(i18n);
app.component('app', {
// used in combination with Suspense.
// useful when translations are not in-memory...
async setup() {
console.log('i18next.isInitialized', i18next.isInitialized)
await i18nextPromise;
return {};
},
template: `<div>
<div>
<h3>Translation</h3>
<language-changer></language-changer>
<p>$t: {{ $t("message.hello") }}</p>
</div>
<div>
<h3>Interpolation</h3>
<i18next i18nKey="term" tag="label" for="tos">
<a href="#" target="_blank">{{ $t("tos") }}</a>
</i18next>
</div>
<div>
<h3>Directive</h3>
<with-directive></with-directive>
</div>
</div>`
});
app.component('language-changer', {
template: `
<div>
<a v-if="$i18n.language === 'en'" v-on:click="changeLanguage('de')">
DE
</a>
<strong v-if="$i18n.language === 'de'">
DE
</strong>
&nbsp;|&nbsp;
<a v-if="$i18n.language === 'de'" v-on:click="changeLanguage('en')">
EN
</a>
<strong v-if="$i18n.language === 'en'">
EN
</strong>
</div>`,
methods: {
changeLanguage(lang) {
this.$i18n.changeLanguage(lang);
},
},
});
app.component('with-directive', {
template: `
<div v-t="{key:'message.hello'}"></div>
<div v-t="'message.hello'"></div>`,
});
app.mount("#app");
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>vue-i18next Example</title>
<script src="https://unpkg.com/vue@3.0.11"></script>
<script src="https://unpkg.com/i18next@20.3.1/i18next.js"></script>
<script src="./vueI18next.js"></script>
<script src="https://cdn.jsdelivr.net/gh/i18next/i18next-http-backend/i18nextHttpBackend.js"></script>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<style media="screen">
body {
height: 100%;
}
#app {
max-width: 800px;
margin: 40px;
}
</style>
</head>
<body>
<div id="app">
<Suspense>
<template #default>
<app />
</template>
<template #fallback>
<span>Loading...</span>
</template>
</Suspense>
</div>
<script src="app.js"></script>
</body>
</html>
{
"message": {
"hello": "Hallo!! - DE"
},
"tos": "Nutzungsbedingungen",
"term": "Ich akzeptiere die {{0}}.",
"loadbundle": "Bundle Laden {{lang}}"
}
{
"message": {
"hello": "Hello!! - EN"
},
"tos": "Term of Service",
"term": "I accept the {{0}}.",
"loadbundle": "Load Bundle {{lang}}"
}
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global.VueI18next = factory());
}(this, (function () { 'use strict';
function parseValue(value) {
if (typeof value === 'string') {
return { key: value };
} else if (typeof value === 'object') {
if (!value.key && value.path) {
value.key = value.path;
delete value.path;
}
if (!value.key) {
throw new Error('no key in value');
}
return value;
} else {
throw new Error();
}
}
function getInterpolateArg(
{ slots },
keys
) {
if (keys.length === 1 && keys[0] === 'default') {
// default slot only
return slots.default ? slots.default() : []
} else {
// named slots
return keys.reduce((arg, key) => {
const slot = slots[key]
if (slot) {
arg[key] = slot()
}
return arg
}, {})
}
}
const VueI18next = {
createI18n: (i18next) => ({
install: (app, options = {}) => {
options.bindI18n = options.bindI18n || 'languageChanged loaded';
options.bindStore = options.bindStore || 'added removed';
// add some reactivity...
app.mixin({
created() {
if (options.bindI18n) {
i18next.on(options.bindI18n, () => this.$forceUpdate());
}
if (options.bindStore && i18next.store) {
i18next.store.on(options.bindStore, () => this.$forceUpdate());
}
}
});
// install globalProperties
app.config.globalProperties.$i18n = i18next;
app.config.globalProperties.$t = (...args) => i18next.t.apply(i18next, args);
// install directive
const bind = (el, { instance, value, modifiers }) => {
const parsedValue = parseValue(value);
el.textContent = i18next.t(parsedValue.key, parsedValue);
};
app.directive('t', {
beforeMount: bind,
beforeUpdate: bind
});
// install component
app.component('i18next', {
props: {
tag: {
type: String,
default: 'span'
},
i18nKey: {
type: String,
required: true
},
options: {
type: Object
}
},
setup(props, context) {
return () => {
const { slots, attrs } = context;
const keys = Object.keys(slots).filter(key => key !== '_')
const children = getInterpolateArg(context, keys)
const key = props.i18nKey;
const tag = props.tag;
const REGEXP = i18next.services.interpolator.regexp;
const i18nextOptions = {
...(props.options || {}),
interpolation: { prefix: '#$?', suffix: '?$#' }
};
const format = i18next.t(key, i18nextOptions);
const tchildren = [];
format.split(REGEXP).reduce((memo, match, index) => {
let child;
if (index % 2 === 0) {
if (match.length === 0) return memo;
child = match;
} else {
const place = match.trim();
// eslint-disable-next-line no-restricted-globals
if (isNaN(parseFloat(place)) || !isFinite(place)) {
children.forEach(e => {
if (
!child &&
e.data.attrs &&
e.data.attrs.place &&
e.data.attrs.place === place
) {
child = e;
}
});
} else {
child = children[parseInt(match, 10)];
}
}
memo.push(child);
return memo;
}, tchildren);
return Vue.h(tag, attrs, tchildren);
};
}
})
}
})
};
return VueI18next;
})));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment