Open gnuletik opened 2 years ago
The same error occurs when using async custom element with :
customElements.define(
'my-element',
defineCustomElement(defineAsyncComponent(() => import('MyComponent.vue')))
)
(but it makes sense because MyComponent
is a child of the async component)
Thank you for feedback!
Unfortunately, this is a limitation of Vue Provide / Inject. :disappointed: https://vue-i18n.intlify.dev/guide/advanced/wc.html#limitations
Vue docs says:
note that this works only between custom elements
https://v3.vuejs.org/guide/web-components.html#definecustomelement
This means that if you use a Vue component from a custom element that is a web component, the inject will not work.
Thanks for your answer ! I missed that important info !
I created an issue in the vue-next repo : https://github.com/vuejs/vue-next/issues/4476
And will use a custom implementation of useI18n
:
import { getCurrentInstance, InjectionKey } from 'vue'
import { I18nInjectionKey } from 'vue-i18n'
export function deepInject<T> (key: InjectionKey<T> | string): T | undefined {
let inst = getCurrentInstance()
if (inst === null) {
throw new Error('getCurrentInstance returned null')
}
inst = inst.parent
if (inst === null) {
return
}
while (inst !== null) {
// @ts-expect-error
if (key in inst.provides) {
// @ts-expect-error
return inst.provides[key]
}
inst = inst.parent
}
}
interface Options {
messages?: Record<string, Record<string, string>>
}
// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
export function useI18n (options?: Options) {
const instance = deepInject(I18nInjectionKey)
if (instance === undefined) {
throw new Error('i18n not found in context')
}
const { global } = instance
// merge locale messages
const messages = options?.messages ?? {}
const locales = Object.keys(messages)
if (locales.length > 0) {
locales.forEach(locale => {
global.mergeLocaleMessage(locale, messages[locale])
})
}
return global
}
It does not cover all use-cases of the original implementation but it covers my use-case.
Thanks!
I think the provide/inject limitation does not effect this. I tried providing the i18n instance from a vue web component and than inject this in a child vue component and it worked fine.
PluginProvider.ce.vue
<template>
<Suspense>
<slot />
</Suspense>
</template>
<script lang="ts">
import { defineComponent, provide } from 'vue';
import { createI18n, I18nInjectionKey } from 'vue-i18n';
import i18nConfig from '~/i18n.config';
const i18n = createI18n<false>(i18nConfig.apply());
export default defineComponent({
setup(props) {
provide(I18nInjectionKey, i18n);
return {};
},
});
</script>
CustomElement.ce.vue
<template>
<div>
{{ t('login') }}
<ChildElement />
</div>
</template>
<script setup lang="ts">
const { t, locale } = useI18n({
useScope: 'local',
messages: {
cs: {
login: 'Přihlásit',
},
en: {
login: 'Login',
},
},
});
</script>
ChildElement.vue
<template>
<div>
ChildElement
</div>
</template>
<script lang="ts" setup>
import { I18nInjectionKey } from 'vue-i18n';
console.log(inject(I18nInjectionKey)); // This properly injects
</script>
We also run into this issue Please checkout this implementation which just create a wrapper component which doesn't need to be mounted on the application but can be mounted on any vue component which will be converted to web component
It would be nice to have this feature inside native i18n So it can be used to create web components without App context
That's interesting! I'll check about it. :)
Reporting a bug?
When calling
useI18n
from a component which is a children from a Custom Element, it throws :here : https://github.com/intlify/vue-i18n-next/blob/24b6d60a711ba591b1abdae937930c1615c98d81/packages/vue-i18n-core/src/i18n.ts#L604
This is because the child components do not have
instance.isCE
.Would it be possible to :
inject
even if!isCE
and check for its result ?Expected behavior
Should not throw
Reproduction
NA
System Info
Screenshot
No response
Additional context
No response
Validations