vue v-for: translate with switch in vue i18n - vue.js

I have a problem to translate the information with vue i18n that is called through "v-for", all the data from the template is translated without problem, but those that I export through arrays and scripts do not render
i am using vue-i18n: version 7.8.1

You're only ever setting the helpTitles property when your component is created.
I would suggest using $t() in your templates instead of within data(). Then it will automatically react to changes.
I honestly don't think using an array from the translation file is a great idea. I'd be more inclined to add them with their own keys, just like your question and info translation keys, eg
helpStartedTitle: "GETTING STARTED - MODEL",
helpMembersTitle: "MEMBERS",
helpAccountTitle: "ACCOUNT",
//etc
You could then set up the keys in your data like this
data: () => {
const keys = [
"helpStarted",
"helpMembers",
"helpAccount",
"helpPayment",
"helpSocial",
"helpFraud",
"helpSupport",
"helpStudio"
]
return {
helpInfo: keys.map((key, id) => ({
id,
title: `general.help.${key}Title`,
question: `general.help.${key}`,
answer: `general.help.${key}Info`
}))
}
}
then in your template
<div v-for="help in helpInfo" :key="help.id">
<div :id="help.id" class="help-subtitle">{{ $t(help.title) }}:</div>
<HelpItem
:question="$t(help.question)"
:answer="$t(help.answer)"
:num="help.id"
/>
</div>
Even better would be to just pass the translation keys through to your HelpItem component and use $t() with it.
<HelpItem
:question="help.question"
:answer="help.answer"
:num="help.id"
/>
and in the HelpItem component
export default {
name: "HelpItem",
props: {
question: String,
answer: String,
num: Number
},
// etc
}
<!-- just guessing here -->
<h1>{{ $t(question) }}</h1>
<p>{{ $t(answer) }}</p>
FYI, I've corrected answear to answer throughout.

Related

How can I rerender my vue template after changing v-for list?

So I'm trying to change a list based on a whether the elements are considered active or not. I do this through a computed data array. Basically a Search Function. However my template does not rerender and update automatically, even though I try to force it with this.forceUpdate().
This is my v-for in template:
<ion-list>
<div v-for="project in activeProjects" :key="project">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>
This is my computed array. The Log returns the correct things.
computed: {
activeProjects: function() {
return this.myprojects.filter(function(u){
console.log(u);
return u.active
})
}
}
And this is where I update the activity. The Log also returns the correct things.
search: function(){
for(var i=0; i<this.myprojects.length; i++){
if(this.myprojects[i].name.includes(this.searchinput)){
this.myprojects[i].active=true;
console.log(this.myprojects[i])
}
}
this.$forceUpdate();
}
Grateful for any help
I understand what you're attempting with the $forceUpdate, but I'm not certain that's the intended behavior here. In particular, by directly modifying the property of an Object in an Array, I believe Vue is missing the changes completely, so it doesn't know what to forceUpdate.
(See these links to read more on when Vue does / doesn't recognize mutations to Objects and Arrays)
TBH I've never attempted to use forceUpdate in this way, but I have done some Array mutation in a spreadsheet-like scenario before and it was a pain... I would avoid it if at all possible.
Rather than modifying a property in the array, I'd compute the filter on-the-fly using a method. You should get the reactivity you want because you're calculating, not mutating, the properties of the list of projects.
<script>
export default {
props: ['myprojects'],
data() {
return {
searchinput: ''
}
},
computed: {
activeProjects() {
return this.myprojects.filter(this.isInSearch)
}
},
methods: {
isInSearch(project) {
return project.name.includes(this.searchinput)
}
}
}
</script>
Vue caches nodes based on :key value. You're passing the entire object, you should be using a unique property on your project.
Try yo use name or an unique id if you have one.
<ion-list>
<div v-for="project in activeProjects" :key="project.name">
<ion-item v-if="checkemail!=project.creator">
<ion-button #click="openProjectPage(project.id)">{{ project.name }}</ion-button>
</ion-item>
</div>
</ion-list>```

How to add different components to the page on-the-fly with Vue.js

My goal is to allow the user to dynamically build a form from different components, based on what they choose in a select element. For example, they can choose to add a Heading, then maybe a Paragraph, then another Heading, etc. Each "part" is a separate Component.
I know this sort of thing has been asked before, but I'm only day 2 into Vue and I think I'm 90% of the way there - I'm just missing something. What I believe I'm stuck on is how to add a component to my app's data to allow Vue to render it out.
Here is the relevant markup:
<div id="creator">
<template v-for="part in existingParts">
<component :is="part"></component>
</template>
<select class = "custom-select" id = "new-part-chooser" v-model="newPart" v-on:change="addPart">
<option disabled value = "">Add a new part</option>
<option
v-for="part in possibleParts"
v-bind:value="part.toLowerCase()"
>{{ part }}</option>
</select>
<?php
// These simply bring in the templates for the components
// I know this isn't standard practice but... one thing at a time
include 'component-heading.html';
include 'component-paragraph.html';
?>
</div>
and my javascript file:
Vue.component("part-heading",{
data:function(){
return {
text: ""
}
},
template:"#component-heading-template"
});
Vue.component("part-paragraph",{
data:function(){
return {
text: ""
}
},
template:"#component-paragraph-template"
});
const Creator = new Vue({
el:"#creator",
data:{
newPart:"",
possibleParts:[
"Heading",
"Paragraph"
],
existingParts:[]
},
methods:{
addPart:function(e){
/*** This is where I'm stuck - what do I push here? ***/
this.existingParts.push();
}
}
});
I've read through the docs and Google'd the hell out of the topic, but every setup seems to be just different enough that I can't figure out how to apply it.
I was missing the fact that in the markup, the :is directive causes Vue to create a component that matches the name of the element in existingParts. So push()-ing "part-heading" into existingParts causes Vue to render an instance of the "part-heading" component.
The updated, working code is:
this.existingParts.push('part-'+this.newPart);

Nuxt JS i18n / multilingual with headless CMS

Just started playing around with Nuxt (and Vue in general) so please forgive me if I am overlooking something obvious here.
I am trying to set up a multilingual site using DatoCMS as my content source.
I have set up the nuxt-i18n plugin, translated routes and all is working fine. Now in a page, I need to switch the content depending on the current locale and I have only found examples with the content being stored locally in json files etc.
I found a workaround which can't be the way it is supposed to be:
<template>
<div>
<div v-if="this.$metaInfo.htmlAttrs.lang === 'de-DE'">{{homepage.germanTitle}}</div>
<div v-else>{{homepage.englishTitle}}</div>
</div>
</template>
<script>
import gql from 'graphql-tag'
export default {
apollo: {
homepage: gql`
{
homepage {
id
title
englishTitle: title(locale: en)
germanTitle: title(locale: de)
}
}
`
}
}
</script>
Any pointers would be much appreciated! :)
I'd say that this is the right way to do it:
<div>
<template v-if="$i18n.locale === 'en">{{homepage.englishTitle}}</tempate>
<template v-else>{{homepage.germanTitle}}</tempate>
</div>
You shouldn't use this inside of template, check i18n API to see where you can get data for your condition (in this case I use $i18n.locale), use template instead of div (or ternary operator, in order to avoid markup issues in some cases).
BUT, if its possible with graphql, I'd better do the following:
homepage {
id
title: {
en: title(locale: en)
de: title(locale: de)
}
}
template:
<div>{{ homepage.title[$i18n.locale] }}</div>
I think that you should fetch the data differently from GraphQL.
I would do something like:
query {
homepage(locale: $locale) {
id
title
}
}
From DatoCMS docs: https://www.datocms.com/docs/content-delivery-api/localization
But then you need to decide how to pass the $locale variable. That depends on how you prefer. I would check here: https://vue-apollo.netlify.com/guide/apollo/queries.html#simple-query the alternative options that you have.

Vue 2 pass props to child [old : "call child's method"]

ok so I've learned that I'm not supposed to call a child's method but pass it props instead.
I've got (parent) :
<template>
<div id="main">
<Header :title ="title"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
name: 'app'
data: function () {
return {
title: true
}
},
methods: {
hideTitle: function () {
this.title = false
console.log(this.title)
},
showTitle: function () {
this.title = true
console.log(this.title)
}
}
}
</script>
and (child) :
<script>
export default {
name: 'Header',
props: ['title'],
created () {
console.log(this.title)
},
methods: {
}
}
</script>
the first console logs (inside the parent) print correctly on each method but the second console log within the child stays true all the time. I got this from : Pass data from parent to child component in vue.js
inside what method does the console.log need to be to be printed everytime the methods in the parent are triggered?
(this is why I wanted to go for method-calling, originally, by going with variables instead, we're potentially omitting valuable parts of the process such as optimization and a "when" for the execution(s!!) of our code. pontetally being the key word here, don't blow up on me, keep in mind that I'm learning.)
OLD:
I've browsed the web and I know there a a million different answers
and my point is with the latest version of vue none of those millions
of answers work.
either everything is deprecated or it just doesn't apply but I need a
solution.
How do you call a child method?
I have a 1 component = 1 file setup.
DOM is declared inside a <template> tag javascript is written inside
a <script> tag. I'm going off of vue-cli scaffolding.
latest method I've tried is #emit (sometimes paired with an #on
sometimes not) doesn't work :
child :
<script>
export default {
name: 'Header',
created () {
this.$on('hideTitlefinal', this.hideTitlefinal)
},
methods: {
hideTitlefinal: function () {
console.log('hideeeee')
},
showTitlefinal: function () {
console.log('shwowwww')
}
}
}
</script>
parent :
<template>
<div id="main">
<Header v-on:hideTitle="hideTitlefinal" v-on:showTitle="showTitlefinal"/>
<router-view/>
<LateralMenu/>
</div>
</template>
<script>
export default {
methods: {
hideTitle: function () {
this.$emit('hideTitle')
},
showTitle: function () {
this.$emit('showTitle')
}
}
}
</script>
console :
Uncaught TypeError: this.$emit is not a function
at Object.showTitle (Main.vue?1785:74)
at VueComponent.showTitle (LateralMenu.vue?c2ae:113)
at boundFn (vue.esm.js?efeb:186)
at invoker (vue.esm.js?efeb:1943)
at HTMLDivElement.fn._withTask.fn._withTask (vue.esm.js?efeb:1778)
Please don't do this. You're thinking in terms of events. When x happens, do y. That's sooo jquery 2005 man. Vue still has all that stuff, but we're being invited to think in terms of a view model...
You want your state in a variable, in window scope, and you want reactive pipes linking your vue stuff to your state object. To toggle visibility, use a dynamic class binding, or v-if. Then think about how to represent your state. It could be as simple as having a property like store.titleVisible. But, you want to 'normalize' your store, and avoid relationships between items of state. So if title visibility really depends on something higher up, like an editMode or something, then just put the higher-up thing in the store, then create computed properties if you need them.
The goal is that you don't care when things happen. You just define the relationships between the markup and the store, then let Vue take care of it. The docs will tell you to use props for parent=>child and $emit for child=>parent communication. Truth is you don't need this until you have multiple instances of a component, or reusable components. Vue stuff talks to a store, not to other vue stuff. For single-use components, as for your root Vue, just use the data:.
Whenever you find yourself writing show/hide methods, you're doing it wrong. It's intuitive (because it's procedural), but you'll quickly appreciate how much better the MVVM approach is.

vue-i18n : how to use inside vue instance filter

I want to use a filter to perform translations.
Problem is that 'this' doesn't point to my vue instance inside my filter function.
This is what I currently have.
inside my template I have this:
<p>{{ parking.status | translate }} </p>
inside my component I have this:
new Vue({ ...
filters: {
translate: function(value, vue) {
return this.$i18n.t('MLAA-47');
}
The error I get is that this == undefined.
How do i point it to my vue instance inside my filter function ?
As points #vitaly-mosin in the comment in that answer explains that you couldn't use this inside the filter functions.
filters are primarily designed for text transformation purposes. For
more complex data transforms in other directives, you should use
Computed properties instead.
I had the same issue and I resolved moving the translation with $i18n to a computed method, like:
Inside your template, instead of this:
<p>{{ parking.status | translate }} </p>
Change it to:
<p>{{ translateStatus(parking.status) }} </p>
And in the methods:
methods: {
translateStatus (status) {
return this.$t(status)
}
},
I guess that you have a dynamic status (not returning always: 'MLAA-47') and you should assert that you have translations for all of them. It worked for me!
Hope it helps to you too
We can import file exporting i18n instance like this
import i18n from '#/i18n.js'
And use it like this
i18n.t('your.message')
I am not sure if this is a good idea, but so far it is doing what I want.
Define the filters like
// the instance.vue will be set on App.vue
export const instance = {
vue: null
};
// little helper
const t = (key) => instance.vue?.$t?.(key);
export default {
filter: (v) => v + t('somekey')
}
Then in App.vue (or whatever it is you use), do
import {instance} from '#/filters'
export default {
mounted() {
instance.vue = this; // set the instance here