I have a small vue app where I want to implement the vue-i18n plug to make my app multilingual. I have installed the vue-i18n plugin from the vue cli. I have two locales and everything works as expected - whenever I manually change the locale from the.env file to the desired language, the language in the app also changes.However, whenever I try to change it with a button on the frontend I fail to do so.
This is what I have in my i18n.js file:
import { createI18n } from 'vue-i18n'
function loadLocaleMessages() {
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;
}
export default createI18n({
legacy: false,
locale: process.env.VUE_APP_I18N_LOCALE || 'en',
fallbackLocale: process.env.VUE_APP_I18N_FALLBACK_LOCALE || 'en',
messages: loadLocaleMessages()
})
This is in .env file:
VUE_APP_I18N_LOCALE=en
VUE_APP_I18N_FALLBACK_LOCALE=en
This is code from a tutorial I saw and they access the locale by this.$i18n.locale, however, this does not work for me, this is how I try to implement it:
<template>
<div class="hello">
<h1>Hello World</h1>
<h2>{{ t("hello") }}</h2>
<h2>{{ t("message") }}</h2>
<button #click="SetLocale('en')">EN</button>
<button #click="SetLocale('nl')">NL</button>
</div>
</template>
<script>
import { useI18n } from "vue-i18n";
export default {
name: "HelloWorld",
setup() {
const { t } = useI18n({
inheritLocale: true,
useScope: "local",
});
// Something todo ..
return {
t
};
},
methods: {
SetLocale(locale) {
console.log(locale);
this.$i18n.locale = locale;
},
},
};
</script>
<i18n>
{
"en": {
"hello": "Hello i18n in English! (from component)",
},
"nl": {
"hello": "Hello i18n in Dutch! (from component)",
}
}
</i18n>
The error that I get when I click the button is:
[Vue warn]: Unhandled error during execution of native event handler
Uncaught TypeError: Cannot set properties of undefined (setting
'locale')
I have tried some other solutions like i18n.locale and this.$root.$i18n.locale but they dont seem to work either.
In addition when I try to access the {{ t("message") }} from which message comes from a JSON file from locales folder I get those warnings:
[intlify] Not found 'message' key in 'nl' locale messages.
[intlify] Fall back to translate 'message' key with 'en' locale
[intlify] Not found 'message' key in 'en' locale messages.
[intlify] Fall back to translate 'message' key with 'nl' locale
My question is, where am I doing something wrong and is there a way to get rid of the warnings that I have when I try to access the JSON files from the locales folder?
Related
I am new to Vue and stuck. I am trying to send user input data from a form into a vuex store. From that vuex store, an action will be called (fetching from API) and I would like that data back into my app and components.
<template>
<div>
<h1>APP NAME</h1>
<form action="submit" #submit.prevent="sendCityName()">
<label for="query"></label>
<input
type="text"
id="query"
v-model="cityName"
>
<button type="submit">Submit</button>
</form>
<h3>{{ lat }}</h3>
</div>
</template>
<script>
import { mapState } from 'vuex'
export default {
data() {
return {
cityName: ''
}
},
computed: {
coordinates () {
return this.$store.state.lat
}
},
methods: {
sendCityName() {
this.$store.commit('fetchCity', this.cityName)
}
},
}
</script>
Here is my index.vue and getting the error "Cannot read properties of undefined (reading 'commit')"
here is my store.js. I want to use the lat and lon across my app.
export const state = () => ({
lat: '',
lon: ''
})
export const mutations = {
SET_LAT(state, payload){
state.lat = payload
},
SET_LON(state, payload){
state.lon = payload
}
}
export const actions = {
async fetchCity({ commit }, cityName) {
// make request
axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "xxxxxxx",
q: cityName,
}
}).then((response) => {
commit('SET_LAT', response.data[0].lat);
commit('SET_LON', response.data[0].lng);
});
},
};
When I button submit I get the error "Cannot read properties of undefined (reading 'commit')"
Here is my working repo with the fixes mentioned below.
There are 3 things in your code:
remove vuex from package.json and run yarn again, that one is already baked into Nuxt as stated in the official documentation, those are the only steps needed
all the files inside of store will be namespaced by default for you, since you do have store/store.js, the proper syntax will be
async sendCityName() {
await this.$store.dispatch('store/fetchCity', this.cityName) // 👈🏻 store prefix
}
since you do use the axios module, you should have the following in your action (using the async/await syntax since it's more modern and preferable)
async fetchCity({ commit }, cityName) {
const response = await this.$axios.get(
`https://api.openweathermap.org/geo/1.0/direct`, {
params: {
appid: "3d91ba5b3c11d13158a2726aab902a0b",
q: cityName,
}
})
commit('SET_LAT', response.data[0].lat)
commit('SET_LON', response.data[0].lng)
}
Looking at the browser's console, you also have some errors to fix.
I can also recommend an ESlint + Prettier configuration so that you keep your code error-proof + properly formatted at all times.
I am currently trying to implement a feature where a user can select a language from a dropdown menu in a Settings page (SettingsDialog.vue), updating all of the text to match the new language. This application has multiple Vue files like a MenuBar.vue, HelpDialog.vue, each pulling from translation.ts for their English translations. However, I noticed that selecting a language from the dropdown menu only changed the elements inside my SettingsDialog.vue file, not all of the other Vue files I have.
I tried using the Vue-I18n documentation implementation of changing locale globally in the file. I was expecting for the locale of the entire application to change after selecting a language in SettingsDialog.vue, applying my English translations in translation.ts to the Menu Bar, Help Page, etc. What happened is that the translations from translation.ts only applied to the SettingsDialog.vue page, no where else.
I guess it would be helpful to add that this is an Electron application, and the Vue files in the project use Quasar. Each file does have the correct import statements.
main.ts:
// ...
window.datalayer = [];
const i18n = createI18n({
legacy: false,
locale: "",
messages,
});
createApp(App)
.use(store, storeKey)
.use(router)
.use(
createGtm({
id: process.env.VUE_APP_GTM_CONTAINER_ID ?? "GTM-DUMMY",
vueRouter: router,
enabled: false,
})
)
.use(Quasar, {
config: {
brand: {
primary: "#a5d4ad",
secondary: "#212121",
},
},
iconSet,
plugins: {
Dialog,
Loading,
},
})
.use(ipcMessageReceiver, { store })
.use(markdownItPlugin)
.use(i18n)
.mount("#app");
SettingsDialog.vue
// ...
<!-- Language Setting Card -->
<q-card flat class="setting-card">
<q-card-actions>
<div id="app" class="text-h5">{{ $t("言語") }}</div>
</q-card-actions>
<q-card-actions class="q-px-md q-py-sm bg-setting-item">
<div id="app">{{ $t("言語を選択する") }}</div>
<q-space />
<q-select
filled
v-model="locale"
dense
emit-value
map-options
options-dense
:options="[
{ value: 'ja', label: '日本語 (Japanese)' },
{ value: 'en', label: '英語 (English)' },
]"
label="Language"
>
<q-tooltip
:delay="500"
anchor="center left"
self="center right"
transition-show="jump-left"
transition-hide="jump-right"
>
Test
</q-tooltip>
</q-select>
</q-card-actions>
</q-card>
// ...
<script lang="ts">
import { useI18n } from "vue-i18n";
// ...
setup(props, { emit }) {
const { t, locale } = useI18n({ useScope: "global" });
// ...
return {
t,
locale,
// ...
};
MenuBar.vue
<template>
<q-bar class="bg-background q-pa-none relative-position">
<div
v-if="$q.platform.is.mac && !isFullscreen"
class="mac-traffic-light-space"
></div>
<img v-else src="icon.png" class="window-logo" alt="application logo" />
<menu-button
v-for="(root, index) of menudata"
:key="index"
:menudata="root"
v-model:selected="subMenuOpenFlags[index]"
:disable="menubarLocked"
#mouseover="reassignSubMenuOpen(index)"
#mouseleave="
root.type === 'button' ? (subMenuOpenFlags[index] = false) :
undefined
"
/>
// ...
<script lang="ts">
import { defineComponent, ref, computed, ComputedRef, watch } from "vue";
import { useStore } from "#/store";
import MenuButton from "#/components/MenuButton.vue";
import TitleBarButtons from "#/components/TitleBarButtons.vue";
import { useQuasar } from "quasar";
import { HotkeyAction, HotkeyReturnType } from "#/type/preload";
import { setHotkeyFunctions } from "#/store/setting";
import {
generateAndConnectAndSaveAudioWithDialog,
generateAndSaveAllAudioWithDialog,
generateAndSaveOneAudioWithDialog,
} from "#/components/Dialog";
import { useI18n } from "vue-i18n";
import messages from "../translation";
type MenuItemBase<T extends string> = {
type: T;
label?: string;
};
export type MenuItemSeparator = MenuItemBase<"separator">;
export type MenuItemRoot = MenuItemBase<"root"> & {
onClick: () => void;
subMenu: MenuItemData[];
};
export type MenuItemButton = MenuItemBase<"button"> & {
onClick: () => void;
};
export type MenuItemCheckbox = MenuItemBase<"checkbox"> & {
checked: ComputedRef<boolean>;
onClick: () => void;
};
export type MenuItemData =
| MenuItemSeparator
| MenuItemRoot
| MenuItemButton
| MenuItemCheckbox;
export type MenuItemType = MenuItemData["type"];
export default defineComponent({
name: "MenuBar",
components: {
MenuButton,
TitleBarButtons,
},
setup() {
const { t } = useI18n({
messages,
});
// ...
};
const menudata = ref<MenuItemData[]>([
{
type: "root",
label: t("ファイル"),
onClick: () => {
closeAllDialog();
},
// ...
]);
translation.ts
const messages = {
en: {
// MenuBar.vue
ファイル: "File",
エンジン: "Engine",
ヘルプ: "Help",
// SettingDialog.vue
言語: 'Language',
言語を選択する: 'Select Language',
オフ: 'OFF',
エンジンモード: 'Engine Mode',
// HelpDialog.vue
ソフトウェアの利用規約: 'test',
}
};
export default messages;
Maybe there are more problems but now I see two:
Your menudata should be computed instead of just ref. Right now you are creating a JS object and setting it label property to result of t() call. When global locale changes this object is NOT created again. It still holds same value the t() function returned the only time it was executed - when setup() was running
// correct
const menudata = computed<MenuItemData[]>(() => [
{
type: "root",
label: t("ファイル"),
onClick: () => {
closeAllDialog();
},
// ...
]);
This way whenever i18n.global.locale changes, your menudata is created again with new translation
As an alternative, set label to key and use t(label) inside the template. However computed is much more effective solution...
You don't need to pass messages to useI18n() in every component. Only to the global instance. By passing config object into a useI18n() in a component you are creating Local scope which makes no sense if you are storing all translations in a single global place anyway
I'm currently working on a notification plugin in my project. I want every component to be able to trigger a notification like this:
this.$notification({
msg: 'error message'
});
To achieve that, I created a following plugin with a component:
Notifications.js
import Notifications from './Shared/Components/Notifications';
import Vue from 'vue';
export default {
install: (app, options) => {
app.component('notifications', Notifications)
Vue.prototype.$notification = function (options) {
Notifications.methods.show(options);
}
},
}
Notifications.vue
<template>
<div>
{{ message }}
</div>
</template>
<script>
export default {
name: "Notifications",
data() {
return {
message: '',
}
},
methods: {
show(options) {
this.message = options.msg;
}
}
}
</script>
But now, when another component triggers the plugin the view of the notifications.vue component does not change. But if I log the new value this.message it shows the new value. How do I achieve, that the new message "error message" is shown?
I already tried to watch the property, but it does not get triggered.
I am using vuetify together with vue-i18n, following the instructions here.
translations.js:
const deDict = require('./i18n/vuetify/de.json');
const enDict = require('./i18n/vuetify/en.json');
export default class Translations {
constructor() {
this.messages = {
de: deDict,
en: enDict,
};
}
}
init.js:
import Translations from './translations';
// Create VueI18n instance with options
const tr = new Translations();
const i18n = new VueI18n({
locale: 'en', // default locale is English
messages: tr.messages, // set locale messages
});
Vue.use(Vuetify, {
lang: {
/* eslint-disable-next-line vue-i18n/no-dynamic-keys */
t: (key, ...params) => i18n.t(key, params),
},
}
const app = new Vue({
i18n,
el: '#app',
data() {
return {
i18n,
};
},
render: h => h(Main),
});
To avoid errors in our growing codebase, I'd like to incorporate the eslint plugin for i18n.
However, vuetify with vue-i18n expects keys to look like this inside the code:
<a #click="buttonAction">
{{ $vuetify.t('$vuetify.components.ActionBtn') }}
</a>
And the en.json (in a separate file) looks like this:
{
"components": {
"ActionBtn": "Click me!"
}
}
but the eslint plugin doesn't recognize that as matching. It wants the code to look like this:
<a #click="buttonAction">
{{ $vuetify.t('components.ActionBtn') }}
</a>
How can I restructure the way I load the json to ensure I can use i18n in vuetify with my linter?
{{ $vuetify.t('$vuetify.components.ActionBtn') }}
This is if you're only using vuetify's built-in internationalization. When combined with vue-i18n you should use $t('components.ActionBtn') along with having the required vuetify messages in your i18n config.
I am trying to implement v-i18n to my project with sfc method. I couldn't make it work. I will not make you confuse with my project, that's why just modified with adding 10-15 lines of code to official v-i18n sfc example.
This is very simply shows my question.
For those who prefer check this very tiny question project on github
https://github.com/berkansivri/V-i18n-Question
Component1.vue
<template>
<p>{{$t('lang')}}</p>
</template>
<i18n>
{
"en":{
"lang" : "English"
},
"es":{
"lang": "Espanol"
}
}
</i18n>
App.vue
<template>
<div id="app">
<label for="locale">locale</label>
<select v-model="locale">
<option>en</option>
<option>es</option>
</select>
<p>message: {{ $t('hello') }}</p>
<Comp1></Comp1>
</div>
</template>
<i18n>
{
"en": {
"hello": "hello"
},
"es": {
"hello": "hola"
}
}
</i18n>
<script>
import Comp1 from './components/component1'
export default {
components:{
Comp1
},
name: 'App',
data () { return { locale: 'en' } },
watch: {
locale (val) {
this.$i18n.locale = val
}
}
}
</script>
So, multiple <i18n>tag in multiple components. I just modified $i18n.locale from App.vue but it did not fire related i18n function $t('lang') on Component1, just modifies $t('hello') on itself.
How can I make it work?
using vue devtools u will find out that messages of $i18n in single file component is different from each other, so they are different objects.
u need to do is:
i18n.js
import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from '#/lang'
Vue.use(VueI18n)
const i18n = new VueI18n({
locale: 'cn',
fallbackLocale: 'en',
messages
})
export default i18n
App.vue
import i18n from "./i18n.js"
i18n.locale = "en"
This is intended behavior of Single file components. If you want to change all locales of all components you can use:
locale (val) {
// this.$i18n.locale = val
this.$root.$i18n.locale = val
}
See this issue.