vue-i18n: language dependent view - vue.js

I use vue-i18n in my application.
Now I would like to add an "About" view with a lot text and links.
I think it would be better maintainable to have several language dependent views than adding several {{ $t(...)}} in one view about.vue.
I thought about something like adding language ISO code to the view name:
.../about.en.vue
.../about.de.vue
.../about.es.vue
What would be the best way to combine and integrate with vue-i18n? Probably there is a different way?

You can use a dynamic component to achieve this:
<template>
<component :is="localizedAbout" />
</template>
<script>
import AboutEn from '../about.en.vue';
import AboutEs from '../about.es.vue';
import AboutDe from '../about.de.vue';
export default {
components: {
AboutEn,
AboutEs,
AboutDe,
},
computed: {
localizedAbout() {
switch (this.$i18n.locale) {
case 'en':
return AboutEn;
case 'es':
return AboutEs;
case 'de':
return AboutDe;
default:
return '';
}
},
},
}
</script>

After doing some other stuff, I was today able to solve this issue by using dynamic imports:
<template>
<b-container class="about">
<component :is="langComponent" />
</b-container>
</template>
<script>
export default {
name: 'AboutView',
data () {
return {
langComponent: null
}
},
mounted () {
this.$watch(
'$i18n.locale',
(newLocale, oldLocale) => {
if (newLocale === oldLocale) {
return
}
this.setLangAbout()
},
{
immediate: true
}
)
},
methods: {
setLangAbout () {
try {
import('#/components/about/About.' + this.$i18n.locale + '.vue').then(module => {
this.langComponent = module.default
})
} catch (err) {
console.log(err)
import('#/components/about/About.en.vue').then(module => {
this.langComponent = module.default
})
}
}
}
}
</script>
Thanks #Pochwar for your initial answer. Based on this I have done some more researched.
Following links helped me to solve this problem:
How does Dynamic Import in webpack works when used with an expression?
Comment at Error: Cannot find module with dynamic import

Related

How to dynamically switch a component based on the existence of window in Nuxt.js?

I have a dynamic component that looks different at different screen resolutions.
<template>
<div>
<headerComponent></headerComponent>
<div v-if="!large" class="placeholder"></div>
<component
v-else
:is="tariffBlock"
>
</component>
</div>
</template>
<script>
import smallComponent from '#/components/small-component'
import largeComponent from '#/components/large-component'
import headerComponent from '#/components/header-component'
const components = {
smallComponent,
largeComponent
}
export default {
components: {
headerComponent
},
data () {
return {
large: false
}
},
computed: {
getComponent () {
if (!this.large) return components.smallComponent
return components.largeComponent
}
},
created () {
if (process.browser) {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
}
}
</script>
By default, a smallComponent is shown, and then a largeComponent. To avoid "jumping" I decided to show the placeholder while large === false.
To avoid the error window in not defined I use the check for process.browser.
PROBLEM: placeholder is only shown in dev mode, but when I start generate the placeholder is not displayed.
The following solutions DIDN'T help:
1.
created () {
this.$nextTick(() => {
if (process.browser) {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
})
}
created () {
this.$nextTick(() => {
this.large = window.matchMedia('(min-width: 1200px)').matches
})
}
mounted () {
this.large = window.matchMedia('(min-width: 1200px)').matches
}
and with the addition process.browser and nextTick()
Creating a mixin with ssr: false, mode: client
Thanks in advance!
This is how you toggle between components in Nuxt.js
<template>
<div>
<div #click="toggleComponents">toggle components</div>
<hr />
<first-component></first-component>
<second-component></second-component>
<hr />
<component :is="firstOrSecond"></component>
</div>
</template>
<script>
export default {
data() {
return {
firstOrSecond: 'first-component',
}
},
methods: {
toggleComponents() {
if (this.firstOrSecond === 'first-component') {
this.firstOrSecond = 'second-component'
} else {
this.firstOrSecond = 'first-component'
}
},
},
}
</script>
You don't need to import them, it's done automatically if you have the right configuration, as explained here: https://nuxtjs.org/blog/improve-your-developer-experience-with-nuxt-components
In this snippet of code, first-component and second-component are shown initially (between the two hr) just to be sure that you have them properly loaded already. You can of course remove them afterwards.
Not recommended
This is what you're looking for. Again, this is probably not how you should handle some visual changes. Prefer CSS for this use-case.
<template>
<div>
<component :is="firstOrSecond"></component>
</div>
</template>
<script>
export default {
data() {
return {
firstOrSecond: 'first-component',
}
},
mounted() {
window.addEventListener('resize', this.toggleComponentDependingOfWindowWidth)
},
beforeDestroy() {
// important, otherwise you'll have the eventListener all over your SPA
window.removeEventListener('resize', this.toggleComponentDependingOfWindowWidth)
},
methods: {
toggleComponentDependingOfWindowWidth() {
console.log('current size of the window', window.innerWidth)
if (window.innerWidth > 1200) {
this.firstOrSecond = 'second-component'
} else {
this.firstOrSecond = 'first-component'
}
},
},
}
</script>
PS: if you really wish to use this solution, at least use a throttle because the window event will trigger a lot and it can cause your UI to be super sluggish pretty quickly.

Nuxt JS load components depending on API response

I'm building a nuxt app to consume the wp rest API. In my fetch method I fetch information about needed components. I can't figure out how to then import all the components and render them. I've tried several methods, but I can't see to make it work.
Here's what works:
<component :is="test" :config="componentList[0]"></component><br>
export default {
async fetch({ store, $axios }) {
await store.dispatch("getPageBySlug", "home");
},
computed: {
test() {
return () => import('~/components/HeroIntro');
}
}
};
Ok so this is easy, nothing special - I could now import the component based on the slug etc. But I need to render multitple components and therefor im doing this:
<component
v-for="component in componentList"
:key="component.acf_fc_layout"
:is="component.acf_fc_layout"
:config="component">
</component>
along with this
export default {
async fetch({ store, $axios }) {
await store.dispatch("getPageBySlug", "home");
},
computed: {
page() {
return this.$store.getters.getPageBySlug("home");
},
componentList() {
return this.page.acf.flexible_content;
},
componentsToImport() {
for(const component of this.componentList) {
() => import('~/components' + component.acf_fc_layout);
}
}
}
};
All I'm getting is
Unknown custom element: HeroIntro - did you register the
component correctly? For recursive components, make sure to provide
the "name" option
How do I archieve what im trying?
edit:
So, after a lot of trying, I could only make it work with using an extra component, "DynamicComponent":
<template>
<component :is="componentFile" :config="config"></component>
</template>
<script>
export default{
name: 'DynamicComponent',
props: {
componentName: String,
config: Object
},
computed: {
componentFile() {
return () => import(`~/components/${this.componentName}.vue`);
}
}
}
</script>
Now in Index.vue
<template>
<main class="container-fluid">
<DynamicComponent
v-for="(component, index) in componentList"
:key="index"
:componentName="component.name"
:config="component"
/>
</main>
</template>
<script>
export default {
components: {
DynamicComponent: () => import("~/components/base/DynamicComponent")
}
I am not sure yet if this is optimal - but for now it works great - any input / opinions would be great!

How to fix 'Cannot find module' in a vuejs module with npm link

I've created a vuejs library with some components that could be used in many project.
In that library, I've got a component which can load svg files to be used inline in html (svg-icon).
It work great.
But in this same library, I've got another component which use svg-icon with a svg image stored in the library.
An import point, I'd like to use this library (node module) with an npm link
Which is the good way to give the path of the svg image, or where to store it?
I've tried a lot of different paths, but none of them is working...
This is my svg-icon component:
<template>
<component :is="component"></component>
</template>
<script>
export default {
name: 'SvgIcon',
props: {
icon: {
type: String,
required: true
}
},
data () {
return {
component: null
}
},
watch: {
icon: () => {
this.load()
}
},
computed: {
loader () {
return () => import(this.icon)
}
},
methods: {
load () {
this.loader().then(() => {
this.component = () => this.loader()
})
}
},
mounted () {
this.load()
}
}
</script>
And this is the component which use svg-icon (the svg image is in the same folder actually) :
<template>
<svg-icon icon="~my-module/components/media/no-image.svg"></svg-icon>
<svg-icon icon="./no-image.svg"></svg-icon>
</template>
<script>
import SvgIcon from '../svg-icon/SvgIcon'
export default {
components: {
SvgIcon
}
}
</script>
I always got this errors:
Cannot find module '~my-module/components/media/no-image.svg'
Cannot find module './no-image.svg'
Which is the good path in that situation? Or should I put the svg file somewhere else? (I'd like to keep it in the library)
I've created a CodeSandbox here
SvgIcon.vue
<template>
<span v-html="icon"></span>
</template>
<script>
export default {
name: "SvgIcon",
props: {
icon: {
type: String,
required: true
}
}
};
</script>
HelloWorld.vue
//Usage
<template>
<svg-icon :icon="AlertIcon"></svg-icon>
</template>
<script>
import AlertIcon from "../assets/alert.svg";
import SvgIcon from "./SvgIcon";
export default {
data() {
return { AlertIcon };
},
components: {
SvgIcon
}
};
</script>
I've made some changes to your components.
You need to import the image and pass it to your component because dynamic import causes complications when it comes to the absolute paths.
I've removed some unnecessary fields from your SvgIcon code.
Hope this helps.

Vue component computed not reacting

I have 2 components OperatorsList and OperatorButton.
The OperatorsList contains of course my buttons and I simply want, when I click one button, to update some data :
I emit select with the operator.id
This event is captured by OperatorList component, who calls setSelectedOperator in the store
First problem here, in Vue tools, I can see the store updated in real time on Vuex tab, but on the Components tab, the operator computed object is not updated until I click antoher node in the tree : I don't know if it's a display issue in Vue tools or a real data update issue.
However, when it's done, I have another computed property on Vue root element called selectedOperator that should return... the selected operator : its value stays always null, I can't figure out why.
Finally, on the button, I have a v-bind:class that should update when the operator.selected property is true : it never does, even though I can see the property set to true.
I just start using Vue, I'm pretty sure I do something wrong, but what ?
I got the same problems before I used Vuex, using props.
Here is my OperatorList code :
<template>
<div>
<div class="conthdr">Operator</div>
<div>
<operator-button v-for="operator in operators" :op="operator.id"
:key="operator.id" #select="selectOp"></operator-button>
</div>
</div>
</template>
<script>
import OperatorButton from './OperatorButton';
export default {
name: 'operators-list',
components : {
'operator-button': OperatorButton
},
computed : {
operators() { return this.$store.getters.operators },
selected() {
this.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
},
},
methods : {
selectOp(arg) {
this.$store.commit('setSelectedOperator', arg);
}
},
}
</script>
OperatorButton code is
<template>
<span>
<button type="button" v-bind:class="{ sel: operator.selected }"
#click="$emit('select', {'id':operator.id})">
{{ operateur.name }}
</button>
</span>
</template>
<script>
export default {
name: 'operator-button',
props : ['op'],
computed : {
operator() {
return this.$store.getters.operateurById(this.op);
}
},
}
</script>
<style scoped>
.sel{
background-color : yellow;
}
</style>
and finally my app.js look like that :
window.Vue = require('vue');
import Vuex from 'vuex';
import { mapState, mapGetters, mapMutations, mapActions } from 'vuex';
const store = new Vuex.Store({
state: {
periods : [],
},
mutations: {
setInitialData (state, payload) {
state.periods = payload;
},
setSelectedOperator(state, payload) {
this.getters.operateurs.forEach( op => {
op.selected = (op.id==payload.id)
})
},
},
getters : {
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
// op.selected=false; //replaced after Radu Diță answer by next line :
if (ops.selected === undefined) op.selected=false;
})
return ops;
}
},
operatorById : (state, getters) => (id) => {
return getters.operators.find(operator => operator.id==id);
},
}
});
import Chrono from './components/Chrono.vue';
var app = new Vue({
el: '#app',
store,
components : { Chrono },
mounted () {
this.$store.commit('setInitialData',
JSON.parse(this.$el.attributes.initialdata.value));
},
computed: {
...mapState(['periods']),
...mapGetters(['operators', 'operatorById']),
selectedOperator(){
this.$store.getters.operators.forEach(op =>{
if (op.selected) return op;
});
return null;
}
},
});
Your getter in vuex for operators is always setting selected to false.
operators : (state) => {
if (Array.isArray(state.periods))
{
let ops = state.periods
.map( item => {
return item.operators
}).flat();
ops.forEach(op => {
op.selected=false;
})
return ops;
}
}
I'm guessing you do this for initialisation, but that's a bad place to put it, as you'll never get a selected operator from that getter. Just move it to the proper mutations. setInitialData seems like the right place.
Finally I found where my problems came from :
The $el.attributes.initialdata.value came from an API and the operator objects it contained didn't have a selected property, so I added it after data was set and it was not reactive.
I just added this property on server side before converting to JSON and sending to Vue, removed the code pointed by Radu Diță since it was now useless, and it works.

Vuex-map-fields updating multiple stores through modules

I'm working Vuex modules to state my data.
I store the data in multiple modules to keep my code base nice and clean.
When using vuex-map-fields I have a situation where I'm using data from multiple modules.
There seems to be no method to do this or I am doing it wrong.
Below is my current code;
My component
<template>
<div class="">
<input type="text" v-model="no_panels"><br>
<input type="text" v-model="firstName"><br>
<router-link to="/step-2">Go to step 2</router-link>
</div>
</template>
<script>
import { createHelpers } from 'vuex-map-fields';
const { mapFields } = createHelpers({
getterType: [
'getKitchenField',
'getApplicantField',
],
mutationType: 'updateKitchenField',
});
export default {
computed: {
...mapFields(['no_panels', 'firstName', 'lastName'])
},
}
</script>
My store file
import kitchen from './kitchen';
import applicant from "./applicant";
export default {
modules: {
kitchen: kitchen,
applicant: applicant
},
strict: false
}
Applicant.js
import { createHelpers } from 'vuex-map-fields';
const { getApplicantField, updateApplicantField } = createHelpers({
getterType: 'getApplicantField',
mutationType: 'updateApplicantField',
});
export default {
state: {
firstName: '',
lastName: ''
},
getters: {
getApplicantField
},
mutations: {
updateApplicantField
}
}
The code above results in the following error:
Error in render: "TypeError: this.$store.getters[getterType] is not a function"
I'm the creator of vuex-map-fields, the Author asked the same question on GitHub:
Instead of passing multiple getters to createHelpers(), you can destructure and rename the return value of createHelpers() and call it twice.
const { mapFields: mapKitchenFields } = createHelpers({
getterType: 'getKitchenField',
mutationType: 'updateKitchenField',
});
const { mapFields: mapApplicantFields } = createHelpers({
getterType: 'getApplicantField',
mutationType: 'updateApplicantField',
});
export default {
computed: {
...mapKitchenFields(['no_panels']),
...mapApplicantFields(['firstName', 'lastName']),
},
}
If the desctructuring syntax is new to you, you can read more about it from Wes Bos: https://wesbos.com/destructuring-renaming/