i'm new to vue-i18n, seams great, but have some challenge getting it to work probably.
All template translations are updated as expected when changing locale, but when
script
data() {
return {
locales: {
en: this.$i18n.t('topnav.lang.english'),
da: this.$i18n.t('topnav.lang.danish'),
sw: this.$i18n.t('topnav.lang.swedish'),
no: this.$i18n.t('topnav.lang.norwegian'),
}
}
},
template
WORKING
{{$t('topnav.lang.english')}}
NOT WORKING
<a class="dropdown-item">{{locales.en}}</a>
NOT WORKING
<a class="dropdown-item" #click="changeLocale(key)" v-for="(value, key) in locales">{{value}}</a>
i have tried a lot of things, eg. lazyload the languages files and so on, but with no luck.
change from data to computed, data is not inherently reactive but luckily computed is!
the alternative is to directly put your translation in the template if you do not want to use computed
Related
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.
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);
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.
I'd like to insert new vuejs components on the fly, at arbitrary points within a block of not-necessarily-predefined HTML.
Here's a slightly contrived example that demonstrates the sort of thing I'm trying to do:
Vue.component('child', {
// pretend I do something useful
template: '<span>--><slot></slot><--</span>'
})
Vue.component('parent', {
data() {
return {
input: 'lorem',
text: '<p>Lorem ipsum dolor sit amet.</p><p><i>Lorem ipsum!</i></p>'
}
},
template: `<div>
Search: <input type='text' v-model="input"><br>
<hr>
This inserts the child component but doesn't render it
or the HTML:
<div>{{output}}</div>
<hr>
This renders the HTML but of course strips out the child component:
<div v-html="output"></div>
<hr>
(This is the child component, just to show that it's usable here:
<child>hello</child>)
<hr>
This is the goal: it renders both the input html
and the inserted child components:
TODO ¯\_(ツ)_/¯
</div>`,
computed: {
output() {
/* This is the wrong approach; what do I replace it with? */
var out = this.text;
if (this.input) {
this.input = this.input.replace(/[^a-zA-Z\s]/g,'');
var regex = new RegExp(this.input, "gi");
out = out.replace(regex, '<child><b>' + this.input + '</b></child>');
}
return out;
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<parent></parent>
</div>
In the above snippet, assume data.text is sanitized HTML. <child> is some sub-component that does something useful, which I want to wrap around chunks of data.text that aren't known ahead of time. (input is just for demo here. This MCVE doesn't really resemble the code I'm building, it's just an example that shows the sort of situation I'm stuck on.)
So: how would I change either the output function or the parent component's template, such that both the HTML from input and the inserted <child> templates are rendered properly?
What I've tried
In Vue 1, the answer to this would be a straightforward $compile. I'm using vuejs2 which removed $compile (out of justifiable concern that it made it too easy to naively introduce XSS vulnerabilities.)
v-html sanitizes what you feed it, which strips the child component out. Obviously this is not the way to do this. (That page suggests using partials instead, but I'm not sure how that could be applied to this situation; in any case partials have also been removed from vue2.)
I've tried passing the results of output() into another component which would then use it as its template. This seems like a promising approach, but I can't figure out how to change that secondary component's template. template only accepts a string, not a function like many of the other component properties, so I can't pass the template html in, say, a prop. Something like rewriting this.template inside beforeMount() or bind() would have been nice, but no joy there either. Is there some other way to replace a component's template string before it's mounted?
Unlike template, I can pass data to a component's render() function... but then I'm still stuck having to parse that html string into nested createElement functions. Which is exactly what Vue is doing internally in the first place; is there some way to hook into that here short of reinventing it myself?
Vue.component('foo', {
props: ['myInput'],
render(createElement) {
console.log(this.myInput); // this works...
// ...but how to parse the html in this.myInput into a usable render function?
// return createElement('div', this.myInput);
},
})
I wasn't able to cheat my around this with inline-template, either: <foo inline-template>{{$parent.output}}</foo> does exactly the same thing as a plain old {{output}}. In retrospect that should have been obvious, but it was worth a shot.
Maybe constructing an async component on the fly is the answer? This could clearly generate a component with an arbitrary template, but how would I reasonably call that from the parent component, and feed output to the constructor? (It would need to be reusable with different input, with multiple instances potentially visible simultaneously; no globals or singletons.)
I've even considered ridiculous stuff like having output() split the input into an array at the points where it would have inserted <child>, and then doing something like this in the main template:
...
<template v-for="chunk in output">
<span v-html="chunk"></span>
<child>...</child>
</template>
....
That would be doable, if laborious -- I'd have to split out what goes in the child's slot into a separate array too and get it by index during the v-for, but that could be done... if input were plain text instead of HTML. In splitting HTML I'll often wind up with unbalanced tags in each chunk, which can mess up the formatting when v-html rebalances it for me. And anyway this whole strategy feels like a bad hack; there must be a better way.
Maybe I just drop the whole input into a v-html and then (somehow) insert the child components at the proper positions through after-the-fact DOM manipulation? I haven't explored this option too deeply because it, too, feels like a hack, and the reverse of the data-driven strategy, but maybe it's a way to go if all else fails?
A couple of pre-emptive disclaimers
I'm very well aware of the XSS risks involved in $compile-like operations. Please be assured that none of what I'm doing involves unsanitized user input in any way; the user isn't inserting arbitrary component code, instead a component needs to insert child components at user-defined positions.
I'm reasonably confident that this is not an XY problem, that I really do need to insert components on the fly. (I hope it's obvious from the number of failed attempts and blind alleys I've run down that I've put more than a little thought into this one!) That said, if there's a different approach that leads to similar results, I'm all ears. The salient point is that I know which component I need to add, but I can't know ahead of time where to add it; that decision happens at run time.
If it's relevant, in real life I'm using the single-file component structure from vue-cli webpack template, not Vue.component() as in the samples above. Answers that don't stray too far from that structure are preferred, though anything that works will work.
Progress!
#BertEvans points out in comments that Vue.compile() is a thing that exists, which is an I-can't-believe-I-missed-that if ever there was one.
But I'm still having trouble using it without resorting to global variables as in that documentation. This renders, but hardcodes the template in a global:
var precompiled = Vue.compile('<span><child>test</child></span>');
Vue.component('test', {
render: precompiled.render,
staticRenderFns: precompiled.staticRenderFns
});
But various attempts to rejigger that into something that can accept an input property have been unsuccessful (the following for example throws "Error in render function: ReferenceError: _c is not defined", I assume because the staticRenderFns aren't ready to go when render needs them?
Vue.component('test', {
props: ['input'],
render() { return Vue.compile(this.input).render()},
staticRenderFns() {return Vue.compile(this.input).staticRenderFns()}
});
(It's not because there are two separate compile()s -- doing the precompile inside beforeMount() and then returning its render and staticRenderFns throws the same error.)
This really feels like it's on the right track but I'm just stuck on a dumb syntax error or the like...
As mentioned in the my comment above, $compile was removed, but Vue.compile is available in certain builds. Using that below works as I believe you intend except in a couple cases.
Vue.component('child', {
// pretend I do something useful
template: '<span>--><slot></slot><--</span>'
})
Vue.component('parent', {
data() {
return {
input: 'lorem',
text: '<div><p>Lorem ipsum dolor sit amet.</p><p><i>Lorem ipsum!</i></p></div>'
}
},
template: `<div>
Search: <input type='text' v-model="input"><br>
<hr>
<div><component :is="output"></component></div>
</div>`,
computed: {
output() {
if (!this.input)
return Vue.compile(this.text)
/* This is the wrong approach; what do I replace it with? */
var out = this.text;
if (this.input) {
this.input = this.input.replace(/[^a-zA-Z\s]/g,'');
var regex = new RegExp(this.input, "gi");
out = out.replace(regex, '<child><b>' + this.input + '</b></child>');
out = Vue.compile(out)
}
return out;
}
}
});
new Vue({
el: '#app'
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.3.0/vue.js"></script>
<div id="app">
<parent></parent>
</div>
You mentioned you are building with webpack and I believe the default for that build is Vue without the compiler, so you would need to modify it to use a different build.
I added a dynamic component to accept the results of the compiled output.
The sample text is not a valid template because it has more than one root. I added a wrapping div to make it a valid template.
One note: this will fail if the search term matches all or part of any of the HTML tags in the text. For example, if you enter "i", or "di" or "p" the results will not be what you expect and certain combinations will throw an error on compilation.
I'm posting this as a supplement to Bert Evans's answer, for the benefit of vue-cli webpack users who want to use .vue files instead of Vue.component(). (Which is to say, I'm mostly posting this so I'll be able to find this information when I inevitably forget it...)
Getting the right Vue build
In vue-cli 2 (and possibly 1?), to ensure Vue.compile will be available in the distribution build, confirm webpack.base.conf.js contains this line:
'vue$': 'vue/dist/vue.esm.js' // or vue/dist/vue.common.js for webpack1
instead of 'vue/dist/vue.runtime.esm.js'. (If you accepted the defaults when running vue init webpack you will already have the full standalone build. The "webpack-simple" template also sets the full standalone build.)
Vue-cli 3 works somewhat differently, and does not have Vue.compile available by default; here you'll need to add the runtimeCompiler rule to vue.config.js:
module.exports = {
/* (other config here) */
runtimeCompiler: true
};
The component
The "child" component can be a normal .vue file, nothing special about that.
A bare-bones version of the "parent" component would be:
<template>
<component :is="output"></component>
</template>
<script>
import Vue from 'vue';
import Child from './Child'; // normal .vue component import
export default {
name: 'Parent',
computed: {
output() {
var input = "<span>Arbitrary single-root HTML string that depends on <child></child>. This can come from anywhere; don't use unsanitized user input though...</span>";
var ret = Vue.compile(input);
ret.components = { Child }; // add any other necessary properties similarly
ret.methods = { /* ... */ } // like so
return ret;
}
}
};
</script>
(The only significant difference between this and the non-webpack version is importing the child, then declaring the component dependencies as ret.components: {Child} before returning it.)
When should I use a directive vs a component in vue.js? I'm implementing some stuff from Bootstrap and it looks like I could do it either way (I'm starting with the dropdown menu).
I get the feeling that a directive is more for manipulating the dom on a single element, while components are for packaging a bunch of data and/or dom manipulation. Is this a good way to look at it?
This Stack Overflow question is the #1 result to the Google query "vue directive vs component". Saurshaz’s answer is currently the accepted one and it’s very wrong in Vue 2.0. I imagine this is leading a lot of people astray so I'm going to weigh in here.
The answer to “should I use a directive or a component in Vue” is almost always a component.
Do you want to have reusable HTML? I.e. reusable widgets? Then use a component. Do you want two of these widgets to have discrete data? Then use a component. The data of one will NOT override the data of another. Maybe that was true in Vue 1.0, I don't know. But it's absolutely not true in Vue 2.0. In Vue 2.0, your components have a data function that returns a unique set of data. Consider this real-life of a Vue dropdown that has an HTML markup similar to the UI Bootstrap dropdown:
<template>
<span class="dropdown sm-dropdown" #click="toggle" :class="{'open': isOpen}">
<a class="dropdown-toggle">
<span class="special-field">{{ label }}</span>
</a>
<ul class="dropdown-menu">
<li v-for="choice in choices">
<a #click.prevent="click(choice)">{{ choice.label }}</a>
</li>
</ul>
</span>
</template>
<script>
export default {
name: 'Dropdown',
props: ['label', 'options', 'onChange'],
data() {
return {
choices: this.options,
isOpen: false
}
},
methods: {
click(option) {
this.onChange(option);
},
toggle() {
this.isOpen = !this.isOpen;
}
}
}
</script>
Now in a parent component, I can do something like this:
<template>
<div class="container">
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
<dropdown
label="-- Select --"
:options="ratingChoices"
:onChange="toggleChoice"
>
</dropdown>
</div>
</template>
<script>
import Dropdown from '../dropdown/dropdown.component.vue';
export default {
name: 'main-directive',
components: { Dropdown },
methods: {
toggleChoice(newChoice) {
// Save this state to a store, e.g. Vuex
}
},
computed: {
ratingChoices() {
return [{
value: true,
label: 'Yes'
}, {
value: false,
label: 'No'
}]
}
}
}
</script>
There's a decent amount of code here. What's happening is we're setting up a parent component and inside that parent component we have two dropdowns. In other words, the dropdown component is being called twice. The point I'm trying to make in showing this code is this: when you click on the dropdown, the isOpen for that dropdown changes for that directive and for that directive only. Clicking on one of the dropdowns does not affect the other dropdown in any way.
Don't choose between components or directives based on whether or not you're wanting discrete data. Components allow for discrete data.
So when would you want to choose a directive in Vue?
Here are a couple of guidelines that'll hopefully get you thinking in the right direction.
You want to choose a directive when you're wanting to extend the functionality of HTML components and you suspect that you’re going to need this extendability across multiple components and you don't want your DOM to get deeper as a result. To understand what I mean by this, let's look at the directives that Vue provides out of the box. Take its v-for directive for instance. It allows you to loop through a collection. That's very useful and you need to be able to do that in any component you want, and you don't want the DOM to get any deeper. That's a good example of when a directive is the better choice.[1]
You want to choose a directive when you want a single HTML tag to have multiple functionality. For example, an element that both triggers an Ajax request and that has a custom tooltip. Assuming you want tooltips on elements other than Ajax-triggering elements, it makes sense to split these up into two different things. In this example I would make the tooltip a directive and the Ajax feature driven by a component so I could take advantage of the built-in #click directive that’s available in components.
1 A footnote for the more curious. In theory v-for could have been made as a component, but doing so would have required a deeper-than-necessary DOM every time you wanted to use v-for as well as a more awkward syntax. If Vue had chosen to make a component out of it, instead of this:
<a v-for="link in links" :href="link.href">link.anchor</a>
The syntax would have had to have been this:
<v-for items="link in links">
<a :href="link.href">link.anchor</a>
</v-for>
Not only is this clumsy, but since the component code would have needed to implement the <slot></slot> syntax in order to get the innerHTML, and since slots cannot be immediate children of a <template> declaration (since there's no guarantee that slot markup has a single node of entry at its top level), this means there would have to be a surrounding top-level element in the component definition for v-for. Hence the DOM would get deeper than necessary. Directive was unequivocally the right choice here.
I think of it this way:
Components define widgets - these are sections of html that have behavior associated with them.
Directives modify behavior of sections of html (which may or may not be widgets).
I think this difference is better explained with two examples.
Components: are wrappers that are best suited when you need to insert (or add) your own HTML tags over something to render it. E.g. a widget, a custom button, etc where you would need to add some HTML tags to show it properly.
Directives: don't add tags but rather give you direct access to the HTML tag (to which you have added the directive). This gives you access to modify the attributes of that HTML element directly. E.g. initializing a tooltip, set css styles, bind to an event, etc.
Reusability is a reason for using directives,
While Components are also creating reusable 'widgets', two components in the same html system would overwrite the previous ones 'data', So think of directives in a case like this.
Another point worth thinking of is - Can user be using it via HTML only after some instructions ?