Vue3 vue-i18n Lazy loading - vue.js

I would like to implement lazy loading for individual languages in my APP, however I don't really understand the example.
Example: https://vue-i18n.intlify.dev/guide/advanced/lazy.html
i18n.js
import { nextTick } from 'vue'
import { createI18n } from 'vue-i18n'
export const SUPPORT_LOCALES = ['de', 'en']
export function setupI18n(options = { locale: 'de' }) {
const i18n = createI18n(options)
setI18nLanguage(i18n, options.locale)
return i18n
}
export function setI18nLanguage(i18n, locale) {
if (i18n.mode === 'legacy') {
i18n.global.locale = locale
} else {
i18n.global.locale.value = locale
}
document.querySelector('html').setAttribute('lang', locale)
}
export async function loadLocaleMessages(i18n, locale) {
// load locale messages with dynamic import
const messages = await import(
/* webpackChunkName: "locale-[request]" */ `./locales /${locale}.json`
)
// set locale and locale message
i18n.global.setLocaleMessage(locale, messages.default)
return nextTick()
}
My folder structure looks quite similar.
I don't use the composition API at this point.
Instead of loading the languages via vue-router I would like to define a default language which can be changed in the user settings.
Where and how do I have to load the function "loadLocaleMessages()" now?
Currently I load the configuration in my main.js like this so that I have "$t" available in the template:
import { setupI18n } from "#/plugins/i18n";
...
app.use(setupI18n());
...
The i18n.js looks like in the example only that my path for the languages is different.
Also I would like to know how I have to include everything so that I have e.g. "$t" also available in other (not components)?
E.g. in the routes.js or in the store (vuex)
EDIT:
middlewares.js - beforeEachHook
import { i18n } from "#/plugins/i18n"
const { t } = i18n.global
/**
* Translate/set page title
*/
export function setPageTitleMiddleware (to, from, next) {
const pageTitle = to.matched.find(item => item.meta.title)
if (to.meta && to.meta.title)
window.document.title = process.env.VUE_APP_DOMAIN_TITLE + ' | ' + t(to.meta.title)
else if
(pageTitle && pageTitle.meta) window.document.title = process.env.VUE_APP_DOMAIN_TITLE + ' | ' + t(pageTitle.meta.title)
next()
}

To be able to get i18n translation from anywhere in your application (vue3 in my case) :
import i18n from "#/i18n";
i18n.global.t("your_key")

Related

CASL - Vue 3 - Element not showing for role

I am having a bit of a challenge implementing CASL in my app.
I have created the following composable useAppAbility ("hook") that defines all the rules:
import { AbilityBuilder, createMongoAbility, subject } from "#casl/ability";
import { useAbility } from "#casl/vue";
const service = {};
const user = {};
const subscription = {};
const invoice = {};
const account = {};
const ability = createMongoAbility();
const ROLES = ["admin", "account_owner", "beneficiary", "super_admin"];
const defineAbilityFor = (role: Object) => {
const { can, rules } = new AbilityBuilder(createMongoAbility);
const is = (r: string) => {
return ROLES.indexOf(r) >= ROLES.indexOf(role);
};
if (is("admin")) {
can("add", subject("User", user));
can("remove", subject("User", user));
}
return ability.update(rules);
};
export { defineAbilityFor, ability, subject };
export const useAppAbility = () => useAbility();
Added the plugin to the main.ts:
import { ability } from "#/composables/useAppAbility";
import { abilitiesPlugin } from "#casl/vue";
createApp(App)
.use(abilitiesPlugin, ability, {
useGlobalProperties: true,
})
//stuff
.mount("#app");
And then, I found that using the beforeEach hook in the router and passing in the user before each route was the simplest way to deal with page load and SPA routing.
I have therefore added the following to my router/index.ts:
import { ability, defineAbilityFor } from "#/composables/useAppAbility";
import useAuth from "#/composables/useAuth";
const {
getUserByClaims,
} = useAuth();
// routes
router.beforeEach(async (to, _from, next) => {
defineAbilityFor(getUserByClaims.value.roles)
})
At this stage I can verify that the user is being passed properly to the defineAbilityFor function and when using the ability.on("update") hook to log the rules object, I have the following output:
Which seems to confirm that the rules for this user are built and updated correctly?
However, when trying to display a button for the said admin in a component, the button does not show.
MyComponent.vue:
<script setup lang="ts">
import { useAppAbility, subject } from "#/composables/useAppAbility";
const { can } = useAppAbility();
</script>
<template>
<div v-if="can('add', subject('User', {}))">TEST FOR CASL</div> <!-- DOES NOT SHOW-->
</template>
Not sure where to go from there, any help would be appreciated.
Thanks

VueI18n dynamic labels from api call

I want to retrieve the labels from api, they are in 2 languages, and switch them when user clicks to change language. Right now one language is loaded as default, but it is not reactive. How to make it react to user changing the language? I was thinking about somehow calling the function before everything else and storing labels in localStorage
The code: (lang.js file)
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import { HTTP } from './../config/http-request.js'
Vue.use(VueI18n)
const locale = 'da'
var langName;
const i18n = new VueI18n({
locale, // language identifier
messages: {}
})
function getLang(lang){
console.log(lang)
if(lang == "da"){
langName = "Danish"
}else{
langName = "English"
}
HTTP.get("1.0/language/label?language_name="+ langName + "&compact=True")
.then((response) => {
var msg = response.data.compact
i18n.setLocaleMessage(lang, msg)
i18n.locale = lang
return lang
})
.catch(function(error) {
console.log(error)
})
}
// This method is called when the manual switch languages, to be loaded when the page is initialized with a default language
// Vue.prototype.$loadLanguageAsync = loadLanguageAsync
getLang(locale)
export default i18n

i18n-js in react native: language not loaded on startup

I have a react native app (ios) with multiple languages. I'm currently facing a weird issue: when I open the app, for a split second, I don't see the translations but I see an error: 'missing translation ...'. This error normally shows up when the translation key is not found (key-value pair) in the json file. Like I said, this error is only there for a split second, after like 500ms, the translations pop up. It seems like the translation file is not loaded properly when the app starts up.
This error is new since the last few days, before that it didn't happen. It also does not happen on the ios simulator, but only when I'm testing on a device. Both the debug scheme and the release scheme have this issue.
My language file looks like this:
// en.json
// { key: value }
{
"hello": "Hello",
"world": "World",
...
}
This is my code in react-native:
import React, { useEffect } from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { _setLanguage } from '../redux/Actions';
import _ from 'lodash';
import i18n from 'i18n-js';
import * as RNLocalize from "react-native-localize";
const translationGetters = {
'en': () => require('./translations/en.json')
};
export default useLanguage = (props) => {
const dispatch = useDispatch();
// get the stored language in the redux store (if set)
const _language = useSelector(state => state.data.config.language);
// translate function to memoize the language file
const translate = _.memoize(
(key, config) => i18n.t(key, config),
(key, config) => (config ? key + JSON.stringify(config) : key)
);
// useEffect function loaded on start up to get the language from redux
// (if set) or from the device
// setLanguage() is called to load the correct language file
useEffect(() => {
if (_.isNull(_language)) {
const fallback = { languageTag: 'en' };
const { languageTag } = RNLocalize.findBestAvailableLanguage(Object.keys(translationGetters)) || fallback;
dispatch(_setLanguage({ language: languageTag }));
setLanguage(languageTag);
} else {
setLanguage(_language);
}
}, []);
// setLanguage()
// sets the language file
const setLanguage = (language) => {
translate.cache.clear();
i18n.translations = { ['en']: translationGetters['en'](), [language]: translationGetters[language]() };
i18n.locale = language;
i18n.defaultLocale = 'en';
i18n.fallbacks = true;
i18n.missingTranslation = () => (null);
dispatch(_setLanguage({ language }));
};
return {
setLanguage,
language: _language,
translate
};
};

how to get access to vuex inside other js files like plugins in nuxt

i have a problem. i wanna get access to one of my getters inside my vee-validate.js file.
how can i do that?
in my pages and components, (in <script>...</script> part, outside export default{...}) i used this function:
component.vue
<script>
let lang;
function getLang({store}) {
lang = store.state.lang
}
export default{
...
}
but it is not working!
i'm trying to access my custom lang file (for translation purpose) that is stored in lang state in my vuex, and use it in vee-validate.js file for custom message.
i tried to import store but not working.
veevalidate.js:
import Vue from 'vue'
import { required } from 'vee-validate/dist/rules'
import { extend, ValidationObserver, ValidationProvider, setInteractionMode } from 'vee-validate'
import {store} from '../store'
let langFile = store
setInteractionMode('eager')
extend('required', {
...required,
message: ''
})
Vue.component('ValidationProvider', ValidationProvider);
Vue.component("ValidationObserver", ValidationObserver);
UPDATED: My store index.js
import langfile from '../static/lang'
export const state = () => ({
lang: null,
dir: null,
})
export const getters = {
//----------------- Language and Direction
lang(state){
return state.lang
},
dir(state){
return state.dir
},
}
export const mutations = {
SET_LANGUAGE(state, lang){
state.lang = lang
},
SET_DIRECTION(state, dir){
state.dir = dir
},
}
export const actions = {
async nuxtServerInit({dispatch, commit}) {
// ------------- Read Language File
let baseLang = process.env.SITE_LANGUAGE;
let siteLang = langfile[baseLang];
let siteDir = langfile[baseLang]['dir'];
commit('SET_LANGUAGE', siteLang);
commit('SET_DIRECTION', siteDir);
},
}

Accessing store from Quasar boot file

I am using Quasar and I have in a boot file (basically same as main.js):
Vue.use(VueCurrencyFilter, {
symbol: '$',
thousandsSeparator: '.',
fractionCount: 2,
fractionSeparator: ',',
symbolPosition: 'front',
symbolSpacing: true
})
I can do in my component:
{{purchaseOrderTotal | currency(currentLocation.currency)}}
currentLocation is a state in my store called with mapState.
currentLocation.currency is the currency for symbol option.
This works but means I have to pass the param in every component.
Is there any way to call currentLocation.currency in the js file to be used as default?
I forgot about anatomy of boot files in Quasar:
import VueCurrencyFilter from 'vue-currency-filter'
export default ({ Vue, store }) => {
Vue.use(VueCurrencyFilter,
.......
};
store needed to be exported instead of imported.
You can import the Vuex store into any module:
import store from '#/store';
And use it in that file the same as though you were using it in a component:
let currency = store.state.currentLocation.currency;
This is the same this.$store object that's accessible through the components.
In Quasar 2 an alternative is to create a new boot file: quasar new boot file
In the created file ../boot/file.(js|ts) we define the variable: mystore (Or also myrouter if we want to use store and router in your functions)
And we initialize them from the "export default":
import { boot } from 'quasar/wrappers'
let myrouter = null;
let mystore = null;
/**
* #param {Array} value
*/
const redirectFromLogin = (value) => {
if (value.length > 1) {
//set routing
myrouter.push("/multiple");
} else {
//set routing
myrouter.push("/single");
}
}
/**
* #param {Array} userdata
*/
const saveUser = (userdata) => {
// Save user info in Store
mystore.commit("userstore/setUser", userdata);
}
// "async" is optional;
// more info on params: https://v2.quasar.dev/quasar-cli/boot-files
export default boot(async ({ app, router, store }) => {
myrouter = router;
mystore = store
})
export { saveUser, redirectFromLogin }