Skip to content

Commit 7c9742b

Browse files
authored
fix: change warning from error for mutiple useI18n local scope calling (#2235)
* fix: change warning from error for mutiple useI18n local scope calling * docs: add scope and multiple useI18n calling
1 parent 61a6688 commit 7c9742b

File tree

5 files changed

+123
-27
lines changed

5 files changed

+123
-27
lines changed

docs/guide/advanced/composition.md

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -375,6 +375,100 @@ const { t, d, n, tm, locale } = useI18n({
375375
// Something to do here ...
376376
```
377377

378+
### Implicit `useScope` Resolution
379+
380+
When using `useI18n`, the `useScope` option has implicit resolution behavior that you should be aware of:
381+
382+
:::info Default Behavior
383+
If you don't explicitly specify `useScope`, Vue I18n will implicitly determine the scope based on whether the component has an i18n block:
384+
- **With i18n block**: Defaults to `local` scope
385+
- **Without i18n block**: Defaults to `global` scope
386+
:::
387+
388+
```js
389+
// In a component WITH i18n block
390+
391+
// This implicitly uses local scope
392+
const { t } = useI18n() // same as useI18n({ useScope: 'local' })
393+
394+
395+
// In a component WITHOUT i18n block (e.g., composables, stores)
396+
397+
// This implicitly uses global scope
398+
const { t } = useI18n() // same as useI18n({ useScope: 'global' })
399+
```
400+
401+
This explicit approach prevents unexpected behavior and makes your code more maintainable.
402+
403+
### Avoiding Multiple useI18n Calls
404+
405+
:::warning IMPORTANT
406+
**Do not call `useI18n` with local scope multiple times within the same component.** When you call `useI18n` with local scope more than once in the same component, it will not work properly and Vue I18n will emit a warning.
407+
:::
408+
409+
#### Bad: Multiple calls to useI18n with local scope
410+
411+
```js
412+
export default {
413+
setup() {
414+
// First call - creates a new Composer instance
415+
const { t } = useI18n({
416+
locale: 'en',
417+
messages: {
418+
en: { hello: 'Hello' },
419+
ja: { hello: 'こんにちは' }
420+
}
421+
})
422+
423+
// Second call - creates another Composer instance (triggers warning)
424+
const { locale } = useI18n({
425+
locale: 'en',
426+
messages: {
427+
en: { world: 'World' },
428+
ja: { world: '世界' }
429+
}
430+
})
431+
432+
// These instances are not synchronized!
433+
return { t, locale }
434+
}
435+
}
436+
```
437+
438+
#### Good: Single call to useI18n
439+
440+
```js
441+
export default {
442+
setup() {
443+
// Destructure all needed properties from a single call
444+
const { t, locale, tm, d, n } = useI18n({
445+
locale: 'en',
446+
messages: {
447+
en: {
448+
hello: 'Hello',
449+
world: 'World'
450+
},
451+
ja: {
452+
hello: 'こんにちは',
453+
world: '世界'
454+
}
455+
}
456+
})
457+
458+
return { t, locale }
459+
}
460+
}
461+
```
462+
463+
When you violate this rule, you'll see the following warning in development mode:
464+
465+
```
466+
[Vue I18n warn]: Duplicate `useI18n` calling by local scope. Please don't call it on local scope, due to it does not work properly in component.
467+
```
468+
469+
If you need to use i18n features in multiple places within your component, destructure all the needed properties from a single `useI18n` call or store the returned object and access its properties as needed.
470+
471+
378472
### Locale messages
379473

380474
If you use i18n custom blocks in SFC as i18n resource of locale messages, it will be merged with the locale messages specified by the `messages` option of `useI18n`.

packages/vue-i18n-core/src/errors.ts

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,7 @@ export const I18nErrorCodes = {
2626
// not compatible legacy vue-i18n constructor
2727
NOT_COMPATIBLE_LEGACY_VUE_I18N: 33,
2828
// Not available Compostion API in Legacy API mode. Please make sure that the legacy API mode is working properly
29-
NOT_AVAILABLE_COMPOSITION_IN_LEGACY: 34,
30-
// duplicate `useI18n` calling
31-
DUPLICATE_USE_I18N_CALLING: 35
29+
NOT_AVAILABLE_COMPOSITION_IN_LEGACY: 34
3230
} as const
3331

3432
type I18nErrorCodes = (typeof I18nErrorCodes)[keyof typeof I18nErrorCodes]
@@ -59,7 +57,5 @@ export const errorMessages: { [code: number]: string } = {
5957
[I18nErrorCodes.NOT_COMPATIBLE_LEGACY_VUE_I18N]:
6058
'Not compatible legacy VueI18n.',
6159
[I18nErrorCodes.NOT_AVAILABLE_COMPOSITION_IN_LEGACY]:
62-
'Not available Compostion API in Legacy API mode. Please make sure that the legacy API mode is working properly',
63-
[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING]:
64-
"Duplicate `useI18n` calling by local scope. Please don't call it on local scope"
60+
'Not available Compostion API in Legacy API mode. Please make sure that the legacy API mode is working properly'
6561
}

packages/vue-i18n-core/src/i18n.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -774,7 +774,7 @@ export function useI18n<
774774
i18nInternal.__setInstance(instance, composer)
775775
} else {
776776
if (__DEV__ && scope === 'local') {
777-
throw createI18nError(I18nErrorCodes.DUPLICATE_USE_I18N_CALLING)
777+
warn(getWarnMessage(I18nWarnCodes.DUPLICATE_USE_I18N_CALLING))
778778
}
779779
}
780780

packages/vue-i18n-core/src/warnings.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ export const I18nWarnCodes = {
1212
/**
1313
* @deprecated will be removed at vue-i18n v12
1414
*/
15-
DEPRECATE_TRANSLATE_CUSTOME_DIRECTIVE: 12
15+
DEPRECATE_TRANSLATE_CUSTOME_DIRECTIVE: 12,
16+
// duplicate `useI18n` calling
17+
DUPLICATE_USE_I18N_CALLING: 13
1618
} as const
1719

1820
type I18nWarnCodes = (typeof I18nWarnCodes)[keyof typeof I18nWarnCodes]
@@ -28,7 +30,9 @@ export const warnMessages: { [code: number]: string } = {
2830
/**
2931
* @deprecated will be removed at vue-i18n v12
3032
*/
31-
[I18nWarnCodes.DEPRECATE_TRANSLATE_CUSTOME_DIRECTIVE]: `'v-t' has been deprecated in v11. Use translate APIs ('t' or '$t') instead.`
33+
[I18nWarnCodes.DEPRECATE_TRANSLATE_CUSTOME_DIRECTIVE]: `'v-t' has been deprecated in v11. Use translate APIs ('t' or '$t') instead.`,
34+
[I18nWarnCodes.DUPLICATE_USE_I18N_CALLING]:
35+
"Duplicate `useI18n` calling by local scope. Please don't call it on local scope, due to it does not work properly in component."
3236
}
3337

3438
export function getWarnMessage(

packages/vue-i18n-core/test/i18n.test.ts

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import {
3535
import { Composer } from '../src/composer'
3636
import { errorMessages, I18nErrorCodes } from '../src/errors'
3737
import { createI18n, useI18n } from '../src/i18n'
38+
import { I18nWarnCodes, warnMessages } from '../src/warnings'
3839
import { pluralRules as _pluralRules, mount, randStr } from './helper'
3940

4041
import type { IntlifyDevToolsEmitterHooks } from '@intlify/devtools-types'
@@ -623,7 +624,11 @@ describe('useI18n', () => {
623624
)
624625
})
625626

626-
test(errorMessages[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING], async () => {
627+
test(warnMessages[I18nWarnCodes.DUPLICATE_USE_I18N_CALLING], async () => {
628+
const mockWarn = vi.spyOn(shared, 'warn')
629+
// eslint-disable-next-line @typescript-eslint/no-empty-function
630+
mockWarn.mockImplementation(() => {})
631+
627632
const i18n = createI18n<false>({
628633
legacy: false,
629634
locale: 'en',
@@ -645,26 +650,22 @@ describe('useI18n', () => {
645650
return { message: t('there', { count: count.value }) }
646651
}
647652

648-
let error = ''
649653
const App = defineComponent({
650654
setup() {
651655
let message: string = ''
652656
let t: any // eslint-disable-line @typescript-eslint/no-explicit-any
653-
try {
654-
const i18n = useI18n({
655-
messages: {
656-
en: {
657-
hi: 'hi!'
658-
}
657+
const i18n = useI18n({
658+
messages: {
659+
en: {
660+
hi: 'hi!'
659661
}
660-
})
661-
t = i18n.t
662-
const ret = useMyComposable()
663-
message = ret.message
664-
} catch (e: any) {
665-
error = e.message
666-
}
667-
return { t, message, error }
662+
}
663+
})
664+
t = i18n.t
665+
const ret = useMyComposable()
666+
useMyComposable()
667+
message = ret.message
668+
return { t, message }
668669
},
669670
template: `
670671
<h1>Root</h1>
@@ -676,11 +677,12 @@ describe('useI18n', () => {
676677
</form>
677678
<p>{{ t('hi') }}</p>
678679
<p>{{ message }}</p>
679-
<p>{{ error }}</p>
680680
`
681681
})
682682
await mount(App, i18n as any) // eslint-disable-line @typescript-eslint/no-explicit-any
683-
expect(error).toBe(errorMessages[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING])
683+
expect(mockWarn.mock.calls[0][0]).toBe(
684+
warnMessages[I18nWarnCodes.DUPLICATE_USE_I18N_CALLING]
685+
)
684686
})
685687
})
686688

0 commit comments

Comments
 (0)