Load i18n for all components Vue3 - vue.js

When using vue-i18n, I need to add this to every single component:
setup() {
const { t, locale } = useI18n();
return { t, locale };
}
Is there any way to preload the t function to all my components?

You can load and switch languages as below.
in i18n.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
function loadLocaleMessages () {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
const messages = {}
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
messages[locale] = locales(key)
}
})
return messages
}
const locale = localStorage.getItem('language') || 'tr'
export default new VueI18n({
locale: locale,
fallbackLocale: locale,
messages: loadLocaleMessages()
})
in main.js
import i18n from './i18n'
new Vue({
el: "#app",
router,
store,
i18n,
render: h => h(App),
})

createI18n() creates a Vue plugin that makes vue-i18n available globally for all components:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createI18n } from 'vue-i18n'
const i18n = createI18n(/*...*/)
createApp(App).use(i18n).mount('#app')
t is then available in the template as $t, and locale is $i18n.locale.
demo

Related

Why Vue doesn't see vuex?

I work with microfrontend single-spa (https://single-spa.js.org/)
I create a store
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
count: 0
},
mutations: {
}
})
in src/main.js i import it
import Vue from 'vue';
import singleSpaVue from 'single-spa-vue';
import 'devextreme/dist/css/dx.light.css';
import App from './App.vue';
import store from './store';
console.log("πŸš€ ~ file: main.js ~ line 6 ~ store", store)
Vue.config.productionTip = false;
Vue.prototype.$bus = new Vue();
const vueLifecycles = singleSpaVue({
store,
Vue,
appOptions: {
render(h) {
return h(App, {
store,
props: {
},
});
},
},
});
export const bootstrap = vueLifecycles.bootstrap;
export const mount = vueLifecycles.mount;
export const unmount = vueLifecycles.unmount;
in console.log store displayed, but in the app cnsole.log(this) does not contain store
I don't understand where I am connecting wrong?
According to the docs, store should be a subproperty of appOptions:
const vueLifecycles = singleSpaVue({
// store, ❌
Vue,
appOptions: {
store, βœ…
render(h) {
return h(App, {
// store, ❌
props: {},
})
},
},
})

How to register translate function in app

I got this translate function, in order to use it I have to import it in every component which is quite annoying.
Looks like
import { useI18n } from "vue-i18n";
export default {
setup() {
const { t } = useI18n();
return { t }
}
}
main.js looks like
import installI18n from "./lang/index";
installI18n(app);
lang/index
const i18n = createI18n({
locale: getLocale(),
legacy: false,
messages,
});
// export default i18n
export default (app) => {
app.use(i18n);
};
Can I register this somehow in my app, so I dont have to put it in setup every time?
You can use globalInjection parameter to enable global injection even in non-legacy (Composition API) mode
const i18n = createI18n({
locale: getLocale(),
legacy: false,
globalInjection: true,
messages,
});
Then you must use functions prefixed with $ in your templates - see Implicit with injected properties and functions
To use app-wide i18n,
locate translate json files in locales folder - locales/en.json
{
"key": "value",
}
create i18n.js file
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
function loadLocaleMessages () {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i) // Path to the locales folder
const messages = {}
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
messages[locale] = locales(key)
}
})
return messages
}
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})
And then use it as param while creating Vue app - in app.js
...
import i18n from './i18n'
...
new Vue({
router,
store,
i18n,
render: h => h(App)
}).$mount('#admin-app')
And then use $t() in Vue files. - index.vue
<h3>
{{ $t('key') }}
</h3>

vue show page after i18n translations loaded from backend

In my application translations can be edited by admin, so I need to preload them from my backend and initialize in the vue apps I have. So I found that vue i18n package has the ability to lazy load messages (link) . But on their example, they already initialize vue with default (en) messages, but in my case, I do not have default messages preloaded. So I want to load messages from the backend at a moment of creating a new instance of the VueI18n.
Here is the i18n.js file:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import {syncGetData} from "#/apiUtil";
import NProgress from 'nprogress'
import './sass/nprogress.scss'
Vue.use(VueI18n)
let loadedLanguages = []
export const i18n = new VueI18n({
locale: navigator.language.split('-')[0] || process.env.VUE_APP_I18N_LOCALE,
fallbackLocale: navigator.language.split('-')[0] || process.env.VUE_APP_I18N_LOCALE,
messages: loadLanguage(navigator.language.split('-')[0] || process.env.VUE_APP_I18N_LOCALE)
})
function setI18nLanguage (lang) {
i18n.locale = lang
document.querySelector('html').setAttribute('lang', lang)
return lang
}
export function loadLanguage(lang) {
// If the language was already loaded
if (loadedLanguages.includes(lang)) {
return Promise.resolve(setI18nLanguage(lang))
}
NProgress.start();
return syncGetData('/api/translations/' + lang)
.then(function(data) {
setI18nLanguage(lang)
loadedLanguages.push(lang)
return data;
})
.catch(function (error) {
console.error(error)
}).finally(function () {
NProgress.done();
});
}
also here is the main.js (simplified):
import Vue from 'vue'
import App from './App.vue'
import {i18n} from './i18n'
new Vue({
i18n,
render: h => h(App)
}).$mount('#app')
The loading of the translations working, but Vue render the template before they loaded, and instead of actual translations I see translation keys. I tried to use v-cloak but it did not help.
So what I want to achieve is, while translations loading users should see only the loading bar (I'm using NProgress, you can see the usage in the i18n.js) and render the app only after translations loaded (maybe not render but initialize). What I'm getting instead: both loading bar and rendered app without actual translations
Thanks in advance!
Updates after Michal LevΓ½ asnwer
Now, i18n.js looks like this:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import { syncGetData} from "#/apiUtil";
import NProgress from 'nprogress'
import './sass/nprogress.scss'
Vue.use(VueI18n)
let loadedLanguages = [];
export function initI18n() {
const lang = navigator.language.split('-')[0] || process.env.VUE_APP_I18N_LOCALE
return syncGetData('/api/translations/' + lang)
.then(function(data) {
console.log(data)
setI18nLanguage(lang)
loadedLanguages.push(lang)
return new VueI18n({
locale: lang,
fallbackLocale: lang,
messages: data
})
})
.catch(function (error) {
console.error(error)
});
}
function setI18nLanguage (lang) {
document.querySelector('html').setAttribute('lang', lang)
return lang
}
export function loadLanguage(lang) {
// If the language was already loaded
if (loadedLanguages.includes(lang)) {
return Promise.resolve(setI18nLanguage(lang))
}
NProgress.start();
return syncGetData('/api/translations/' + lang)
.then(function(data) {
setI18nLanguage(lang)
loadedLanguages.push(lang)
return data;
})
.catch(function (error) {
console.error(error)
}).finally(function () {
NProgress.done();
});
}
and main.js is:
import Vue from 'vue'
import App from './App.vue'
import {initI18n} from './i18n'
import NProgress from "nprogress";
import './sass/nprogress.scss'
NProgress.start();
initI18n().then(function(i18n) {
new Vue({
i18n,
render: h => h(App)
}).$mount('#app')
}).finally(function () {
NProgress.done();
});
I can confirm that now messages are loaded before the Vue initalized, b-z in my App.vue I have a console.log in mounted method:
mounted() {
console.log(this.$i18n.messages)
}
and the output of console log is Object { main_title: "Calculation", tab_you: "You", tab_friend: "Your Friend",...} , before it was empty object.
But still <h2 class="title-form">{{ $t('main_title') }}</h2> renders main_title instead of Calculation
Replace VueI18n innitialization with something like this:
export function initI18n() {
const lang = navigator.language.split('-')[0] || process.env.VUE_APP_I18N_LOCALE
return loadLanguage(lang).then(function(data) {
return new VueI18n({
locale: lang,
fallbackLocale: lang,
messages: data
})
})
}
main.js
import Vue from 'vue'
import App from './App.vue'
import { initI18n } from './i18n'
initI18n().then(function(i18n) {
new Vue({
i18n,
render: h => h(App)
}).$mount('#app')
})
Edit after Q update:
It seems the format of your localization is wrong. VueI18 is able to use multiple languages at once so all your translation keys should be contained in "language object"
Instead of:
{
main_title: "Calculation",
tab_you: "You",
tab_friend: "Your Friend",
...
}
your API should return
{
"en": {
main_title: "Calculation",
tab_you: "You",
tab_friend: "Your Friend",
...
}
}
...where "en" is language identifier
Check your Browser console, I believe VueI18n should warn about missing keys...

Get current locale in a child component

I don't manage to get the locale parameter from vue-i18n in my child component.
I've installed vue-i18n in cli ui. The translation with $t("message") is working but I have error when i try to access to i18n.locale
my enter point (main.js)
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import i18n from './i18n'
new Vue({
router,
i18n,
render: h => h(App)
}).$mount('#app')
my child component
<template>
<div>{{ $t("message") }}</div>
</template>
<script>
import {HTTP} from '#/http-common'
export default
{
name : 'c1',
methods:{
selectMap()
{
console.log(i18n.locale);//=> doesn't work
}
}
</script>
i18n.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
function loadLocaleMessages () {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
const messages = {}
locales.keys().forEach(key => {
const matched = key.match(/([A-Za-z0-9-_]+)\./i)
if (matched && matched.length > 1) {
const locale = matched[1]
messages[locale] = locales(key)
}
})
return messages
}
export default new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})
Try this.$i18n.locale, or just $i18n.locale if inside the <template>.
For Composition API this is my solution:
<script setup>
import { useI18n } from "vue-i18n";
const i18nLocale = useI18n();
console.log(i18nLocale.locale.value); // "en"
</script>

Vue-i18n - Cannot read property 'config' of undefined

First of all I show you what works (in App.js)
import router from './routes.js';
import VueI18n from 'vue-i18n';
const messages = {
en: {
message: {
hello: 'hello world'
}
}
}
// Create VueI18n instance with options
const i18n = new VueI18n({
locale: 'en', // set locale
messages, // set locale messages
})
const app = new Vue({
el: '#app',
router,
i18n
});
But if I want to separate the code in lang.js
import VueI18n from 'vue-i18n';
const messages = {
en: {
message: {
hello: 'hello world'
}
}
}
export default new VueI18n({
locale: 'en', // set locale
messages, // set locale messages
});
So that I can write in App.js
import router from './routes.js';
import i18n from './lang.js';
const app = new Vue({
el: '#app',
router,
i18n
});
But somehow this doesn't work even though routes.js is built exact the same.
My bootstrap.js looks like that, if it is important to know.
import Vue from 'vue';
window.Vue = Vue;
import VueRouter from 'vue-router';
import VueI18n from 'vue-i18n';
Vue.use(VueRouter);
Vue.use(VueI18n);
I'm sorry for the long code, but somehow the mistake lies in import i18n from './lang.js';
I get the message: Uncaught TypeError: Cannot read property 'config' of undefined
In your main file where you create the app instance add the i18n as option instead of Vue.use(Vuei18n) like this:
new Vue({
el: '#app',
i18n, // < --- HERE
store,
router,
template: '<App/>',
components: { App }
})
Put it just after el;
And this should be your lang:
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import en from './en'
import fr from './fr'
import ro from './ro'
Vue.use(VueI18n)
export default new VueI18n({
locale: 'en',
fallbackLocale: 'en',
messages: {
en, fr, ro
}
})
Just to add a detail, you MUST construct the new VueI18n after using Vue.use(VueI18n)
Vue.use(VueI18n);
// must be called after vue.use
const i18n = new VueI18n({
locale: "en",
fallbackLocale: "en",
messages: {
en
}
});
new Vue({
el: "#app",
i18n,
render: (h) => h(App),
});
The idea of code separation can be implemented this way
/* i18n.ts */
import VueI18n from "vue-i18n";
import en from "./locales/en.json";
// Export as an arrow function
export default () =>
new VueI18n({
locale: process.env.VUE_APP_I18N_LOCALE || "en",
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || "en",
messages: { en }
});
/* plugins.ts */
import Vue from "vue";
// Plugins
import VueI18n from "vue-i18n";
import i18n from "./i18n";
Vue.use(VueI18n);
export default {
i18n: i18n()
};
And finally, the main.ts
/* main.ts */
import Vue from "vue";
import plugins from "./core/plugins";
import App from "./App.vue";
new Vue({
...plugins,
render: (h) => h(App)
}).$mount("#app");
The trick is using arrow functions when exporting i18n settings. It's worth mentioning that this problem does not occur with some other Vue plugins.