Vue i18n translation for single file components - javascript

I'm using laravel and currently trying to do multilanguage pages,
So i've found this pretty neat plugin called VueI18N for translations and got it working (somehow) by installing it via npm and then putting the following code in my app.js
//app.js
window.Vue = require('vue');
import VueI18n from 'vue-i18n'
Vue.use(VueI18n)
//tons of more components here
Vue.component('vue-test', require('./components/VueTestFileForLocalization.vue').default);
const messages = {
en: {
message: {
hello: 'Hello, {name}!'
}
},
de: {
message: {
hello: 'Guten Tag, {name}!'
}
}
};
const i18n = new VueI18n({
locale: 'de',
messages
});
const app = new Vue({
el: '#vue-app',
i18n
});
Then in my vue-test i tried outputting this successfully:
<template>
<div>{{ $t('message.hello', { name: 'John' }) }}</div>
</template>
<script>
export default {
data() {
return {};
},
created() {
this.$i18n.locale = 'en';
}
};
</script>
and by changing the locale i can also display the other language. Great.
Now I think with so many components, I might have a problem defining all the localization inside app.js , and its not beautiful either. So I tried looking up This link here to the docs for single file components but unsuccessfully, unfortunately.
I copy-pasted the code, (vue-i18n-loader should also be installed by laravel by default) and modified the webpack file. The error I get seems pretty common after research but I cannot seem to fix it.
Value of key 'hello' is not a string!
Cannot translate the value of keypath 'hello'. Use the value of keypath as default
It does simply output whatever the key is i specify in message.
Does any of you out there have an idea, what I might have done wrong or forgot to setup?
Any hints would be appreciated very very much.
Thank you for your time
Best regards,
Desory

While not a direct answer to your question I recently found another approach to the same problem that is less effort when it comes to maintaining translations.
I put all my translations in JSON files so I can share the same translations between Laravel backend and Vue front end.
I did this based on this:
https://www.codeandweb.com/babeledit/tutorials/how-to-translate-your-vue-app-with-vue-i18n
So as per: https://laravel.com/docs/7.x/localization#using-translation-strings-as-keys
Create resources/lang/en.json etc. with contents:
{
"my_message": "This is my message in english",
...
}
I'd create resources/js/i18n.js containing:
import Vue from "vue";
import VueI18n from "vue-i18n";
Vue.use(VueI18n);
function loadLocaleMessages() {
const locales = require.context(
"../lang",
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()
});
and in app.js import that as follows:
//Localise
import i18n from "./i18n";
Vue.use(i18n);
/**
* Next, we will create a fresh Vue application instance and attach it to
* the page. Then, you may begin adding components to this application
* or customize the JavaScript scaffolding to fit your unique needs.
*/
const app = new Vue({
i18n,
el: "#app"
});
You can then use the same translations in your blade templates with the __ helper and in Vue with $t(...)

Try the changes below for app.js and your code should work fine:
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import App from './components/VueTestFileForLocalization.vue';
Vue.use(VueI18n);
const messages = {
en: {
message: {
hello: 'Hello, {name}!'
}
},
de: {
message: {
hello: 'Guten Tag, {name}!'
}
}
};
const i18n = new VueI18n({
locale: 'de',
messages
});
new Vue({
i18n,
render: h => h(App)
}).$mount('#vue-app');

I had the same problem, i solved it by restarting the server.
Run npm run serve again.
Hope it helps someone in the future..

Related

Using vue3-runtime-template, I get an error, "Property "xxx" was accessed during render but is not defined on instance"

I am using vue3-runtime-template to render a vue code converted from xml. The code is the following:
<template>
<v-container>
<v-runtime-template :template="text"></v-runtime-template>
</v-container>
</template>
<script>
import xmlStr from '../assets/sample.xml'
import xslStr from '../assets/sample.xsl'
import VRuntimeTemplate from "vue3-runtime-template"
export default {
name: 'Edition',
components: {
VRuntimeTemplate,
},
data: () => ({
text: `<div>{{ getConvertedText }}</div>`,
}),
computed: {
getConvertedText() {
var xml = new DOMParser().parseFromString(xmlStr, 'text/xml')
var xsl = new DOMParser().parseFromString(xslStr, 'text/xml')
var xsltProcessor = new XSLTProcessor()
xsltProcessor.importStylesheet(xsl)
return xsltProcessor.transformToDocument(xml).documentElement.outerHTML
},
},
}
</script>
When I run this code, I get an error, Property "getConvertedText" was accessed during render but is not defined on instance.
It does not work though I am pretty sure that I followed the documentation. I currently run on vue-cli, and I don't get any compile errors on the terminal, which should mean that the plugin itself is properly installed. It would be great if anyone knows what's wrong.
Instead of using vue3-runtime-template, I managed to embed the converted code to the parent template by returning an entire component.
I chose to copy to a global variable that is registered as a Vue component, rather than using a return value of the method.
<template>
<v-container>
<convertedTemplate/>
</v-container>
</template>
var convertedTemplete = {
template: '<div></div>'
}
<script>
import xmlStr from '../assets/sample.xml'
import xslStr from '../assets/sample.xsl'
export default {
name: 'Edition',
components: {
convertedTemplete,
},
computed: {
getConvertedText() {
var xml = new DOMParser().parseFromString(xmlStr, 'text/xml')
var xsl = new DOMParser().parseFromString(xslStr, 'text/xml')
var xsltProcessor = new XSLTProcessor()
xsltProcessor.importStylesheet(xsl)
convertedTemplate = {
template: "<div>xsltProcessor.transformToDocument(xml)
.documentElement.outerHTML</div>",
}
},
},
}
</script>
But this code cannot render the converted child component at the same timing as the parent component is loaded. It is necessary to trigger the method after the parent is rendered. Actually, I have not checked if the code above really works since I am recently working on my project with a CDN version of Vue.js, not a Vue-Cli one. With the CDN version, I have to make the getConvertedText() method async since I need to use fetch() methods to import files, which makes the coding strategy so different. But, I guess, in a Vue-Cli version, without the async method, if we move the getConvertedText() method to created: it might render as soon as the parent component is called.

How to correctly globally register and use Vue Rangedate Picker component?

I am trying to use VueRangedatePicker and I can't seem to figure out how to use this on the template of some other vue component. I am using Webpack.
I have registered the component/plugin on my main.js file like this:
import Vue from 'vue'
import App from './App'
import router from './router'
import { store } from './store/store'
import firebase from './firebase-config'
import vuefire from 'vuefire'
//////////////// HERE
import VueRangedatePicker from 'vue-rangedate-picker' // importing the plugin here
Vue.use(VueRangedatePicker) // using it
Vue.component('VueRangedatePicker', { }) // creating the component globally (if I don't add this line the app complains the component is not registered
////////////////
Vue.config.productionTip = false
let app;
Vue.use(vuefire)
firebase.auth().onAuthStateChanged(function(user){
if (!app) {
/* eslint-disable no-new */
app = new Vue({
el: '#app',
template: '<App/>',
components: { App, VueRangedatePicker },
router,
store,
VueRangedatePicker
})
}
})
Then on my component component_A.vue I am again importing the VueRangedatePicker plugin in the following manner:
<template>
<div>
<vue-rangedate-picker #selected="onDateSelected" i18n="EN" />
</div>
</template>
<script>
import firebase,{ itemRef } from '../firebase-config';
import VueRangedatePicker from 'vue-rangedate-picker'
export default {
firebase() {
return {
items: itemsRef,
}
},
name: 'component_A',
data () {
return {
}
},
created() {
console.log(VueRangedatePicker);
},
methods: {
onDateSelected: function (daterange) {
this.selectedDate = daterange
},
}
</script>
I know the plugin/component is registered because when I log the Vue Rangedate Picker on the console I can see the object
However I am getting the an error message like this
I have read the complete readme.md file on the project's github but I am still puzzled. What is Vue_Daterange_picker? Is it a plugin? Is it a component? Is it a plugin that allows me to build a component? I am quite confused. Can you clarify this for me a little better? How can I make this work?
This is because you have registered the component with an empty name.
In main.js :
Vue.component('DatePicker', VueRangedatePicker)
Then in your component use the component as :
<date-picker></date-picker>

With Vue-cli, where do I declare my global variables?

In most Vue.js tutorials, I see stuff like
new Vue({
store, // inject store to all children
el: '#app',
render: h => h(App)
})
But I'm using vue-cli (I'm actually using quasar) and it declares the Vue instance for me, so I don't know where I'm supposed to say that I want store to be a "Vue-wide" global variable. Where do I specify that? Thanks
Yea, you can set those variables like this, in your entrypoint file (main.js):
Vue.store= Vue.prototype.store = 'THIS IS STORE VARIABLE';
and later access it in your vue instance like this:
<script>
export default {
name: 'HelloWorld',
methods: {
yourMethod() {
this.store // can be accessible here.
}
}
}
</script>
You can also see this in the vue-docs here.
Edit 1:
from the discussions in the comment sections about "no entrypoint file" in quasar's template.
what you can do is, to go to src/router/index.js, and there you will be able to get access to Vue, through which you can set a global variable like this:
...
import routes from './routes'
Vue.prototype.a = '123';
Vue.use(VueRouter)
...
and then if you console.log it in App.vue, something like this:
<script>
export default {
name: 'App',
mounted() {
console.log(this.a);
}
}
</script>
now, look at your console:
You can also do the same in App.vue file in the script tag.
You don't need to make the store a global variable like that, as every component (this.$store) and the Vue instance itself have access to the store after the initial declaration.
Take a look at the Quasar docs for App Vuex Store.
store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
updateCount(state) {
state.count += 1
}
}
})
main.js
import App from './App.vue'
import store from '/path/to/store.js'
new Vue({
el: '#app',
store,
render: h => h(App)
})
If you need to access the store from within a component you can either import it (as we did in main.js) and use it directly [note that this is a bad practice] or access using this.$store. You can read a bit more about that here.
In any case here's the official Getting Started guide from Vuex team
We could add the Instance Properties
Like this, we can define instance properties.
Vue.prototype.$appName = 'My App'
Now $appName is available on all Vue instances, even before creation.
If we run:
new Vue({
beforeCreate: function() {
console.log(this.$appName)
}
})
Then "My App" will be logged to the console!
Slightly redundant to the aforementioned answer, but I found this to be simpler per the current Vuex state documentation at the time of this reply.
index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default function (/* { ssrContext } */) {
const Store = new Vuex.Store({
modules: {
// example
},
state: {
cdn_url: 'https://assets.yourdomain.com/'
},
// for dev mode only
strict: process.env.DEV
})
return Store
}
...and then in your component, e.g. YourPage.vuex
export default {
name: 'YourPage',
loadImages: function () {
img.src = this.$store.state.cdn_url + `yourimage.jpg`
}
}
Joining the show a bit late, but the route I personally use in Quasar is to create a Boot file for my global constants and variables.
I create the Boot file (I call it global-constants.js but feel free to call it whatever).
/src/boot/global-constants.js
import Vue from 'vue'
Vue.prototype.globalConstants = {
baseUrl: {
website: 'https://my.fancy.website.example.com',
api: 'https://my.fancy.website.example.com/API/v1'
}
}
if (process.env.DEV) {
Vue.prototype.globalConstants.baseUrl.website = 'http://localhost'
Vue.prototype.globalConstants.baseUrl.api = 'http://localhost/API/v1'
}
if (process.env.DEV) {
console.log('Global Constants:')
console.log(Vue.prototype.globalConstants)
}
Then add a line in quasar.conf.js file to get your Boot file to kick:
/quasar.conf.js
module.exports = function (ctx) {
return {
boot: [
'i18n',
'axios',
'notify-defaults',
'global-constants' // Global Constants and Variables
],
Then to use it:
from Vuex
this._vm.globalConstants.baseUrl.api
for example: axios.post(this._vm.globalConstants.baseUrl.api + '/UpdateUserPreferences/', payload)
from Vue HTML page
{{ globalConstants.baseUrl.api }}
from Vue code (JavaScript part of Vue page
this.globalConstants.baseUrl.api
An alternative Vue3 way to this answer:
// Vue3
const app = Vue.createApp({})
app.config.globalProperties.$appName = 'My App'
app.component('child-component', {
mounted() {
console.log(this.$appName) // 'My App'
}
})

react-i18next, add resources from props

I have repo with components and repo with main app. I implemented i18next in repo with components and it works fine when I have an i18n config file in this repo (or when I pass it by props from app repo). But I have a problem when I'm trying to send only "resource" part from main app and replace it in config file in components. I tried to clone i18n instance and set resources but, it's not work.
It's my config file:
i18n.js
import i18n from 'i18next';
import LngDetector from 'i18next-browser-languagedetector';
import { reactI18nextModule } from 'react-i18next';
i18n
.use(LngDetector)
.use(reactI18nextModule)
.init({
detection: {
order: ['cookie', 'localStorage'],
lookupLocalStorage: 'i18n_lang',
lookupCookie: 'i18n_lang',
caches: ['localStorage'],
},
load: 'current',
fallbackLng: 'en',
ns: ['components'],
defaultNS: 'components',
keySeparator: false,
interpolation: {
escapeValue: false,
formatSeparator: ',',
},
react: {
wait: true,
},
});
export default i18n;
resources.js file (I tried with resources key at beginning but it's still doesn't work):
import * as en from './en.json';
import * as de from './de.json';
export default {
en: {
components: en,
},
de: {
components: de,
},
};
Now I tried something like this:
import * as langs from './resources';
const newI18 = i18n.cloneInstance({ resources: langs });
const i18ProviderDecorator = (storyFn) => (
<I18nextProvider i18n={newI18}>
{ storyFn() }
</I18nextProvider>
When I pass i18n.js by props with resources, it works perfect, but I want to remove i18next from main app and leave it only in the components.
Greetings
a i18next cloned instance uses the same store as the original instance -> and does not init that again -> so passing in resources that way does not work: https://github.com/i18next/i18next/blob/master/src/i18next.js#L308
make a new instance i18n.createInstance or pass resources to clone using i18n.addResourceBundle: https://www.i18next.com/api.html#addresourcebundle

accessing vuex store in js file

Just like in main.js, I'm trying to access my store from a helper function file:
import store from '../store'
let auth = store.getters.config.urls.auth
But it logs an error:
Uncaught TypeError: Cannot read property 'getters' of undefined.
I have tried
this.$store.getters.config.urls.auth
Same result.
store:
//Vuex
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
config: 'config',
},
getters: {
config: state => state.config
},
});
export default store
How do I make my store available outside of components?
The following worked for me:
import store from '../store'
store.getters.config
// => 'config'
This Worked For Me In 2021
I tried a bunch of different things and it seems, at least in Vue 3, that this works. Here is an example store:
export default {
user: {
bearerToken: 'initial',
},
};
Here is my Getters file:
export default {
token: (state) => () => state.user.bearerToken,
};
Inside your .js file add the page to your store\index.js file.
import store from '../store';
In order to access the getters just remember it is a function (which may seem different when you use mapGetters.)
console.log('Checking the getters:', store.getters.token());
The state is more direct:
console.log('Checking the state:', store.state.user.bearerToken);
If you are using namespaced modules, you might encounter the same difficulties I had while trying to retrieve items from the store;
what might work out for you is to specify the namespace while calling the getters (example bellow)
import store from '../your-path-to-your-store-file/store.js'
console.log(store.getters.['module/module_getter']);
// for instance
console.log(store.getters.['auth/data']);
put brackets on your import and it should work
import { store } from '../store'
using this approach has worked for me:
// app.js
import store from "./store/index"
const app = new Vue({
el: '#app',
store, //vuex
});
window.App = app;
// inside your helper method
window.App.$store.commit("commitName" , value);
if you are using nuxt you can use this approach
window.$nuxt.$store.getters.myVar
if you have multiple modules
window.$nuxt.$store.getters['myModule/myVar']
export default ( { store } ) => {
store.getters...
}

Categories

Resources