vue-i18n
vue-i18n copied to clipboard
Custom Elements: Handle child component
Reporting a bug?
When calling useI18n from a component which is a children from a Custom Element, it throws :
Uncaught TypeError: Cannot read property '__VUE_I18N_SYMBOL__' of null
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 :
- Check recursively parent's property for isCE ?
- Call
injecteven if!isCEand check for its result ?
Expected behavior
Should not throw
Reproduction
NA
System Info
Binaries:
Node: 16.7.0 - /usr/bin/node
Yarn: 1.22.11 - /usr/bin/yarn
npm: 7.21.0 - /usr/bin/npm
Browsers:
Chromium: 92.0.4515.159
Firefox: 91.0.1
npmPackages:
vue: ^3.2.6 => 3.2.6
vue-i18n: ^9.2.0-beta.3 => 9.2.0-beta.3
Screenshot
No response
Additional context
No response
Validations
- [X] Read the Contributing Guidelines
- [X] Read the Documentation
- [X] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [X] Check that this is a concrete bug. For Q&A open a GitHub Discussion
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!