Repository: intlify/vue-i18n-next
Pull request #1839 (2024-05-06 09:05:09)

feat!: deprecate `tc` and `$tc` for Legacy API mode
## deprecated feature

The following APIs are deprecated in v10:

- `tc(key: Key | ResourceKeys): TranslateResult;`
- `tc(key: Key | ResourceKeys, locale: Locales | Locale): TranslateResult;`
- `tc(key: Key | ResourceKeys, list: unknown[]): TranslateResult;`
- `tc(key: Key | ResourceKeys, named: Record<string, unknown>): TranslateResult;`
- `tc(key: Key | ResourceKeys, choice: number): TranslateResult;`
- `tc(key: Key | ResourceKeys, choice: number, locale: Locales | Locale): TranslateResult;`
- `tc(key: Key | ResourceKeys, choice: number, list: unknown[]): TranslateResult;`
- `tc(key: Key | ResourceKeys, choice: number, named: Record<string, unknown>): TranslateResult;`
- `$tc(key: Key): TranslateResult;`
- `$tc(key: Key, locale: Locale): TranslateResult;`
- `$tc(key: Key, list: unknown[]): TranslateResult;`
- `$tc(key: Key, named: Record<string, unknown>): TranslateResult;`
- `$tc(key: Key, choice: number): TranslateResult;`
- `$tc(key: Key, choice: number, locale: Locale): TranslateResult;`
- `$tc(key: Key, choice: number, list: unknown[]): TranslateResult;`
- `$tc(key: Key, choice: number, named: Record<string, unknown>): TranslateResult;`

**Reason**: Legacy API mode has `t` and `$t` support plural interfaces, so they can be replaced.

In v10, `tc` and `$tc` still exist to give benefit migration. These will be dropped completely in v11.

If you will use them, Vue I18n will output the console warning in your application.

cloudflare-pages[bot]: ## Deploying vue-i18n-next with &nbsp;<a href="https://pages.dev"><img alt="Cloudflare Pages" src="https://user-images.githubusercontent.com/23264/106598434-9e719e00-654f-11eb-9e59-6167043cfa01.png" width="16"></a> &nbsp;Cloudflare Pages

<table><tr><td><strong>Latest commit:</strong> </td><td>
<code>db38d30</code>
</td></tr>
<tr><td><strong>Status:</strong></td><td>&nbsp;✅&nbsp; Deploy successful!</td></tr>
<tr><td><strong>Preview URL:</strong></td><td>
<a href='https://3156503a.vue-i18n-next.pages.dev'>https://3156503a.vue-i18n-next.pages.dev</a>
</td></tr>
<tr><td><strong>Branch Preview URL:</strong></td><td>
<a href='https://fix-deprecate-tc1.vue-i18n-next.pages.dev'>https://fix-deprecate-tc1.vue-i18n-next.pages.dev</a>
</td></tr>
</table>

[View logs](https://dash.cloudflare.com/?to=/:account/pages/view/vue-i18n-next/3156503a-0526-4f44-a0a6-b9ee57999a8f)


Patch:
diff --git a/docs/api/injection.md b/docs/api/injection.md
index 4f629b59..ab635b53 100644
--- a/docs/api/injection.md
+++ b/docs/api/injection.md
@@ -599,6 +599,10 @@ Translation message
 
 ### $tc(key)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
@@ -632,6 +636,10 @@ Translation message that is pluraled
 
 ### $tc(key, locale)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
@@ -659,6 +667,10 @@ Translation message that is pluraled
 
 ### $tc(key, list)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
@@ -686,6 +698,10 @@ Translation message that is pluraled
 
 ### $tc(key, named)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
@@ -713,6 +729,10 @@ Translation message that is pluraled
 
 ### $tc(key, choice)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
@@ -740,6 +760,10 @@ Translation message that is pluraled
 
 ### $tc(key, choice, locale)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
@@ -768,6 +792,10 @@ Translation message that is pluraled
 
 ### $tc(key, choice, list)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
@@ -796,6 +824,10 @@ Translation message that is pluraled
 
 ### $tc(key, choice, named)
 
+:::danger NOTICE
+**`$tc` has been deprecated in v10. Use `$t` instead. `$tc` is going to remove in v11.**
+:::
+
 Locale message pluralization
 
 **Signature:**
diff --git a/docs/guide/migration/breaking10.md b/docs/guide/migration/breaking10.md
index 45429ba3..235695a0 100644
--- a/docs/guide/migration/breaking10.md
+++ b/docs/guide/migration/breaking10.md
@@ -197,3 +197,382 @@ const i18n = createI18n({
 
 console.log(i18n.global.t('message.hello', { name: 'dio' }, { locale: 'ja' }))
 ```
+
+### Deprecate `tc` and `$tc` for Legacy API mode
+
+The following APIs are deprecated in v10:
+
+- `tc(key: Key | ResourceKeys): TranslateResult;`
+- `tc(key: Key | ResourceKeys, locale: Locales | Locale): TranslateResult;`
+- `tc(key: Key | ResourceKeys, list: unknown[]): TranslateResult;`
+- `tc(key: Key | ResourceKeys, named: Record<string, unknown>): TranslateResult;`
+- `tc(key: Key | ResourceKeys, choice: number): TranslateResult;`
+- `tc(key: Key | ResourceKeys, choice: number, locale: Locales | Locale): TranslateResult;`
+- `tc(key: Key | ResourceKeys, choice: number, list: unknown[]): TranslateResult;`
+- `tc(key: Key | ResourceKeys, choice: number, named: Record<string, unknown>): TranslateResult;`
+- `$tc(key: Key): TranslateResult;`
+- `$tc(key: Key, locale: Locale): TranslateResult;`
+- `$tc(key: Key, list: unknown[]): TranslateResult;`
+- `$tc(key: Key, named: Record<string, unknown>): TranslateResult;`
+- `$tc(key: Key, choice: number): TranslateResult;`
+- `$tc(key: Key, choice: number, locale: Locale): TranslateResult;`
+- `$tc(key: Key, choice: number, list: unknown[]): TranslateResult;`
+- `$tc(key: Key, choice: number, named: Record<string, unknown>): TranslateResult;`
+
+**Reason**: Legacy API mode has `t` and `$t` support plural interfaces, so they can be replaced.
+
+In v10, `tc` and `$tc` still exist to give benefit migration. These will be dropped completely in v11.
+
+If you will use them, Vue I18n will output the console warning in your application.
+
+
+#### `tc(key: Key | ResourceKeys): TranslateResult;`
+
+Vue I18n v9.x:
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana'))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, plural: number): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', 1))
+```
+
+#### `tc(key: Key | ResourceKeys, locale: Locales | Locale): TranslateResult;`
+
+Vue I18n v9.x:
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana', 'ja'))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, plural: number, options: TranslateOptions<Locales>): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', 1, { locale: 'ja' }))
+```
+
+#### `tc(key: Key | ResourceKeys, list: unknown[]): TranslateResult;`
+
+
+Vue I18n v9.x:
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana', ['dio']))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, list: unknown[], plural: number): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', ['dio'], 1))
+```
+
+#### `tc(key: Key | ResourceKeys, named: Record<string, unknown>): TranslateResult;`
+
+Vue I18n v9.x:
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana', { name: 'dio' }))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, named: NamedValue, plural: number): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', { name: 'dio' }, 1))
+```
+
+#### `tc(key: Key | ResourceKeys, choice: number): TranslateResult;`
+
+Vue I18n v9.x:
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana', 2))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, plural: number): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', 2))
+```
+
+#### `tc(key: Key | ResourceKeys, choice: number, locale: Locales | Locale): TranslateResult;`
+
+Vue I18n v9.x:
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana', 2, 'ja'))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, plural: number, options: TranslateOptions<Locales>): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', 2, { locale: 'ja' }))
+```
+
+#### `tc(key: Key | ResourceKeys, choice: number, list: unknown[]): TranslateResult;`
+
+Vue I18n v9.x:
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', 2, ['dio']))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, list: unknown[], plural: number): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.t('banana', ['dio'], 2))
+```
+
+#### `tc(key: Key | ResourceKeys, choice: number, named: Record<string, unknown>): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana', 2, { name: 'dio' }))
+```
+
+Vue I18n v10 or later:
+
+use `t(key: Key | ResourceKeys, named: NamedValue, plural: number): TranslateResult;`
+
+```ts
+const i18n = createI18n({
+  legacy: true,
+  // something options ...
+})
+console.log(i18n.global.tc('banana', { name: 'dio' }, 2))
+```
+
+#### `$tc(key: Key): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana') }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, plural: number): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', 1) }}</p>
+</template>
+```
+
+#### `$tc(key: Key, locale: Locale): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana', 'ja') }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, plural: number, options: TranslateOptions): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', 1, { locale: 'ja' }) }}</p>
+</template>
+```
+
+#### `$tc(key: Key, list: unknown[]): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana', ['dio']) }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, list: unknown[], plural: number): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', ['dio'], 1) }}</p>
+</template>
+```
+
+#### `$tc(key: Key, named: Record<string, unknown>): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana', { name: 'dio' }) }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, named: NamedValue, plural: number): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', { name: 'dio' }, 1) }}</p>
+</template>
+```
+
+#### `$tc(key: Key, choice: number): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana', 2) }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, plural: number): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', 2) }}</p>
+</template>
+```
+
+#### `$tc(key: Key, choice: number, locale: Locale): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana', 2, 'ja') }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, plural: number, options: TranslateOptions): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', 2, { locale: 'ja' }) }}</p>
+</template>
+```
+
+#### `$tc(key: Key, choice: number, list: unknown[]): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana', 2, ['dio']) }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, list: unknown[], plural: number): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', ['dio'], 2) }}</p>
+</template>
+```
+
+#### `$tc(key: Key, choice: number, named: Record<string, unknown>): TranslateResult;`
+
+Vue I18n v9.x:
+
+```vue
+<template>
+  <p>{{ $tc('banana', 2,  { name: 'dio' }) }}</p>
+</template>
+```
+
+Vue I18n v10 or later:
+
+use `$t(key: Key, named: NamedValue, plural: number): TranslateResult;`
+
+```vue
+<template>
+  <p>{{ $t('banana', { name: 'dio' }, 2) }}</p>
+</template>
+```
diff --git a/packages/vue-i18n-core/src/legacy.ts b/packages/vue-i18n-core/src/legacy.ts
index 6edbf38b..4fe4f6f8 100644
--- a/packages/vue-i18n-core/src/legacy.ts
+++ b/packages/vue-i18n-core/src/legacy.ts
@@ -1,6 +1,7 @@
 /* eslint-disable @typescript-eslint/no-explicit-any */
 import { createComposer, DefineLocaleMessage } from './composer'
 import { createI18nError, I18nErrorCodes } from './errors'
+import { I18nWarnCodes, getWarnMessage } from './warnings'
 import { EnableEmitter, DisableEmitter } from './symbols'
 import { DEFAULT_LOCALE } from '@intlify/core-base'
 import {
@@ -11,7 +12,8 @@ import {
   isBoolean,
   isFunction,
   isRegExp,
-  assign
+  assign,
+  warnOnce
 } from '@intlify/shared'
 
 import type {
@@ -1720,6 +1722,10 @@ export function createVueI18n(options: any = {}): any {
       let list: unknown[] | null = null
       let named: NamedValue | null = null
 
+      if (__DEV__) {
+        warnOnce(getWarnMessage(I18nWarnCodes.DEPRECATE_TC))
+      }
+
       if (!isString(arg1)) {
         throw createI18nError(I18nErrorCodes.INVALID_ARGUMENT)
       }
diff --git a/packages/vue-i18n-core/src/warnings.ts b/packages/vue-i18n-core/src/warnings.ts
index b429dbf1..85964a43 100644
--- a/packages/vue-i18n-core/src/warnings.ts
+++ b/packages/vue-i18n-core/src/warnings.ts
@@ -9,7 +9,9 @@ export const I18nWarnCodes = {
   COMPONENT_NAME_LEGACY_COMPATIBLE: inc(), // 9
   NOT_FOUND_PARENT_SCOPE: inc(), // 10
   IGNORE_OBJ_FLATTEN: inc(), // 11
-  NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG: inc() // 12
+  NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG: inc(), // 12
+  DEPRECATE_TC: inc(), // 13
+  __EXTEND_POINT__: inc() // 14
 } as const
 
 type I18nWarnCodes = (typeof I18nWarnCodes)[keyof typeof I18nWarnCodes]
@@ -19,7 +21,8 @@ export const warnMessages: { [code: number]: string } = {
   [I18nWarnCodes.COMPONENT_NAME_LEGACY_COMPATIBLE]: `Component name legacy compatible: '{name}' -> 'i18n'`,
   [I18nWarnCodes.NOT_FOUND_PARENT_SCOPE]: `Not found parent scope. use the global scope.`,
   [I18nWarnCodes.IGNORE_OBJ_FLATTEN]: `Ignore object flatten: '{key}' key has an string value`,
-  [I18nWarnCodes.NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG]: `'translateExistCompatible' option will be dropped in the next major version.`
+  [I18nWarnCodes.NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG]: `'translateExistCompatible' option will be dropped in the next major version.`,
+  [I18nWarnCodes.DEPRECATE_TC]: `'tc' and '$tc' has been deprecated in v10. Use 't' or '$t' instead. 'tc' and '$tc’ are going to remove in v11.`
 }
 
 export function getWarnMessage(


Test patch:
diff --git a/packages/vue-i18n-core/test/i18n.test.ts b/packages/vue-i18n-core/test/i18n.test.ts
index e40dfcbc..5dc87e8f 100644
--- a/packages/vue-i18n-core/test/i18n.test.ts
+++ b/packages/vue-i18n-core/test/i18n.test.ts
@@ -8,7 +8,8 @@ vi.mock('@intlify/shared', async () => {
   const actual = await vi.importActual<object>('@intlify/shared')
   return {
     ...actual,
-    warn: vi.fn()
+    warn: vi.fn(),
+    warnOnce: vi.fn()
   }
 })
 
@@ -1036,6 +1037,10 @@ describe('merge i18n custom blocks to global scope', () => {
 })
 
 describe('custom pluralization', () => {
+  const mockWarn = vi.spyOn(shared, 'warnOnce')
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  mockWarn.mockImplementation(() => {})
+
   test('legacy', async () => {
     const i18n = createI18n({
       locale: 'ru',
diff --git a/packages/vue-i18n-core/test/legacy.test.ts b/packages/vue-i18n-core/test/legacy.test.ts
index 1c143623..7236446c 100644
--- a/packages/vue-i18n-core/test/legacy.test.ts
+++ b/packages/vue-i18n-core/test/legacy.test.ts
@@ -6,7 +6,8 @@ vi.mock('@intlify/shared', async () => {
   const actual = await vi.importActual<object>('@intlify/shared')
   return {
     ...actual,
-    warn: vi.fn()
+    warn: vi.fn(),
+    warnOnce: vi.fn()
   }
 })
 
@@ -117,6 +118,10 @@ test('postTranslation', () => {
 })
 
 test('pluralizationRules', () => {
+  const mockWarn = vi.spyOn(shared, 'warnOnce')
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  mockWarn.mockImplementation(() => {})
+
   const i18n = createVueI18n({
     locale: 'ru',
     pluralizationRules: _pluralRules,
@@ -133,6 +138,8 @@ test('pluralizationRules', () => {
   expect(i18n.tc('car', 4)).toEqual('4 машины')
   expect(i18n.tc('car', 12)).toEqual('12 машин')
   expect(i18n.tc('car', 21)).toEqual('21 машина')
+
+  expect(mockWarn).toHaveBeenCalled()
 })
 
 test('messages', () => {
diff --git a/packages/vue-i18n-core/test/mixin.test.ts b/packages/vue-i18n-core/test/mixin.test.ts
index c5160ad4..04876588 100644
--- a/packages/vue-i18n-core/test/mixin.test.ts
+++ b/packages/vue-i18n-core/test/mixin.test.ts
@@ -2,6 +2,16 @@
  * @vitest-environment jsdom
  */
 
+// utils
+import * as shared from '@intlify/shared'
+vi.mock('@intlify/shared', async () => {
+  const actual = await vi.importActual<object>('@intlify/shared')
+  return {
+    ...actual,
+    warnOnce: vi.fn()
+  }
+})
+
 import { mount } from './helper'
 import { defineComponent, nextTick } from 'vue'
 import {
@@ -126,6 +136,10 @@ test('$rt', async () => {
 })
 
 test('$tc', async () => {
+  const mockWarn = vi.spyOn(shared, 'warnOnce')
+  // eslint-disable-next-line @typescript-eslint/no-empty-function
+  mockWarn.mockImplementation(() => {})
+
   const i18n = createI18n({
     legacy: true,
     locale: 'en',
@@ -140,6 +154,7 @@ test('$tc', async () => {
   const { vm } = await mount(App, i18n)
 
   expect(vm.$tc!('banana', 2)).toEqual('2 bananas')
+  expect(mockWarn).toHaveBeenCalled()
 })
 
 test('$te', async () => {
diff --git a/packages/vue-i18n-core/test/warnings.test.ts b/packages/vue-i18n-core/test/warnings.test.ts
index 4a0a243d..dc7835e5 100644
--- a/packages/vue-i18n-core/test/warnings.test.ts
+++ b/packages/vue-i18n-core/test/warnings.test.ts
@@ -1,5 +1,5 @@
 import { I18nWarnCodes } from '../src/warnings'
 
 test('I18nWarnCodes', () => {
-  expect(I18nWarnCodes.NOTICE_DROP_TRANSLATE_EXIST_COMPATIBLE_FLAG).toBe(12)
+  expect(I18nWarnCodes.__EXTEND_POINT__).toBe(14)
 })