Repository: intlify/vue-i18n (TypeScript)

Issue:
$te and $t not working correct if path has ".name" suffix
### Reporting a bug?

Please see reproduction link.
If you add '.name' to a path, the result is "fn", even though that is an invalid path. $te also returns `true`.

### Expected behavior

$te should return `false` and the key should be shown as fallback, not 'fn'

### Reproduction

https://stackblitz.com/edit/vitejs-vite-tykujb?file=src%2FApp.vue

### System Info

```shell
System:
    OS: Windows 10 10.0.22621
    CPU: (16) x64 Intel(R) Core(TM) i9-9880H CPU @ 2.30GHz
    Memory: 70.75 GB / 95.78 GB
  Binaries:
    Node: 18.17.0 - C:\Program Files\nodejs\node.EXE
    Yarn: 1.22.19 - C:\Program Files (x86)\Yarn\bin\yarn.CMD
    npm: 9.8.1 - C:\Program Files\nodejs\npm.CMD
    pnpm: 8.6.12 - ~\AppData\Roaming\npm\pnpm.CMD
  Browsers:
    Chrome: 118.0.5993.89
    Edge: Chromium (118.0.2088.57)
    Internet Explorer: 11.0.22621.1
```


### Screenshot

_No response_

### Additional context

_No response_

### Validations

- [X] Read the [Contributing Guidelines](https://github.com/intlify/vue-i18n-next/blob/master/.github/CONTRIBUTING.md)
- [X] Read the [Documentation](https://vue-i18n.intlify.dev/)
- [X] Check that there isn't [already an issue](https://github.com/intlify/vue-i18n-next/issues) that reports the same bug to avoid creating a duplicate.
- [X] Check that this is a concrete bug. For Q&A open a [GitHub Discussions](https://github.com/intlify/vue-i18n-next/discussions)


Discussion:

Patch:
diff --git a/packages/core-base/src/resolver.ts b/packages/core-base/src/resolver.ts
index ba838c00c..165cff19c 100644
--- a/packages/core-base/src/resolver.ts
+++ b/packages/core-base/src/resolver.ts
@@ -1,4 +1,4 @@
-import { isObject } from '@intlify/shared'
+import { isObject, isFunction } from '@intlify/shared'
 
 /** @VueI18nGeneral */
 export type Path = string
@@ -348,6 +348,9 @@ export function resolveValue(obj: unknown, path: Path): PathValue {
     if (val === undefined) {
       return null
     }
+    if (isFunction(last)) {
+      return null
+    }
     last = val
     i++
   }
diff --git a/packages/vue-i18n-core/src/composer.ts b/packages/vue-i18n-core/src/composer.ts
index a2968174e..411bbcb1b 100644
--- a/packages/vue-i18n-core/src/composer.ts
+++ b/packages/vue-i18n-core/src/composer.ts
@@ -2364,25 +2364,11 @@ export function createComposer(options: any = {}, VueI18nLegacy?: any): any {
       () => [key],
       'translate exists',
       root => {
-        console.log('root ... te')
         return Reflect.apply(root.te, root, [key, locale])
       },
       NOOP_RETURN_FALSE,
       val => isBoolean(val)
     )
-    /*
-    if (!key) {
-      return false
-    }
-    const targetLocale = isString(locale) ? locale : _locale.value
-    const message = getLocaleMessage(targetLocale)
-    const resolved = _context.messageResolver(message, key)
-    return (
-      isMessageAST(resolved) ||
-      isMessageFunction(resolved) ||
-      isString(resolved)
-    )
-    */
   }
 
   function resolveMessages(key: Path): LocaleMessageValue<Message> | null {


Test patch:
diff --git a/packages/core-base/test/resolver.test.ts b/packages/core-base/test/resolver.test.ts
index 3cc00008a..13285113f 100644
--- a/packages/core-base/test/resolver.test.ts
+++ b/packages/core-base/test/resolver.test.ts
@@ -120,4 +120,9 @@ test('resolveValue', () => {
   expect(resolveValue({}, 'a.b.c[]')).toEqual(null)
   // blanket middle
   expect(resolveValue({}, 'a.b.c[]d')).toEqual(null)
+  // function
+  const fn = () => 1
+  expect(resolveValue({ a: fn }, 'a.name')).toEqual(null)
+  expect(resolveValue({ a: fn }, 'a.toString')).toEqual(null)
+  expect(resolveValue({ a: fn }, 'a')).toEqual(fn)
 })
diff --git a/packages/vue-i18n-core/test/issues.test.ts b/packages/vue-i18n-core/test/issues.test.ts
index 84768c570..61ab56268 100644
--- a/packages/vue-i18n-core/test/issues.test.ts
+++ b/packages/vue-i18n-core/test/issues.test.ts
@@ -1198,7 +1198,7 @@ test('issue #1595 merge case', async () => {
   )
 })
 
-test('issue #1610 merge case', async () => {
+test('issue #1610', async () => {
   const en = {
     hello: 'Hello, Vue I18n',
     language: 'Languages'
@@ -1227,3 +1227,49 @@ test('issue #1610 merge case', async () => {
     `<h1>Hello, Vue I18n</h1> true (...but this should be true)`
   )
 })
+
+test('issue #1615', async () => {
+  console.log('----')
+  const en = {
+    hello: (() => {
+      const fn = ctx => {
+        const { normalize: _normalize } = ctx
+        return _normalize(['Hello, Vue I18n'])
+      }
+      fn.source = 'Hello, Vue I18n'
+      return fn
+    })(),
+    language: (() => {
+      const fn = ctx => {
+        const { normalize: _normalize } = ctx
+        return _normalize(['Languages'])
+      }
+      fn.source = 'Languages'
+      return fn
+    })()
+  }
+  const i18n = createI18n({
+    legacy: false,
+    locale: 'en',
+    globalInjection: true,
+    messages: {
+      en: {}
+    }
+  })
+
+  const App = defineComponent({
+    template: `
+<h1>{{ $t('hello.name') }}</h1>
+<p>(( "hello.name" does not exist. correct path would just be "hello")</p>
+<p id="te">{{ $te('hello.name') }} (...but this should be false)</p>
+`
+  })
+  const wrapper = await mount(App, i18n)
+
+  i18n.global.setLocaleMessage('en', en)
+  await nextTick()
+
+  expect(wrapper.find('#te')?.textContent).toEqual(
+    `false (...but this should be false)`
+  )
+})