Repository: intlify/vue-i18n-next
Pull request #1798 (2024-04-13 09:33:25)

fix: not throw warnings when using implicit fallback
resolves https://github.com/intlify/vue-i18n-next/issues/1768


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5429d949..3187ec9b 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,21 @@
+# v9.12.1 (2024-04-14T10:48:11Z)
+
+This changelog is generated by [GitHub Releases](https://github.com/intlify/vue-i18n-next/releases/tag/v9.12.1)
+
+<!-- Release notes generated using configuration in .github/release.yml at v9.12.1 -->
+
+## What's Changed
+
+### 🐛 Bug Fixes
+
+- fix: compile error for key with included hyphen in named interpolation by @kazupon in https://github.com/intlify/vue-i18n-next/pull/1797
+
+### 👕 Refactoring
+
+- refactor: tokenizer by @kazupon in https://github.com/intlify/vue-i18n-next/pull/1799
+
+**Full Changelog**: https://github.com/intlify/vue-i18n-next/compare/v9.12.0...v9.12.1
+
 # v9.12.0 (2024-04-12T01:30:04Z)
 
 This changelog is generated by [GitHub Releases](https://github.com/intlify/vue-i18n-next/releases/tag/v9.12.0)
diff --git a/package.json b/package.json
index bd6a0449..617877ab 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "vue-i18n-next",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "license": "MIT",
   "author": {
     "name": "kazuya kawaguchi",
diff --git a/packages/core-base/package.json b/packages/core-base/package.json
index 44207d80..4eaaa0e8 100644
--- a/packages/core-base/package.json
+++ b/packages/core-base/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@intlify/core-base",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "@intlify/core-base",
   "keywords": [
     "core",
diff --git a/packages/core-base/src/context.ts b/packages/core-base/src/context.ts
index bf0a6455..3104e1c7 100644
--- a/packages/core-base/src/context.ts
+++ b/packages/core-base/src/context.ts
@@ -692,4 +692,32 @@ export function updateFallbackLocale<Message = string>(
   ctx.localeFallbacker<Message>(ctx, fallback, locale)
 }
 
+/** @internal */
+export function isAlmostSameLocale(
+  locale: Locale,
+  compareLocale: Locale
+): boolean {
+  if (locale === compareLocale) return false
+  return locale.split('-')[0] === compareLocale.split('-')[0]
+}
+
+/** @internal */
+export function isImplicitFallback(
+  targetLocale: Locale,
+  locales: Locale[]
+): boolean {
+  const index = locales.indexOf(targetLocale)
+  if (index === -1) {
+    return false
+  }
+
+  for (let i = index + 1; i < locales.length; i++) {
+    if (isAlmostSameLocale(targetLocale, locales[i])) {
+      return true
+    }
+  }
+
+  return false
+}
+
 /* eslint-enable @typescript-eslint/no-explicit-any */
diff --git a/packages/core-base/src/translate.ts b/packages/core-base/src/translate.ts
index 465266a5..70a54e44 100644
--- a/packages/core-base/src/translate.ts
+++ b/packages/core-base/src/translate.ts
@@ -20,6 +20,8 @@ import { isMessageAST } from './compilation'
 import { createMessageContext } from './runtime'
 import {
   isTranslateFallbackWarn,
+  isAlmostSameLocale,
+  isImplicitFallback,
   handleMissing,
   NOT_REOSLVED,
   getAdditionalMeta,
@@ -839,6 +841,7 @@ function resolveMessageFormat<Messages, Message>(
     if (
       __DEV__ &&
       locale !== targetLocale &&
+      !isAlmostSameLocale(locale, targetLocale) &&
       isTranslateFallbackWarn(fallbackWarn, key)
     ) {
       onWarn(
@@ -905,15 +908,17 @@ function resolveMessageFormat<Messages, Message>(
       break
     }
 
-    const missingRet = handleMissing(
-      context as any, // eslint-disable-line @typescript-eslint/no-explicit-any
-      key,
-      targetLocale,
-      missingWarn,
-      type
-    )
-    if (missingRet !== key) {
-      format = missingRet as PathValue
+    if (!isImplicitFallback(targetLocale, locales)) {
+      const missingRet = handleMissing(
+        context as any, // eslint-disable-line @typescript-eslint/no-explicit-any
+        key,
+        targetLocale,
+        missingWarn,
+        type
+      )
+      if (missingRet !== key) {
+        format = missingRet as PathValue
+      }
     }
     from = to
   }
diff --git a/packages/core/package.json b/packages/core/package.json
index bbd465dc..df377171 100644
--- a/packages/core/package.json
+++ b/packages/core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@intlify/core",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "@intlify/core",
   "keywords": [
     "core",
diff --git a/packages/devtools-if/package.json b/packages/devtools-if/package.json
index 58a89c42..6b7303dd 100644
--- a/packages/devtools-if/package.json
+++ b/packages/devtools-if/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@intlify/devtools-if",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "@intlify/devtools-if",
   "keywords": [
     "devtools",
diff --git a/packages/format-explorer/package.json b/packages/format-explorer/package.json
index c1069bee..760fab2e 100644
--- a/packages/format-explorer/package.json
+++ b/packages/format-explorer/package.json
@@ -1,7 +1,7 @@
 {
   "name": "@intlify/message-format-explorer",
   "description": "Intlify message format explorer",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "private": true,
   "scripts": {
     "dev": "vite",
diff --git a/packages/message-compiler/package.json b/packages/message-compiler/package.json
index 4ea92b12..6e60fccd 100644
--- a/packages/message-compiler/package.json
+++ b/packages/message-compiler/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@intlify/message-compiler",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "@intlify/message-compiler",
   "keywords": [
     "compiler",
diff --git a/packages/petite-vue-i18n/package.json b/packages/petite-vue-i18n/package.json
index c2663d3e..0b351bd4 100644
--- a/packages/petite-vue-i18n/package.json
+++ b/packages/petite-vue-i18n/package.json
@@ -1,6 +1,6 @@
 {
   "name": "petite-vue-i18n",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "Vue I18n lite version",
   "keywords": [
     "i18n",
diff --git a/packages/shared/package.json b/packages/shared/package.json
index c2a71ec2..cb525675 100644
--- a/packages/shared/package.json
+++ b/packages/shared/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@intlify/shared",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "@intlify/shared",
   "keywords": [
     "i18n",
diff --git a/packages/size-check-core/package.json b/packages/size-check-core/package.json
index 59e2b674..1f445e24 100644
--- a/packages/size-check-core/package.json
+++ b/packages/size-check-core/package.json
@@ -13,5 +13,5 @@
   "devDependencies": {
     "vite": "^5.1.4"
   },
-  "version": "9.12.0"
+  "version": "9.12.1"
 }
diff --git a/packages/size-check-petite-vue-i18n/package.json b/packages/size-check-petite-vue-i18n/package.json
index ec5411be..7130f1b9 100644
--- a/packages/size-check-petite-vue-i18n/package.json
+++ b/packages/size-check-petite-vue-i18n/package.json
@@ -17,5 +17,5 @@
     "vite": "^5.1.4",
     "vue-tsc": "^2.0.0"
   },
-  "version": "9.12.0"
+  "version": "9.12.1"
 }
diff --git a/packages/size-check-vue-i18n/package.json b/packages/size-check-vue-i18n/package.json
index ce9ce330..66ca96d3 100644
--- a/packages/size-check-vue-i18n/package.json
+++ b/packages/size-check-vue-i18n/package.json
@@ -17,5 +17,5 @@
     "vite": "^5.1.4",
     "vue-tsc": "^2.0.0"
   },
-  "version": "9.12.0"
+  "version": "9.12.1"
 }
diff --git a/packages/vue-devtools/package.json b/packages/vue-devtools/package.json
index 1fa9edb1..bc09be84 100644
--- a/packages/vue-devtools/package.json
+++ b/packages/vue-devtools/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@intlify/vue-devtools",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "@intlify/vue-devtools",
   "keywords": [
     "i18n",
diff --git a/packages/vue-i18n-bridge/package.json b/packages/vue-i18n-bridge/package.json
index ac19b2af..10cb5efc 100644
--- a/packages/vue-i18n-bridge/package.json
+++ b/packages/vue-i18n-bridge/package.json
@@ -1,6 +1,6 @@
 {
   "name": "vue-i18n-bridge",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "A bridge for Vue I18n Legacy",
   "keywords": [
     "bridge",
diff --git a/packages/vue-i18n-core/package.json b/packages/vue-i18n-core/package.json
index bb9db1b2..725a75b3 100644
--- a/packages/vue-i18n-core/package.json
+++ b/packages/vue-i18n-core/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@intlify/vue-i18n-core",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "@intlify/vue-i18n-core",
   "keywords": [
     "core",
diff --git a/packages/vue-i18n/package.json b/packages/vue-i18n/package.json
index 5640a550..4f676639 100644
--- a/packages/vue-i18n/package.json
+++ b/packages/vue-i18n/package.json
@@ -1,6 +1,6 @@
 {
   "name": "vue-i18n",
-  "version": "9.12.0",
+  "version": "9.12.1",
   "description": "Internationalization plugin for Vue.js",
   "keywords": [
     "i18n",


Test patch:
diff --git a/packages/vue-i18n-core/test/issues.test.ts b/packages/vue-i18n-core/test/issues.test.ts
index b9edbf81..c635eef9 100644
--- a/packages/vue-i18n-core/test/issues.test.ts
+++ b/packages/vue-i18n-core/test/issues.test.ts
@@ -1359,6 +1359,67 @@ test('issue #1738', async () => {
   expect(wrapper.find('#te2')?.textContent).toEqual(`true - expected true`)
 })
 
+describe('issue #1768', () => {
+  test('Implicit fallback using locales', async () => {
+    const mockWarn = vi.spyOn(shared, 'warn')
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
+    mockWarn.mockImplementation(() => {})
+
+    const i18n = createI18n({
+      locale: 'en-US',
+      fallbackLocale: 'en',
+      messages: {
+        en: {
+          hello: {
+            'vue-i18n': 'Hello, Vue I18n'
+          }
+        }
+      }
+    })
+
+    const App = defineComponent({
+      template: `<div>{{ $t('hello.vue-i18n') }}</div>`
+    })
+    const wrapper = await mount(App, i18n)
+
+    expect(wrapper.html()).toEqual('<div>Hello, Vue I18n</div>')
+    expect(mockWarn).toHaveBeenCalledTimes(0)
+  })
+
+  test('Explicit fallback with decision maps', async () => {
+    const mockWarn = vi.spyOn(shared, 'warn')
+    // eslint-disable-next-line @typescript-eslint/no-empty-function
+    mockWarn.mockImplementation(() => {})
+
+    const i18n = createI18n({
+      locale: 'zh-Hant',
+      fallbackLocale: {
+        'de-CH': ['fr', 'it'],
+        'zh-Hant': ['zh-Hans'],
+        'es-CL': ['es-AR'],
+        es: ['en-GB'],
+        pt: ['es-AR'],
+        default: ['en', 'da']
+      },
+      messages: {
+        zh: {
+          hello: {
+            'vue-i18n': '你好，Vue I18n'
+          }
+        }
+      }
+    })
+
+    const App = defineComponent({
+      template: `<div>{{ $t('hello.vue-i18n') }}</div>`
+    })
+    const wrapper = await mount(App, i18n)
+
+    expect(wrapper.html()).toEqual('<div>你好，Vue I18n</div>')
+    expect(mockWarn).toHaveBeenCalledTimes(0)
+  })
+})
+
 test('#1796', async () => {
   const i18n = createI18n({
     locale: 'en',