Vue-i18n not translating inside component script tags - vue.js

Building a language switcher, all works fine but when I use the $t() inside the data object it will not be dynamic when I switch between a language.
Component.vue
<template>
// loop menu here
<div v-for="item in menu">
{{ item.label }}
</div>
</template>
<script>
const mainMenu = [
{
label: $t('dashboard'),
},
{
label: $t('users'),
},
{
label: $t('settings'),
},
}
export default {
data () {
return {
menu = MainMenu
}
}
}
</script>
i18n.js
// https://vue-i18n.intlify.dev/
import { createI18n } from 'vue-i18n'
export function loadLocalMessages () {
const locales = require.context('../locales', 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;
}
const i18n = createI18n({
locale: 'en',// .env not working
fallbackLocale: 'en',// .env not working
messages: loadLocalMessages(),
});
export default i18n

<template>
<div v-for="item in menu">
{{ item.label }}
</div>
</template>
<script>
export default {
computed: {
menu() {
return [{
label: this.$t('dashboard'),
}, {
label: this.$t('users'),
}, {
label: this.$t('settings'),
}]
}
}
}
</script>

data is only ever called once when creating the component, and it's not intended to be reactive.
To make a property reactive on $t(), it should be computed:
export default {
computed: {
hello() {
return this.$t('hello')
}
}
}
demo

Related

Attribute patch in pinia store is working from one component, but not from another (Nuxt.js)

I've those two components using Nuxt 3.
The setActive method in component 1 changes the state of activeColor, but the cancelEdit method in component 2 does not.
Any idea why this is the case?
Component 1
Here the setActive method changes activeColor:
<template>
<div class="f-button" #click="addColor">+ Add Color</div>
{{ activeColor }}
<div class="f-colors">
<Color v-for="color in colors" :color="color" :edit="activeColor === color" #click="setActive(color)"/>
</div>
</template>
<script>
import {useProjectStore} from "~/stores/projects";
import {storeToRefs} from "pinia";
import Color from "~/components/Color.vue";
export default defineComponent({
name: "ColorManagement",
components: {Color},
setup() {
const projectStore = useProjectStore()
const { getColors, getActiveColor } = storeToRefs(projectStore);
return {
projectStore,
colors: getColors,
activeColor: getActiveColor
};
},
methods: {
addColor() {
...
},
setActive(color) {
this.projectStore.$patch({ activeColor: color })
}
}
});
</script>
Component 2
Here the cancelEdit method doesn't change activeColor:
<div class="f-color">
<div class="f-color__actions" v-if="edit">
<div class="f-color__action" #click="cancelEdit">
<Cancel /><span>Cancel</span>
</div>
</div>
</div>
</template>
<script>
import Cancel from "~/components/icons/Cancel.vue";
import {useProjectStore} from "~/stores/projects";
import {storeToRefs} from "pinia";
export default defineComponent({
name: "Color",
components: {Cancel},
props: ["color","edit"],
setup() {
const projectStore = useProjectStore()
const { activeColor } = storeToRefs(projectStore);
return {
projectStore,
activeColor
};
},
methods: {
cancelEdit() {
this.projectStore.$patch({ activeColor: false })
}
}
});
</script>
nuxt.config.ts
export default defineNuxtConfig({
vite: {
css: {
preprocessorOptions: {
scss: {
additionalData: '#use "#/assets/styles/_styles.scss" as *;'
}
}
}
},
modules: ['#pinia/nuxt']
})
Store
import { defineStore } from "pinia";
export const useProjectStore = defineStore({
id: 'project',
state: () => {
return {
project: {
colors: [{ color: false, name: '' }]
},
activeColor: null
}
},
getters: {
getColors(state){
return state.project.colors || [];
},
getActiveColor(state){
return state.activeColor;
}
}
});
Ok, if I got this correctly, the deal is this:
Your so called Component 2 is the <Color ... component being used in Component 1, right?
When you trigger cancelEdit inside Component 2 (aka Color) you are also triggering the logic from setActive due to this <Color ...#click="setActive(color)"...so your activeColor is set to false (from the cancelEdit method) but right after it is set to active again, got it?
To fix this (if you don't want to change your HTML structure) you can use events stopPropagation method inside the cancelEdit:
cancelEdit(e) {
e.stopPropagation()
this.projectStore.$patch({ activeColor: false })
}
Event.stopPropagation() reference

Vuelidate with i18n: Key not found in locale messages

So in my i18n-validators.js file I want to export validators with translated messages to my language of choice and use them in my vue component to validate a form.
My code:
// import * as VuelidateValidators from 'https://cdn.jsdelivr.net/npm/#vuelidate/validators';
// import * as VueI18n from 'https://unpkg.com/vue-i18n#9';
const messages = {
en: {
validations: {
required: 'The field {property} is required.',
}
},
cs: {
validations: {
required: 'Toto pole {property} je povinné',
}
},
}
const i18n = VueI18n.createI18n({
locale: 'cz',
fallbackLocale: 'en',
messages
})
const withI18nMessage = VuelidateValidators.createI18nMessage({
t: VueI18n.createI18n().global.t.bind(i18n)
})
export const required = withI18nMessage(VuelidateValidators.required)
Console:
Not found 'validations.required' key in 'en-US' locale messages. vue-i18n#9
Fall back to translate 'validations.required' key with 'en' locale. vue-i18n#9
Not found 'validations.required' key in 'en' locale messages.
And I want the validator to throw me the specified message instead of the "validations.required" message
First make sure you have installed vuelidade and vue-i18n
Following your example, you can change the file above to:
import * as validators from "#vuelidate/validators";
import { createI18n } from "vue-i18n";
const { createI18nMessage } = validators;
const messages = {
en: {
validations: {
required: "The field {property} is required.",
},
},
cs: {
validations: {
required: "Toto pole {property} je povinné",
},
},
};
const i18n = createI18n({
locale: "cs",
fallbackLocale: "en",
messages,
});
const withI18nMessage = createI18nMessage({ t: i18n.global.t.bind(i18n) });
export const required = withI18nMessage(validators.required);
as a component you can follow this one as example:
<template>
...
<div class="mb-3">
<input
v-model="formData.name"
className="form-control"
placeholder="Insert your name.."
/>
</div>
<span v-for="error in v$.name.$errors" :key="String(error.$uid)">
<span class="text-danger">{{ error.$message }}</span>
</span>
<div class="mt-5 submit">
<button class="btn btn-primary btn-sm" type="button" #click="submitForm">
Next
</button>
</div>
...
</template>
<script lang="ts">
import { defineComponent, reactive } from "vue";
import useVuelidate from "#vuelidate/core";
import { required } from "#/utils/validators/i18n-validators";
export default defineComponent({
name: "InitialDataForm",
setup() {
const formData = reactive({
name: "",
});
const rules = {
name: { required },
};
const v$ = useVuelidate(rules, formData);
return {
formData,
v$,
};
},
methods: {
async submitForm() {
const result = await this.v$.$validate();
if (result) {
alert("validation passed");
}
},
},
});
</script>
and now you should be able to see the translated message:

Vue received a Component which was made a reactive object

The problem I need to solve: I am writing a little vue-app based on VueJS3.
I got a lot of different sidebars and I need to prevent the case that more than one sidebar is open at the very same time.
To archive this I am following this article.
Now I got a problem:
Vue received a Component which was made a reactive object. This can lead to unnecessary performance overhead, and should be avoided by marking the component with markRaw or using shallowRef instead of ref. (6)
This is my code:
SlideOvers.vue
<template>
<component :is="component" :component="component" v-if="open"/>
</template>
<script>
export default {
name: 'SlideOvers',
computed: {
component() {
return this.$store.state.slideovers.sidebarComponent
},
open () {
return this.$store.state.slideovers.sidebarOpen
},
},
}
</script>
UserSlideOver.vue
<template>
<div>test</div>
</template>
<script>
export default {
name: 'UserSlideOver',
components: {},
computed: {
open () {
return this.$store.state.slideovers.sidebarOpen
},
component () {
return this.$store.state.slideovers.sidebarComponent
}
},
}
</script>
slideovers.js (vuex-store)
import * as types from '../mutation-types'
const state = {
sidebarOpen: false,
sidebarComponent: null
}
const getters = {
sidebarOpen: state => state.sidebarOpen,
sidebarComponent: state => state.sidebarComponent
}
const actions = {
toggleSidebar ({commit, state}, component) {
commit (types.TOGGLE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
},
closeSidebar ({commit, state}, component) {
commit (types.CLOSE_SIDEBAR)
commit (types.SET_SIDEBAR_COMPONENT, component)
}
}
const mutations = {
[types.TOGGLE_SIDEBAR] (state) {
state.sidebarOpen = !state.sidebarOpen
},
[types.CLOSE_SIDEBAR] (state) {
state.sidebarOpen = false
},
[types.SET_SIDEBAR_COMPONENT] (state, component) {
state.sidebarComponent = component
}
}
export default {
state,
getters,
actions,
mutations
}
App.vue
<template>
<SlideOvers/>
<router-view ref="routerView"/>
</template>
<script>
import SlideOvers from "./SlideOvers";
export default {
name: 'app',
components: {SlideOvers},
};
</script>
And this is how I try to toggle one slideover:
<template>
<router-link
v-slot="{ href, navigate }"
to="/">
<a :href="href"
#click="$store.dispatch ('toggleSidebar', userslideover)">
Test
</a>
</router-link>
</template>
<script>
import {defineAsyncComponent} from "vue";
export default {
components: {
},
data() {
return {
userslideover: defineAsyncComponent(() =>
import('../../UserSlideOver')
),
};
},
};
</script>
Following the recommendation of the warning, use markRaw on the value of usersslideover to resolve the warning:
export default {
data() {
return {
userslideover: markRaw(defineAsyncComponent(() => import('../../UserSlideOver.vue') )),
}
}
}
demo
You can use Object.freeze to get rid of the warning.
If you only use shallowRef f.e., the component will only be mounted once and is not usable in a dynamic component.
<script setup>
import InputField from "src/core/components/InputField.vue";
const inputField = Object.freeze(InputField);
const reactiveComponent = ref(undefined);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = undefined;
}, 5000);
setTimeout(function() => {
reactiveComponent.value = inputField;
}, 5000);
</script>
<template>
<component :is="reactiveComponent" />
</template>

Nuxt + Vuex mapGetters value is always undefined

I'm using VueX with Nuxt.JS so let's suppose the following code in the file store/search.js:
export const state = () => ({
results: null
});
export const mutations = {
setResults(state, { results }) {
state.results = results;
}
};
export const actions = {
startSearch({ commit, dispatch }, { type, filters }) {
commit("setResults", { type, filters });
}
};
export const getters = {
results: state => state.results
};
Now in my component results.vue, under the computed property I have something like this:
<template>
<button #click="handleSearch">Search</button>
<div v-if="results && results.length" class="results" >
<div v-for="item in results" :key="item.id">
{{item}}
</div>
</div>
</template>
<script>
import { mapActions, mapGetters } from "vuex";
data() {
return {
selected_type: null,
filters: null
};
},
methods: {
setType(type) {
this.selected_type = type;
this.handleSearch();
},
setFilters(filters) {
this.filters = filters;
},
handleSearch() {
this.startSearch({ type: this.selected_type, filters: this.filters });
},
...mapActions("search", {
startSearch: "startSearch"
})
},
computed: {
...mapGetters("search", {
results: "results"
})
}
</script>
My question is: why the item in the for loop (in the template section) always return undefined ?
Thank you very much for your answers.
So far, I found it:
in computed should be an array, not an object so:
...mapGetters("search", [
"results"
]
// Now results is populated.

Make Chartkick wait for data to populate from Firebase before rendering chart

I'm using chartkick in my Vue project. Right now, the data is loading from Firebase after the chart has rendered, so the chart is blank. When I change the code in my editor, the chart renders as expected, since it's already been retrieved from Firebase. Is there a way to make chartkick wait for the data to load before trying to render the chart? Thanks!
Line-Chart Component:
<template>
<div v-if="loaded">
<line-chart :data="chartData"></line-chart>
</div>
</template>
<script>
export default {
name: 'VueChartKick',
props: ['avgStats'],
data () {
return {
loaded: false,
chartData: this.avgStats
}
},
mounted () {
this.loaded = true
}
}
</script>
Parent:
<template>
...
<stats-chart v-if="avgStatsLoaded" v-bind:avgStats="avgStats" class="stat-chart"></stats-chart>
<div v-if="!avgStatsLoaded">Loading...</div>
...
</template>
<script>
import StatsChart from './StatsChart'
export default {
name: 'BBall',
props: ['stats'],
components: {
statsChart: StatsChart
},
data () {
return {
avgStatsLoaded: false,
avgStats: []
}
},
computed: {
sortedStats: function () {
return this.stats.slice().sort((a, b) => new Date(b.date) - new Date(a.date))
}
},
methods: {
getAvgStats: function () {
this.avgStats = this.stats.map(stat => [stat.date, stat.of10])
this.avgStatsLoaded = true
}
},
mounted () {
this.getAvgStats()
}
}
modify your code of StatsChart component:
you may use props directly
<template>
<div v-if="loaded">
<line-chart :data="avgStats"></line-chart>
</div>
</template>
export default {
name: 'VueChartKick',
props: ['avgStats'],
data () {
return {
loaded: false,
}
},
mounted () {
this.loaded = true
}
}