I am trying to setup a vue3 app with i18n localization. The localization is supposed to be located in json files. I added i18n via vue add i18n to my project. The questions asked during installation were all answered with the default value except the one with the legacy support (my answer: no). When i try to use a text from a json file, it will tell me in the console [intlify] Not found 'message' key in 'en' locale messages.
The local translations work just fine.
And i have no clue why it is not working with the translations provided in the JSON file.
Here is my code:
packages.json
{
"name": "optinity-frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"test:unit": "vue-cli-service test:unit",
"i18n:report": "vue-cli-service i18n:report --src \"./src/**/*.?(js|vue)\" --locales \"./src/locales/**/*.json\""
},
"dependencies": {
"#fortawesome/fontawesome-svg-core": "^1.2.35",
"#fortawesome/free-brands-svg-icons": "^5.15.3",
"#fortawesome/free-regular-svg-icons": "^5.15.3",
"#fortawesome/free-solid-svg-icons": "^5.15.3",
"#fortawesome/vue-fontawesome": "^3.0.0-4",
"#kyvg/vue3-notification": "^2.3.1",
"#microsoft/signalr": "^5.0.6",
"axios": "^0.21.1",
"babel-plugin-transform-decorators": "^6.24.1",
"bulma": "^0.9.2",
"core-js": "^3.6.5",
"vue": "^3.0.0",
"vue-class-component": "^8.0.0-0",
"vue-i18n": "^9.1.0",
"vue-router": "^4.0.0-0",
"vuex": "^4.0.0-0",
"vuex-module-decorators": "^1.0.1"
},
"devDependencies": {
"#intlify/vue-i18n-loader": "^2.1.0",
"#types/jest": "^24.0.19",
"#vue/cli-plugin-babel": "~4.5.0",
"#vue/cli-plugin-router": "~4.5.0",
"#vue/cli-plugin-typescript": "~4.5.0",
"#vue/cli-plugin-unit-jest": "~4.5.0",
"#vue/cli-plugin-vuex": "~4.5.0",
"#vue/cli-service": "~4.5.0",
"#vue/compiler-sfc": "^3.0.0",
"#vue/test-utils": "^2.0.0-0",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"typescript": "~4.1.5",
"vue-cli-plugin-i18n": "~2.1.1",
"vue-jest": "^5.0.0-0"
}
}
i18n.ts
import { createI18n, LocaleMessages, VueMessageType } from 'vue-i18n'
/**
* Load locale messages
*
* The loaded `JSON` locale messages is pre-compiled by `#intlify/vue-i18n-loader`, which is integrated into `vue-cli-plugin-i18n`.
* See: https://github.com/intlify/vue-i18n-loader#rocket-i18n-resource-pre-compilation
*/
function loadLocaleMessages(): LocaleMessages<VueMessageType> {
const locales = require.context('./locales', true, /[A-Za-z0-9-_,\s]+\.json$/i)
const messages: LocaleMessages<VueMessageType> = {}
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()
})
main.ts
import { createApp } from 'vue';
import App from './App.vue';
import router from './router';
import store from './store';
import i18n from './i18n'
const app = createApp(App).use(i18n).use(store).use(router)
app.mount('#app');
vue.config.js
module.exports = {
// ... your other options
transpileDependencies: [
'vuex-module-decorators'
],
pluginOptions: {
i18n: {
locale: 'en',
fallbackLocale: 'en',
localeDir: 'locales',
enableLegacy: false,
runtimeOnly: false,
compositionOnly: false,
fullInstall: true
}
}
}
en.json
{
"message": "hello i18n !!"
}
and finally
HelloI18n.vue
<template>
<p>{{ t('message') }}</p>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { useI18n } from 'vue-i18n'
export default defineComponent({
name: 'HelloI18n',
setup() {
const { t } = useI18n({
inheritLocale: true,
useScope: 'global'
})
// Something todo ..
return { t }
}
})
</script>
<i18n>
{
"en": {
"hello": "Hello i18n in SFC!"
}
}
</i18n>
If i switch the scope in my HelloI18n.vue to local i can use the transaltions provided in the <i18n> tag.
I already added a console log in my i18n.ts file in order to check if the file is being found, which is the case.
I have no clue why this is not working. Does anyone have any ideas or can point me in the right direction?
The main probles is that the function in i18n.ts doing loadLocalMessages is not getting properly the files from the locales folder.
function loadLocaleMessages (): LocaleMessages<VueMessageType> {
const locales = require.context(
'./locales',
true,
/[A-Za-z0-9-_,\s]+\.json$/i
)
const messages: LocaleMessages<VueMessageType> = {}
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).default
}
})
return messages
}
I have added locales(key).default with that you get the values that are getted from the files.
Related
I'm new to vuejs and have implemented this example:
Auth0 vuejs and api example
It works just fine, but I run into some issues when trying to merge the vuejs code to my own project.
When loading the page requiring authentication, I get this error:
index.vue?4db4:11 Uncaught (in promise) TypeError: Cannot destructure property 'getAccessTokenSilently' of 'Object(...)(...)' as it is undefined.
The code for my page, looks like this:
<script>
import Layout from "../../../layouts/main.vue";
import { getProtectedResource } from "#/services/message.service";
import { ref } from "vue";
import { useAuth0 } from "#auth0/auth0-vue";
const message = ref("");
const getMessage = async () => {
const { getAccessTokenSilently } = useAuth0();
const accessToken = await getAccessTokenSilently();
const { data, error } = await getProtectedResource(accessToken);
if (data) {
message.value = JSON.stringify(data, null, 2);
}
if (error) {
message.value = JSON.stringify(error, null, 2);
}
};
getMessage();
export default {
components: {
Layout
},
data() {
return {
};
},
methods: {
rightcolumn() {
if (document.querySelector('.layout-rightside-col').classList.contains('d-none'))
{
document.querySelector('.layout-rightside-col').classList.remove('d-none')
} else {
document.querySelector('.layout-rightside-col').classList.add('d-none')
}
}
}
};
</script>
<template>
<Layout>
<p id="page-description">
<span
>This page retrieves a <strong>protected message</strong> from an
external API.</span
>
<span
><strong
>Only authenticated users can access this page.</strong
></span
>
</p>
<CodeSnippet title="Protected Message" :code="message" />
</Layout>
</template>
I've tried the example from the documentation provided here enter link description here
<script>
import Layout from "../../../layouts/main.vue";
//import { getProtectedResource } from "#/services/message.service";
//import { ref } from "vue";
import { useAuth0 } from "#auth0/auth0-vue";
export default {
setup() {
const { loginWithRedirect } = useAuth0();
return {
login: () => {
loginWithRedirect();
}
};
},
components: {
Layout
},
data() {
return {
};
},
methods: {
rightcolumn() {
if (document.querySelector('.layout-rightside-col').classList.contains('d-none')) {
document.querySelector('.layout-rightside-col').classList.remove('d-none')
} else {
document.querySelector('.layout-rightside-col').classList.add('d-none')
}
}
}
}
</script>
But still receives this error:
index.vue?4db4:11 Uncaught (in promise) TypeError: Cannot destructure
property 'loginWithRedirect' of 'Object(...)(...)' as it is undefined.
at setup (index.vue?4db4:11:1)
I'm registrering the plugin this way in main:
createApp(App)
.use(store)
.use(router)
.use(VueApexCharts)
.use(BootstrapVue3)
.component(VueFeather.type, VueFeather)
.use(Maska)
.use(Particles)
.use(i18n)
.use(
createAuth0({
domain: 'xyz.auth0.com',
client_id: 'secret',
redirect_uri: 'http://localhost:4040/callback',
audience: 'https://audience',
})
)
.use(vClickOutside).mount('#app');
My package.json file:
{
"name": "vuejs",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"#auth0/auth0-vue": "^1.0.2",
"#ckeditor/ckeditor5-build-classic": "^32.0.0",
"#ckeditor/ckeditor5-vue": "^2.0.1",
"#fullcalendar/bootstrap": "^5.10.1",
"#fullcalendar/core": "^5.10.1",
"#fullcalendar/daygrid": "^5.10.1",
"#fullcalendar/interaction": "^5.10.1",
"#fullcalendar/list": "^5.10.1",
"#fullcalendar/timegrid": "^5.10.1",
"#fullcalendar/vue3": "^5.10.1",
"#j-t-mcc/vue3-chartjs": "^1.2.0",
"#popperjs/core": "^2.11.2",
"#simonwep/pickr": "^1.8.2",
"#vue-leaflet/vue-leaflet": "^0.6.1",
"#vueform/multiselect": "^2.3.1",
"#vueform/slider": "^2.0.8",
"#vueform/toggle": "^2.0.1",
"#vuelidate/core": "^2.0.0-alpha.34",
"#vuelidate/validators": "^2.0.0-alpha.26",
"#zhuowenli/vue-feather-icons": "^5.0.2",
"aos": "^2.3.4",
"apexcharts": "^3.33.0",
"axios": "^0.27.2",
"bootstrap": "^5.2.1",
"bootstrap-vue-3": "^0.3.3",
"chart.js": "^3.7.0",
"click-outside-vue3": "^4.0.1",
"core-js": "^3.6.5",
"echarts": "^5.3.0",
"feather-icons": "^4.28.0",
"firebase": "^9.6.6",
"highlight.js": "^11.4.0",
"leaflet": "^1.7.1",
"lottie-web": "^5.8.1",
"maska": "^1.5.0",
"moment": "^2.29.1",
"node-sass": "6.0.1",
"particles.vue3": "^1.40.2",
"prismjs": "^1.26.0",
"sass-loader": "^10.2.1",
"simplebar": "^5.3.6",
"simplebar-vue3": "^0.1.5",
"sweetalert2": "^11.4.32",
"swiper": "^6.8.4",
"vue": "3.2.36",
"vue-router": "^4.0.15",
"vue-draggable-next": "^2.1.1",
"vue-easy-lightbox": "^1.3.0",
"vue-feather": "^2.0.0",
"vue-flatpickr-component": "^9.0.5",
"vue-i18n": "^9.2.0-beta.15",
"vue-prismjs": "^1.2.0",
"vue3-apexcharts": "^1.4.1",
"vue3-count-to": "^1.1.2",
"vue3-echarts": "^1.0.4",
"vue3-google-map": "^0.8.3",
"vue3-quill": "^0.2.6",
"vuevectormap": "^1.0.8",
"vuex": "^4.0.2",
"yarn": "^1.22.17"
},
"devDependencies": {
"#vue/cli-plugin-babel": "~4.5.0",
"#vue/cli-plugin-eslint": "~4.5.0",
"#vue/cli-service": "~4.5.0",
"#vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Using #auth0/auth0-vue has a limitation. useAuth0 must be used in the setup hook. Read the documentation for info.
To add login to your application, use the loginWithRedirect function that is exposed on the return value of useAuth0, which you can access in your component's setup function.
<script>
import { ref } from "vue";
import { useAuth0 } from "#auth0/auth0-vue";
export default {
setup() {
const message = ref("");
const { getAccessTokenSilently } = useAuth0();
const getMessage = async () => {
const accessToken = await getAccessTokenSilently();
const { data, error } = await getProtectedResource(accessToken);
if (data) {
message.value = JSON.stringify(data, null, 2);
}
if (error) {
message.value = JSON.stringify(error, null, 2);
}
};
getMessage();
return {
message
}
},
...
}
</script>
<template>...</template>
I am trying to include highcharts-vue like this:
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import HighchartsVue from "highcharts-vue";
const app = createApp(App)
app.use(createPinia())
app.use(router)
app.use(HighchartsVue);
app.mount('#app')
But I'm getting the error:
Dynamic require of "highcharts" is not supported
package.json
{
"name": "mwc-web",
"version": "0.0.0",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview --port 5050"
},
"dependencies": {
"#popperjs/core": "^2.11.4",
"bootstrap": "^5.1.3",
"bootstrap-vue-3": "^0.1.9",
"highcharts": "^10.0.0",
"highcharts-vue": "^1.4.0",
"moment": "^2.29.1",
"pinia": "^2.0.11",
"vue": "^3.2.31",
"vue-router": "^4.0.12"
},
"devDependencies": {
"#vitejs/plugin-vue": "^2.2.2",
"#vue/cli-plugin-babel": "^5.0.4",
"sass": "^1.49.9",
"sass-loader": "^12.6.0",
"vite": "^2.8.4"
}
}
vite.config.js
import { fileURLToPath, URL } from 'url'
import { defineConfig } from 'vite'
import vue from '#vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
alias: {
'#': fileURLToPath(new URL('./src', import.meta.url))
}
},
optimizeDeps: {
exclude: ['highcharts'],
}
})
babel.config.js
module.exports = {
presets: [
'#vue/cli-plugin-babel/preset'
]
}
I think the cause of the issue is this config:
optimizeDeps: {
exclude: ['highcharts'], // ❌ don't do this
}
The solution would be to remove highcharts from optimizeDeps.exclude.
Also, you don't need a Babel config when using Vite. That might be another source of the problem.
demo
Here's a working example of vue3 + highcharts-vue: https://codesandbox.io/s/vue3-highcharts-example-k98kyz
Be sure to update to the latest versions, and to use babel or any other transpilers to work with ESM packages.
I am getting issues trying to get vue-svg-loader to work. I posted this in the issues part of the plugin, but I am not sure if it is, or just that I have implemented it wrong.
I am pretty new to Vue, so apologies if I am making a really stupid mistake when implementing it.
When I run vue --version it says I am on #vue/cli 4.5.9
This is the code inside of my component:
<template>
<div>
<SampleSvg />
</div>
</template>
<script>
import SampleSvg from "#/assets/sample.svg"
export default {
name: "HelloWorld",
components: {
SampleSvg
},
};
</script>
This is my vue.config.js file:
module.exports = {
chainWebpack: (config) => {
const svgRule = config.module.rule('svg');
svgRule.uses.clear();
svgRule
.use('babel-loader')
.loader('babel-loader')
.end()
.use('vue-svg-loader')
.loader('vue-svg-loader');
},
css: {
loaderOptions: {
sass: {
prependData:
`#import "#/assets/scss/globals/_variables.scss";
#import "#/assets/scss/globals/_variables-font-sizes.scss";
#import "#/assets/scss/globals/_colours.scss";
#import "#/assets/scss/globals/_mixins.scss";
`
},
},
},
};
This is my package.json file
{
"name": "frontend",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.6.5",
"postcss": "^8.3.6",
"vue": "^3.0.0",
"vue-router": "^4.0.0-0"
},
"devDependencies": {
"#vue/cli-plugin-babel": "~4.5.0",
"#vue/cli-plugin-eslint": "~4.5.0",
"#vue/cli-plugin-router": "~4.5.0",
"#vue/cli-service": "~4.5.0",
"#vue/compiler-sfc": "^3.0.0",
"#vue/eslint-config-prettier": "^6.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.3.1",
"eslint-plugin-vue": "^7.0.0",
"prettier": "^2.2.1",
"sass": "^1.26.5",
"sass-loader": "^8.0.2",
"style-resources-loader": "^1.4.1",
"vue-svg-loader": "^0.17.0-beta.2",
"vue-template-compiler": "^2.6.14"
}
}
The error I get is:
[Vue warn]: Component is missing template or render function.
at <SampleSvg>
at <HelloWorld onVnodeUnmounted=fn<onVnodeUnmounted> ref=Ref< null > >
at <RouterView>
at <App>
If I open up the sample.svg file, I have this code:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg">
<circle cx="50" cy="50" r="40" stroke="deeppink" stroke-width="2" fill="#ffe6ee" />
</svg>
I am pretty new to Vue, so apologies if this is down to how I have installed it.
My setup is as follows - but since I want to reference both internal and external SVGs (meaning that I want to be able to import an SVG as a Vue Component OR to be able to put it in the SRC of an IMG tag) I use the ?int modifier to signal when I want to receive a component. Without this modifier the import will be the image itself.
const svgRule = config.module.rule('svg');
svgRule.uses.clear();
svgRule
.oneOf('inline')
.resourceQuery(/int/) // internal SVGs must be in the form - import svgComponent from "#/images/example.svg?int" !!!!!
.use('babel-loader')
.loader('babel-loader')
.end()
.use('vue-svg-loader')
.loader('vue-svg-loader')
.options({
svgo:
{
plugins: [
{ inlineStyles: { onlyMatchedOnce: false } },
{ removeXMLNS: true },
{ removeViewBox: false },
{ removeDimensions: true },
]
}
})
.end()
.end()
.oneOf('external')
.use('url-loader')
.loader('url-loader')
.options({
limit: 4096,
fallback:
{
loader: 'file-loader',
options:
{
name: 'img/[name].[hash:8].[ext]',
}
}
});
I'm experimenting with the Composition API with Vue3. But there were some points I couldn't find. The same code did not work in two different projects.
What I want to do in my own project is to take the data through the API and use it according to what is required. In short, do the necessary get/post operations. I got this API from Vue's own example.
This is the first project code, package.json and error message
<template>
<div class="home">
<div v-for="datas in data" :key="datas.description">
{{ datas.description }}
</div>
</div>
</template>
<script lang="ts">
import { Options, Vue } from "vue-class-component";
import axios from "axios";
import { ref } from "vue";
#Options({
props: {
msg: String,
},
})
export default class HelloWorld extends Vue {
setup() {
let data = ref([]);
axios
.get("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => {
data.value = res.data.bpi;
});
}
}
</script>
{
"name": "api-project",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.6.5",
"vue": "^3.0.0",
"vue-class-component": "^8.0.0-0",
"vue-router": "^4.0.0-0"
},
"devDependencies": {
"#vue/cli-plugin-babel": "~4.5.0",
"#vue/cli-plugin-router": "~4.5.0",
"#vue/cli-plugin-typescript": "~4.5.0",
"#vue/cli-service": "~4.5.0",
"#vue/compiler-sfc": "^3.0.0",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"typescript": "~4.1.5"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Vue warn
This is the second project code, package.json, and data
<template>
<div v-for="datas in data" :key="datas.description">
{{ datas.description }}
</div>
</template>
<script lang="ts">
import axios from "axios";
import { ref } from "vue";
export default {
name: "HelloWorld",
setup() {
let data = ref([]);
axios.get("https://api.coindesk.com/v1/bpi/currentprice.json").then((res) => {
data.value = res.data.bpi;
});
return {
data,
};
},
}
</script>
{
"name": "test-api",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"axios": "^0.21.1",
"core-js": "^3.6.5",
"vue": "^3.0.0"
},
"devDependencies": {
"#vue/cli-plugin-babel": "~4.5.0",
"#vue/cli-plugin-eslint": "~4.5.0",
"#vue/cli-service": "~4.5.0",
"#vue/compiler-sfc": "^3.0.0",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-vue": "^7.0.0",
"node-sass": "^4.12.0",
"sass-loader": "^8.0.2",
"typescript": "~4.1.5"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
my data
Could there be an error in my Composition API usage? I've heard that in some videos, "then" is not used for the Composition API. But that's the only way I was able to pull the data from the API.
If my solution is wrong, what method should it be, I'm new at Vuejs can you help?
You need to return the variables from the setup function so that they can be accessed from within the template.
If setup returns an object, the properties on the object can be
accessed in the component's template, as well as the properties of the
props passed into setup:
setup() {
let data = ref([]);
axios
.get("https://api.coindesk.com/v1/bpi/currentprice.json")
.then((res) => {
data.value = res.data.bpi;
});
// return the data as an object
return {
data
}
}
Read more about this in the official vue doc
You can create api dir inside src folder and then inside api dir create a file api.ts and put this code
export async function callApi(endpoint :string, method :string){
return await fetch(endpoint,{
method:method,
headers: {
'Content-type': 'application/json; charset=UTF-8',
},
}).then(async response => {
const resData = await response.json()
if (!response.ok) {
// do something to determine request is not okay
resData.isSuccess = false
}
return resData
}).catch(error => {
console.log("callApi in api.ts err")
console.log(error)
throw error
})
}
Go to you component and use this code
<template>
<div v-for="(item,i) in data.records" v-bind:key="i">
{{ item.chartName}}
</div>
</template>
<script>
import {onMounted,reactive} from "vue"
import {callApi} from "#/api/api"
export default{
name:'MyComponent',
setup() {
const data = reactive({
records: [],
})
onMounted( async() =>{
getRecords()
})
const getRecords = async() => {
let resData = await callApi('https://api.coindesk.com/v1/bpi/currentprice.json', 'GET')
data.records = resData
}
return {
data,
}
}
}
</script>
enter code here
I can't seem to get the following example to work with vue3 and testing library.
https://github.com/testing-library/vue-testing-library/blob/main/src/tests/translations-vue-i18n.js
I've even tried to modify the example like so to get $t to be recognized by injecting messages into a mock but no luck.
Does anyone have an example that works with vue 3?
Here are the details ...
Translations.spec.js
import '#testing-library/jest-dom'
import {render, fireEvent} from '#testing-library/vue'
import Vuei18n from 'vue-i18n'
import Translations from '#/components/Translations'
const messages = {
en: {
Hello: 'Hello!',
message: {
hello: 'Hello!'
}
},
ja: {
Hello: 'こんにちは',
message: {
hello: 'こんにちは'
}
},
}
test('renders translations', async () => {
const {queryByText, getByText} = render(Translations, {
global: {
mocks: {
$t: (messages) => messages
}
}
}, vue => {
// Let's register and configure Vuei18n normally
vue.use(Vuei18n)
const i18n = new Vuei18n({
locale: 'ja',
fallbackLocale: 'ja',
messages,
})
// Notice how we return an object from the callback function. It will be
// merged as an additional option on the created Vue instance.
return {
i18n,
}
})
//expect(getByText('Hello!')).toBeInTheDocument()
//await fireEvent.click(getByText('Japanese'))
expect(getByText('こんにちは')).toBeInTheDocument()
//expect(queryByText('Hello!')).not.toBeInTheDocument()
})
Translations.vue
<template>
<div>
<h2>{{ $t("Hello") }}</h2>
<h2>{{ $t("message.hello") }}</h2>
<button #click="switchLocale('en')">English</button>
<button #click="switchLocale('ja')">Japanese</button>
</div>
</template>
<script>
export default {
name: 'Translations',
methods: {
switchLocale(locale) {
this.$i18n.locale = locale
},
},
}
</script>
package.json
{
"name": "mc",
"version": "0.1.0",
"private": true,
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint",
"test:unit": "vue-cli-service test:unit"
},
"dependencies": {
"#fortawesome/fontawesome-svg-core": "^1.2.35",
"#fortawesome/free-solid-svg-icons": "^5.15.3",
"#fortawesome/vue-fontawesome": "^3.0.0-4",
"#popperjs/core": "^2.9.2",
"bootstrap": "^5.0.2",
"core-js": "^3.6.5",
"es6-promise": "^4.2.8",
"vue": "^3.1.4",
"vue-hotjar": "^1.4.0",
"vue-i18n": "^9.1.6",
"vue-loader": "^16.2.0",
"vue-router": "^4.0.10",
"vuex": "^4.0.2"
},
"devDependencies": {
"#babel/core": "^7.14.8",
"#babel/preset-env": "^7.14.8",
"#testing-library/jest-dom": "^5.14.1",
"#testing-library/vue": "^6.4.2",
"#vue/cli-plugin-babel": "^4.5.13",
"#vue/cli-plugin-eslint": "^4.5.13",
"#vue/cli-plugin-router": "^4.5.13",
"#vue/cli-plugin-unit-jest": "^4.5.13",
"#vue/cli-plugin-vuex": "^4.5.13",
"#vue/cli-service": "^4.5.13",
"#vue/compiler-sfc": "^3.1.4",
"#vue/eslint-config-prettier": "^6.0.0",
"#vue/test-utils": "^2.0.0-rc.9",
"babel-eslint": "^10.1.0",
"eslint": "^6.7.2",
"eslint-plugin-prettier": "^3.4.0",
"eslint-plugin-vue": "^7.0.0",
"flush-promises": "^1.0.2",
"prettier": "^2.3.2",
"typescript": "^4.3.5",
"vue-jest": "^5.0.0-alpha.10"
},
"eslintConfig": {
"root": true,
"env": {
"node": true
},
"extends": [
"plugin:vue/vue3-essential",
"eslint:recommended"
],
"parserOptions": {
"parser": "babel-eslint"
},
"rules": {}
},
"browserslist": [
"> 1%",
"last 2 versions",
"not dead"
]
}
Error
FAIL tests/unit/Translations.spec.js
● renders translations
TestingLibraryElementError: Unable to find an element with the text: こんにちは. This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.
<body>
<div>
<div>
<h2>
Hello
</h2>
<h2>
message.hello
</h2>
<button>
English
</button>
<button>
Japanese
</button>
</div>
</div>
</body>
47 | //await fireEvent.click(getByText('Japanese'))
48 |
> 49 | expect(getByText('こんにちは')).toBeInTheDocument()
| ^
50 |
51 | //expect(queryByText('Hello!')).not.toBeInTheDocument()
52 | })
I had the same problem and solved it like this:
I am using the next version of #vue/test-utils and vue-jest ("#vue/test-utils": "^2.0.0-rc.16" + "vue-jest": "^5.0.0-alpha.10").
I created a file called jest.init.js (u can call it anything u like)
import { config } from '#vue/test-utils';
import translations from '#/locales/en';
config.global.mocks = {
$t: (msg) => translations[msg],
};
and then initiate it as setup file in jest.config.js
module.exports = {
...
setupFiles: [
'./tests/unit/jest.init.js',
],
...
};
This answer is for everyone stumbling across that question when using Composition API where there's no global $t to mock.
I've solved it by exporting a function createConfiguredI18n in src/plugins/i18n.ts:
import { createI18n, I18nOptions } from 'vue-i18n'
import deDE from '#/locales/de-DE.json'
import enUS from '#/locales/en-US.json'
// Type-define 'de-DE' as the master schema for the resource
type MessageSchema = typeof deDE
export function createConfiguredI18n(locale: string, fallbackLocale: string) {
return createI18n<I18nOptions, [MessageSchema], 'de-DE' | 'en-US'>({
locale: locale || 'en-US',
fallbackLocale: fallbackLocale || 'en-US',
messages: {
'de-DE': deDE,
'en-US': enUS,
},
})
}
export const i18n = createConfiguredI18n('de-DE', 'en-US')
Then in the unit test you can do the following to initialize vue-i18n with your translations:
import {flushPromises, mount, VueWrapper} from '#vue/test-utils'
import {nextTick} from 'vue'
import {createConfiguredI18n} from '#/plugins/i18n'
...
describe('SubjectUnderTest', () => {
it('should display translation "FooBar"', async () => {
const locale = 'de-DE'
const fallbackLocale = 'en-US'
const wrapper = await createWrapper({locale, fallbackLocale})
...
}
async function createWrapper(options: {
locale: string
fallbackLocale: string
}): Promise<VueWrapper> {
const i18n = createConfiguredI18n(options.locale, options.fallbackLocale)
const wrapper = mount(sut, {
global: {
plugins: [i18n],
},
})
await nextTick()
await flushPromises()
return wrapper
}
}
If you don't want the translations but instead mock them and check for the keys only, you can do the following in your unit test instead:
import {flushPromises, mount, VueWrapper} from '#vue/test-utils'
import {nextTick} from 'vue'
import {i18n} from '#/plugins/i18n'
...
i18n.global.t = (key) => key
describe('SubjectUnderTest', () => {
it('should display translation for key "foo.bar"', async () => {
const wrapper = await createWrapper()
...
}
async function createWrapper(): Promise<VueWrapper> {
const wrapper = mount(sut, {
global: {
plugins: [i18n],
},
})
await nextTick()
await flushPromises()
return wrapper
}
}