How to dynamically change language for vue-google-maps - vue.js

I use #fawmi/vue-google-maps for show google map in my Vue3 app. Also I use vue-i18n for i18n.
My main.js:
const { createApp } = require("vue");
import VueGoogleMaps from '#fawmi/vue-google-maps';
import * as VueI18n from 'vue-i18n';
import messages from './assets/messages';
import App from './App.vue';
const i18n = VueI18n.createI18n({
locale: 'ar',
fallbackLocale: 'en',
messages,
});
createApp(App)
.use(i18n)
.use(VueGoogleMaps, {
load: {
key: 'myprivatekey',
language: 'ar',
}
})
.mount("#app");
If I want to set language for map by default, it is no problem.
In other files I can set locale dynamically like this
this.$i18n.locale = 'es'
But how I can make it for map component?
Code like below is not working for me (obliviously, because $i18n is not defined here)
.use(i18n)
.use(VueGoogleMaps, {
load: {
key: 'myprivatekey',
language: this.$i18n.locale,
}
})

Related

Easier way to use plugins with the composition api in vue 3

When adding vuex or vue-router as plugin in vue and using the options api you could access these plugins with the this keyword.
main.js
import { createApp } from 'vue';
import i18n from '#/i18n';
import router from './router';
import store from './store';
app.use(i18n);
app.use(store);
app.use(router);
RandomComponent.vue
<script>
export default {
mounted() {
this.$store.dispatch('roles/fetchPermissions');
},
}
</script>
The this keyword is no longer available with the composition api which leads to a lot of repetitive code. To use the store, vue-router or the i18n library I have to import and define the following:
RandomComponent.vue with composition api
<script setup>
import { useStore } from 'vuex';
import { useRouter } from 'vue-router';
import { useI18n } from 'vue-i18n';
const router = useRouter();
const store = useStore();
const { t } = useI18n();
const { handleSubmit, isSubmitting, errors } = useForm('/roles/create', role, CreateRoleValidationSchema, () => {
store.dispatch('notifications/addNotification', {
type: 'success',
title: t('create_success', { field: t('role', 1) }),
});
router.push({ name: 'roles' });
});
</script>
Is there a way to avoid these imports and definitions and have a way to easily use these plugins like I could do with the options api?
There is no built-in way of doing that in Composition API and script setup.
What you could do is:
Create a plugins.js file that exports common bindings you want to import. For example:
export * from 'vuex'
export * from 'vue-router'
export * from 'vue-i18n'
And then you have only 1 import to do:
<script setup>
import { useStore, useRouter, useI18n } from './plugins'
const router = useRouter();
const store = useStore();
const { t } = useI18n();
const { handleSubmit, isSubmitting, errors } = useForm('/roles/create', role, CreateRoleValidationSchema, () => {
store.dispatch('notifications/addNotification', {
type: 'success',
title: t('create_success', { field: t('role', 1) }),
});
router.push({ name: 'roles' });
});
</script>
You can go even further by initiating the plugins like:
import { useStore, useRouter, useI18n } from './plugins'
export function initiateCommonPlugins() {
const router = useRouter();
const store = useStore();
const { t } = useI18n();
return { router, store, t }
}
And your code then will look like this:
<script setup>
import { router, store, t } from './initiate-plugins'
const { handleSubmit, isSubmitting, errors } = useForm('/roles/create', role, CreateRoleValidationSchema, () => {
store.dispatch('notifications/addNotification', {
type: 'success',
title: t('create_success', { field: t('role', 1) }),
});
router.push({ name: 'roles' });
});
</script>
Use unplugin-auto-import plugin
This plugin can eliminate all imports you want and is highly customizable. Haven't tried it yet but I have seen people recommend it.
Stick to Options API
Using Vue 3 doesn't mean that you have to use Composition API for creating components. You can use Options API along with script setup for composables instead of Mixins.
So Options API for components, Composition API for reusing code or advanced use-cases.

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-i18n not use vuetify components strings

i try to use vue-i18n to translate my application. I use also vuetify and the vue cli.
At the moment i have the languages englisch and german.
Here is my project structure and code.
main.js
import Vue from "vue";
import i18n from "./plugins/i18n";
import vuetify from "./plugins/vuetify";
Vue.config.productionTip = false;
new Vue({
vuetify,
i18n,
render: (h) => h(App),
}).$mount("#app");
plugins/i18n.js
import Vue from "vue";
import VueI18n from "vue-i18n";
import de from "#/locale/de";
import en from "#/locale/en";
Vue.use(VueI18n);
const messages = {
de: de,
en: en,
};
const i18n = new VueI18n({
locale: "de",
fallbackLocale: "en",
messages,
});
export default i18n;
locale/en.js
export default {
hello: "hello",
};
locale/de.js
export default {
hello: "Hallo",
$vuetify: {
dataIterator: {
rowsPerPageText: "Einträge pro Seite:",
pageText: "{0}-{1} von {2}",
},
}
};
plugins/vuetify.js
import Vue from "vue";
import Vuetify from "vuetify/lib/framework";
import i18n from "./i18n";
Vue.use(Vuetify);
export default new Vuetify({
lang: {
t: (key, ...params) => i18n.t(key, params),
},
});
All works fine with the hello translation, but the vuetify components not working as expected.
I would like to add a translation to german for few vuetify components in the future.
But at the moment a would like to use the original names from vuetify. And that is not working.
For example, the v-select component looks like:
And other components also not working.
What i do wrong?
You are missing default vuetify component locales. you should provide it by rewriting them in your locales or import it at the beginning of each locale file.
locale/en.js
import { en } from 'vuetify/lib/locale'
export default {
$vuetify: { ...en },
hello: "hello",
};
locale/de.js
import { de } from 'vuetify/lib/locale'
export default {
hello: "Hallo",
$vuetify: {
...de,
dataIterator: {
rowsPerPageText: "Einträge pro Seite:",
pageText: "{0}-{1} von {2}",
},
}
};

How to create a Quasar Framework boot file for vueI18n#next with the Vue Composition API?

We;re trying to install the new vueI18n#next package with Quasar Framework for use with the Vue 2 and the Vue Composition API. The Vue I18n docs mention this:
import { createApp } from 'vue'
import { createI18n, useI18n } from 'vue-i18n'
// call with I18n option
const i18n = createI18n({
legacy: false,
locale: 'ja',
messages: { en: { ... } }
})
const App = {
setup() {
// ...
const { t } = useI18n({ ... })
return { ... , t }
}
}
const app = createApp(App)
app.use(i18n)
app.mount('#app')
When we're trying to translate that to a Quasar Framework boot file we get an error on the app.setup part:
import { boot } from 'quasar/wrappers'
import messages from 'src/i18n'
import Vue from 'vue'
import { createI18n, useI18n, VueI18n } from 'vue-i18n'
declare module 'vue/types/vue' {
interface Vue {
i18n: VueI18n
}
}
const i18n = createI18n({
legacy: false,
locale: 'en-us',
fallbackLocale: 'en-us',
messages,
})
Vue.use(i18n)
export default boot(({ app }) => {
// Set i18n instance on app
app.setup = () => {
const { t } = useI18n()
return { t }
}
})
Error:
What is the correct way to install this?
Quasar is still on Vue 2, not Vue 3. So we're now using the vue-i18n-composable package instead with this boot file:
import { boot } from 'quasar/wrappers'
import { createI18n } from 'vue-i18n-composable'
import messages from 'src/i18n'
import VueI18n from 'vue-i18n'
declare module 'vue/types/vue' {
interface Vue {
i18n: VueI18n
}
}
const i18n = createI18n({
locale: 'en-us',
fallbackLocale: 'en-us',
messages,
})
export default boot(({ app }) => {
app.i18n = i18n
})

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...