Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions packages/vue-i18n-core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ export const I18nErrorCodes = {
CANNOT_SETUP_VUE_DEVTOOLS_PLUGIN: 30,
NOT_INSTALLED_WITH_PROVIDE: 31,
// unexpected error
UNEXPECTED_ERROR: 32
UNEXPECTED_ERROR: 32,
// duplicate `useI18n` calling
DUPLICATE_USE_I18N_CALLING: 33
} as const

type I18nErrorCodes = (typeof I18nErrorCodes)[keyof typeof I18nErrorCodes]
Expand All @@ -49,5 +51,7 @@ export const errorMessages: { [code: number]: string } = {
[I18nErrorCodes.INVALID_VALUE]: `Invalid value`,
[I18nErrorCodes.CANNOT_SETUP_VUE_DEVTOOLS_PLUGIN]: `Cannot setup vue-devtools plugin`,
[I18nErrorCodes.NOT_INSTALLED_WITH_PROVIDE]:
'Need to install with `provide` function'
'Need to install with `provide` function',
[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING]:
'Duplicate local-scope `useI18n` call detected. Call `useI18n` only once per component.'
}
4 changes: 4 additions & 0 deletions packages/vue-i18n-core/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,10 @@ export function useI18n<
setupLifeCycle(i18nInternal, instance, composer)

i18nInternal.__setInstance(instance, composer)
} else {
if (__DEV__ && scope === 'local') {
throw createI18nError(I18nErrorCodes.DUPLICATE_USE_I18N_CALLING)
}
}

return composer as unknown as Composer<
Expand Down
60 changes: 60 additions & 0 deletions packages/vue-i18n-core/test/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,66 @@ describe('useI18n', () => {
errorMessages[I18nErrorCodes.NOT_INSTALLED_WITH_PROVIDE]
)
})

test(errorMessages[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING], async () => {
const i18n = createI18n({
legacy: false,
locale: 'en',
fallbackLocale: ['en'],
messages: {
en: { hello: 'hello!' }
}
})

const useMyComposable = () => {
const count = ref(0)
const { t } = useI18n({
messages: {
en: {
there: 'hi there! {count}'
}
}
})
return { message: t('there', { count: count.value }) }
}

let error = ''
const App = defineComponent({
setup() {
let message: string = ''
let t: any // eslint-disable-line @typescript-eslint/no-explicit-any
try {
const i18n = useI18n({
messages: {
en: {
hi: 'hi!'
}
}
})
t = i18n.t
const ret = useMyComposable()
message = ret.message
} catch (e: any) {
error = e.message
}
return { t, message, error }
},
template: `
<h1>Root</h1>
<form>
<select v-model="locale">
<option value="en">en</option>
<option value="ja">ja</option>
</select>
</form>
<p>{{ t('hi') }}</p>
<p>{{ message }}</p>
<p>{{ error }}</p>
`
})
await mount(App, i18n as any) // eslint-disable-line @typescript-eslint/no-explicit-any
expect(error).toBe(errorMessages[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING])
})
})

test('slot reactivity', async () => {
Expand Down