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
94 changes: 94 additions & 0 deletions docs/guide/advanced/composition.md
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,100 @@ const { t, d, n, tm, locale } = useI18n({
// Something to do here ...
```

### Implicit `useScope` Resolution

When using `useI18n`, the `useScope` option has implicit resolution behavior that you should be aware of:

:::info Default Behavior
If you don't explicitly specify `useScope`, Vue I18n will implicitly determine the scope based on whether the component has an i18n block:
- **With i18n block**: Defaults to `local` scope
- **Without i18n block**: Defaults to `global` scope
:::

```js
// In a component WITH i18n block

// This implicitly uses local scope
const { t } = useI18n() // same as useI18n({ useScope: 'local' })


// In a component WITHOUT i18n block (e.g., composables, stores)

// This implicitly uses global scope
const { t } = useI18n() // same as useI18n({ useScope: 'global' })
```

This explicit approach prevents unexpected behavior and makes your code more maintainable.

### Avoiding Multiple useI18n Calls

:::warning IMPORTANT
**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.
:::

#### Bad: Multiple calls to useI18n with local scope

```js
export default {
setup() {
// First call - creates a new Composer instance
const { t } = useI18n({
locale: 'en',
messages: {
en: { hello: 'Hello' },
ja: { hello: 'こんにちは' }
}
})

// Second call - creates another Composer instance (triggers warning)
const { locale } = useI18n({
locale: 'en',
messages: {
en: { world: 'World' },
ja: { world: '世界' }
}
})

// These instances are not synchronized!
return { t, locale }
}
}
```

#### Good: Single call to useI18n

```js
export default {
setup() {
// Destructure all needed properties from a single call
const { t, locale, tm, d, n } = useI18n({
locale: 'en',
messages: {
en: {
hello: 'Hello',
world: 'World'
},
ja: {
hello: 'こんにちは',
world: '世界'
}
}
})

return { t, locale }
}
}
```

When you violate this rule, you'll see the following warning in development mode:

```
[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.
```

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.


### Locale messages

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`.
Expand Down
8 changes: 2 additions & 6 deletions packages/vue-i18n-core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ export const I18nErrorCodes = {
// not compatible legacy vue-i18n constructor
NOT_COMPATIBLE_LEGACY_VUE_I18N: 33,
// Not available Compostion API in Legacy API mode. Please make sure that the legacy API mode is working properly
NOT_AVAILABLE_COMPOSITION_IN_LEGACY: 34,
// duplicate `useI18n` calling
DUPLICATE_USE_I18N_CALLING: 35
NOT_AVAILABLE_COMPOSITION_IN_LEGACY: 34
} as const

type I18nErrorCodes = (typeof I18nErrorCodes)[keyof typeof I18nErrorCodes]
Expand Down Expand Up @@ -59,7 +57,5 @@ export const errorMessages: { [code: number]: string } = {
[I18nErrorCodes.NOT_COMPATIBLE_LEGACY_VUE_I18N]:
'Not compatible legacy VueI18n.',
[I18nErrorCodes.NOT_AVAILABLE_COMPOSITION_IN_LEGACY]:
'Not available Compostion API in Legacy API mode. Please make sure that the legacy API mode is working properly',
[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING]:
"Duplicate `useI18n` calling by local scope. Please don't call it on local scope"
'Not available Compostion API in Legacy API mode. Please make sure that the legacy API mode is working properly'
}
2 changes: 1 addition & 1 deletion packages/vue-i18n-core/src/i18n.ts
Original file line number Diff line number Diff line change
Expand Up @@ -774,7 +774,7 @@ export function useI18n<
i18nInternal.__setInstance(instance, composer)
} else {
if (__DEV__ && scope === 'local') {
throw createI18nError(I18nErrorCodes.DUPLICATE_USE_I18N_CALLING)
warn(getWarnMessage(I18nWarnCodes.DUPLICATE_USE_I18N_CALLING))
}
}

Expand Down
8 changes: 6 additions & 2 deletions packages/vue-i18n-core/src/warnings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export const I18nWarnCodes = {
/**
* @deprecated will be removed at vue-i18n v12
*/
DEPRECATE_TRANSLATE_CUSTOME_DIRECTIVE: 12
DEPRECATE_TRANSLATE_CUSTOME_DIRECTIVE: 12,
// duplicate `useI18n` calling
DUPLICATE_USE_I18N_CALLING: 13
} as const

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

export function getWarnMessage(
Expand Down
38 changes: 20 additions & 18 deletions packages/vue-i18n-core/test/i18n.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
import { Composer } from '../src/composer'
import { errorMessages, I18nErrorCodes } from '../src/errors'
import { createI18n, useI18n } from '../src/i18n'
import { I18nWarnCodes, warnMessages } from '../src/warnings'
import { pluralRules as _pluralRules, mount, randStr } from './helper'

import type { IntlifyDevToolsEmitterHooks } from '@intlify/devtools-types'
Expand Down Expand Up @@ -623,7 +624,11 @@ describe('useI18n', () => {
)
})

test(errorMessages[I18nErrorCodes.DUPLICATE_USE_I18N_CALLING], async () => {
test(warnMessages[I18nWarnCodes.DUPLICATE_USE_I18N_CALLING], async () => {
const mockWarn = vi.spyOn(shared, 'warn')
// eslint-disable-next-line @typescript-eslint/no-empty-function
mockWarn.mockImplementation(() => {})

const i18n = createI18n<false>({
legacy: false,
locale: 'en',
Expand All @@ -645,26 +650,22 @@ describe('useI18n', () => {
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!'
}
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 }
}
})
t = i18n.t
const ret = useMyComposable()
useMyComposable()
message = ret.message
return { t, message }
},
template: `
<h1>Root</h1>
Expand All @@ -676,11 +677,12 @@ describe('useI18n', () => {
</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])
expect(mockWarn.mock.calls[0][0]).toBe(
warnMessages[I18nWarnCodes.DUPLICATE_USE_I18N_CALLING]
)
})
})

Expand Down
Loading