vue-i18n
vue-i18n copied to clipboard
No translations when vue-i18n used inside library
Related to https://github.com/kazupon/vue-i18n/issues/505 and https://github.com/kazupon/vue-i18n/issues/670
Context
I'm creating a package to share some vue components to my app. Both the package and the app are in vuejs and use vue-i18n to handle translations.
I build the package with the --target option.
Issue
When working on the package as a stand-alone (ie. local mode or building the package without the --target option), all translations displays correctly.
When building the package and using it inside the parent app, the translations does not show. The translation key is visible instead.
Code extract
The package (my-package)
I'm using the following command to build the package:
$vue-cli-service build --target lib --name my-package ./src/all-components.js
// src/main.js (used in dev mode / stand-alone version of the package)
import Vue from 'vue';
import App from './App.vue';
import { i18n, loadLanguage } from './plugin/i18n';
new Vue({
i18n,
beforeCreate() {
loadLanguage('fr');
},
render: (h) => h(App),
}).$mount('#app');
// src/all-components.js (used to build and share the package)
import MySharedComponent1 from '@/components/my-shared-component-1.vue';
import MySharedComponent2 from '@/components/my-shared-component-2.vue';
export default { MySharedComponent1, MySharedComponent2 };
// src/components/my-shared-component-1.vue
<template name="my-shared-component-1">
<main>
<h1 v-t="'article.title'" />
<p>
{{ $t('article.title') }}
<span v-text="$tc('article.comments', count, { x: count })" />
</p>
</main>
</template>
<script>
import { loadLanguage } from '@/plugin/i18n';
export default {
name: 'MySharedComponent1',
props: {
language: {
type: String,
default: 'fr',
},
},
data() {
return {
count: 4,
};
},
created() {
loadLanguage(this.language);
},
};
</script>
// src/plugins/i18n.js
import Vue from 'vue';
import VueI18n from 'vue-i18n';
import de from '@/locales/de.json';
import en from '@/locales/en.json';
import fr from '@/locales/fr.json';
const languages = { de, en, fr };
Vue.use(VueI18n);
const i18n = new VueI18n({
locale: 'fr',
fallbackLocale: 'fr',
messages: { fr: languages.fr },
silentTranslationWarn: process.env.NODE_ENV === 'production',
});
function loadLanguage(lang) {
if (i18n.locale === lang) {
return;
}
const currentLang = languages[lang] ? lang : 'fr';
i18n.setLocaleMessage(currentLang, languages[currentLang]);
i18n.locale = currentLang;
document.querySelector('html').setAttribute('lang', currentLang);
}
export {
i18n,
loadLanguage,
};
The app (my-app)
// src/pages/home.vue
<i18n>
{
"fr": {
"title": "Accueil"
},
"de": {
"title": "Startseite"
},
"en": {
"title": "Homepage"
}
}
</i18n>
<template>
<div>
<my-shared-component-1 language="en" />
...
</div>
</template>
<script>
import { mapGetters } from 'vuex';
import { MySharedComponent1 } from '@namespace/my-package';
export default {
name: 'Home',
components: {
MySharedComponent1,
},
head() {
return {
title: this.$t('title'),
};
},
};
</script>
So, I found something interesting.
In the generated package bundle, it seems that there is some conflicts between the vue-i18n of the package and the vue-i18n of the parent app.
Extract of the package @namespace/my-package/dist/my-package.common.js
:
var i18n = new vue_i18n_esm({
locale: 'fr',
fallbackLocale: 'fr',
messages: {
fr: langages.fr
},
silentTranslationWarn: "production" === 'production'
});
// ...
Vue.prototype.$t = function (key) {
var values = [], len = arguments.length - 1;
while ( len-- > 0 ) values[ len ] = arguments[ len + 1 ];
var i18n = this.$i18n; // Try to use the parent $i18n
return i18n._t.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this ].concat( values ))
};
Vue.prototype.$tc = function (key, choice) {
var values = [], len = arguments.length - 2;
while ( len-- > 0 ) values[ len ] = arguments[ len + 2 ];
var i18n = this.$i18n; // here too
return i18n._tc.apply(i18n, [ key, i18n.locale, i18n._getMessages(), this, choice ].concat( values ))
};
Vue.prototype.$te = function (key, locale) {
var i18n = this.$i18n; // here again
return i18n._te(key, i18n.locale, i18n._getMessages(), locale)
};
When I comment the indicated lines, the translations of my-package appear. However, the translations of my-app are lost instead.
There are many more similar cases in the generated package. It feels that the scope of the package's vue-i18n is not properly closed.
@saawsan I have exactly similar problem, and I think support of library use-case is very important think.
All the conflict happens because of Vue.use(VueI18n)
, but without it nothing will work, except you do some monkey-patching.
@kazupon, any chances to land support of localization for libraries? What do you think about it, in general? I don't want to use second translation library, and I love vue-i18n, but I need this feature too.
In your Shared Library project, You need to external vue-i18n with vue.config.js
.
e.g. vue.config.js configration:
module.exports = {
// ...
configureWebpack: config => {
config.externals = ['vue-i18n']
}
}
Hello @kazupon, thank you for quick reply. Can you explain how externalizing of vue-i18n can help for this case? I don’t want to store my library localization files in my application side (where I will actually load vue-i18n).
UPD: @kazupon, I tried your example and it doesn't work for me. And as I understand how webpack works, it will not help to resolve current issue :(
I can use another library, but I like your, and would be happy to use it in library too. Thank you for working on it. I just need little help to make it work with a library, in other words, make instance of VueI18n
work without Vue.use
. I studied it's code, and as I understand, it shouldn't be difficult. But before I do PR, I would like to know your opinion, will you consider accepting such change?
I ran into the same problem though fixed this by specifying vue-i18n
as a peerDependency
.
This will put the responsibility to supply the vue-i18n
instance with the application package.
Though I haven't tried, adding vue-i18n
too externals
as @kazupon mentioned should also do the trick in case you have listed vue-i18n
as a "regular" npm dependency
.
Both of these will make sure you don't bundle vue-i18n
with the library bundle
I also get a problem when importing my lib with vue-i18n in it I tried adding vue-i18n to: transpileDependencies, configureWebpack as @kazupon offered, peerDependencies in package.json file My parent app still throws an error saying [Vue warn]: Error in render: "TypeError: _vm.$t is not a function"
UPD: So, after I imported i18n from i18n.js file that creates after vue-i18n installation instead of from vue-i18n itself my console error went from [Vue warn]: Error in render: "TypeError: _vm.$t is not a function" to [Vue warn]: Error in render: "TypeError: Cannot read property '_t' of undefined"
Please see https://github.com/kazupon/vue-i18n/issues/505#issuecomment-666750256 for a related solution
I got this to work by using component based localization and adding vue-18n to externals
.
I got this to work by using component based localization and adding vue-18n to
externals
.
@mkampmey hi, could you link some examples, please?
@TorusHelm Sure! So I have a single file component which looks somehow like this:
<template>
<div></div>
</template>
<script>
export default {
name: 'MyComponent',
i18n: {
messages: {
en: {
"foo": "bar"
}
}
},
data() {
return {}
},
...
}
</script>
And I am using webpack externals to exclude vue-i18n from the bundle. In my case I am using webpack-node-externals
to exclude everything (like this: config.externals(nodeExternals())
), but just excluding vue-i18n should also work: config.externals(['vue-i18n'])
. And thats everything. In the bundle where I am using this library I am just importing the component and then translation is working.