kazupon / vue-i18n

:globe_with_meridians: Internationalization plugin for Vue.js
https://kazupon.github.io/vue-i18n/
MIT License
7.26k stars 860 forks source link

TypeError: _normalize is not a function #1493

Open zkriszti opened 2 years ago

zkriszti commented 2 years ago

Reporting a bug?

I'm migrating a Vue2 project from Webpack to Vite. We have been using vue-i18n extensively. We have a special folder structure, so after importing all translation files as modules with Vite's globEager, I am creating a messages object that should be used throughout the app (and it is the same structure that we have successfully used w/ Webpack). However, the translation strings in my object come back as a function, such as: title: (ctx) => {const { normalize: _normalize } = ctx;return _normalize(["My Translated Title"])} When I'm trying to use i18n in a component, I get the below message: TypeError: _normalize is not a function (and my component won't render).

Expected behavior

The above error message should not happen, component with vue-i18n based translation should render seamlessly.

Reproduction

Here's a link to a minimal reproducible example in codesandbox: https://codesandbox.io/s/mre-vue-i18n-normalize-is-not-a-function-duuxwt

i18n.js:

import Vue from 'vue'
import VueI18n from 'vue-i18n'
import messages from '@intlify/vite-plugin-vue-i18n/messages'

//...

export default new VueI18n({
  locale: import.meta.VITE_I18N_LOCALE || locale,
  fallbackLocale: import.meta.VITE_I18N_FALLBACK_LOCALE || locale,
  messages: getLocaleMessages() // this returns custom messages object
})

vite.config.js

import { defineConfig } from 'vite'
import { createVuePlugin as vue } from "vite-plugin-vue2"
import vueI18n from '@intlify/vite-plugin-vue-i18n'
import path from "path";

export default defineConfig({
  define: {
    'process.env': {}
  },
  plugins: [
    vue(),
    vueI18n({
      compositionOnly: false, // set this to false to use Vue I18n Legacy API
      include: path.resolve(__dirname, './src/locales/**')
    })
  ],
  resolve: {
    alias: {
      "@": path.resolve(__dirname, "./src")
    }
  }
})

System Info

System:
    OS: Windows 10 10.0.19044
    CPU: (8) x64 Intel(R) Core(TM) i5-8265U CPU @ 1.60GHz    
    Memory: 2.28 GB / 15.77 GB
  Binaries:
    Node: 12.14.1 - C:\Program Files\nodejs\node.EXE
    npm: 6.13.4 - C:\Program Files\nodejs\npm.CMD
  Browsers:
    Chrome: 100.0.4896.127
    Edge: Spartan (44.19041.1266.0), Chromium (100.0.1185.44)
    Internet Explorer: 11.0.19041.1566

Screenshot

image

Additional context

No response

Validations

zkriszti commented 2 years ago

EDIT: I added a link to a minimal reproducible example.

mmorainville commented 2 years ago

Hi, I have the exact same issue, any news?

kazupon commented 2 years ago

You need to add vue-i18n-bridge package in your project. see the details: https://vue-i18n.intlify.dev/guide/migration/vue2.html#usage

If you do use it, note that there are some limitations.

vue-i18n-bridge has limitations: https://vue-i18n.intlify.dev/guide/migration/vue2.html#limitations

pedrolamas commented 1 year ago

I've been experiencing this exact issue today, and took the above sandbox and added vue-i18n-bridge following the instructions, and I still see the same error message...

Here's the updated sandbox:

https://codesandbox.io/s/mre-vue-i18n-normalize-is-not-a-function-forked-guqn1s

zkriszti commented 1 year ago

I've been experiencing this exact issue today, and took the above sandbox and added vue-i18n-bridge following the instructions, and I still see the same error message...

Here's the updated sandbox:

https://codesandbox.io/s/mre-vue-i18n-normalize-is-not-a-function-forked-guqn1s

Exactly, I've also tried using vue-i18n-bridge as per Kazupon's suggestion, but it unfortunately didn't solve the issue in my case either.

pedrolamas commented 1 year ago

Ok, I managed to bypass this completely... my issue was that the our translations are on yaml files, so once I used a yaml transformer (even removed @intlify/vite-plugin-vue-i18n completely!), all issues were gone and everything started to work as expected!

NathanAP commented 1 year ago

Really important to say that adding that configuration of vue-i18n to vite.config.js doesn't solves this problem.

Adnan-kreiker commented 1 year ago

I'm also migrating a vue 2.7 app from webpack to vite.

I Have been stuck on this error for hours 😕. Any update? I followed the migration guide without success.

I'm still getting TypeError: _normalize is not a function

NathanAP commented 1 year ago

After we run npm run build, this is very problematic.

I'm not sure if I'm not importing my messages correctly, but why are all of them functions? If I have this structure:

{ 
   "en-EN": { 
      "test": "this is a test"
   }
}

the return of importedTest['en-EN'].test is not this is a test. It is actually e=>{const{normalize:n}=e;return n(["this is a test])}. Why this occurs? Am I doing something wrong?

Edit: here is an image of one of my imported JSON:

image

kenhyuwa commented 1 year ago

Nuxt3 with some trick nice work...

<!-- <i18n src="./menu.json"></i18n> -->

<i18n lang="json">
  {
    "id": {
      "name": "Pertama",
      "sub": [
        {
          "one": "Satu"
        }
      ]
    },
    "en": {
      "name": "2nd",
      "sub": [
        {
          "one": "One"
        }
      ]
    }
  }
</i18n>

<script setup lang="ts">
const { locale, t, getLocaleMessage } = useI18n()
const menu = getLocaleMessage(unref(locale))

function normalize([v]: string[]) {
  return v
}
</script>

<template>
  <div>
    <div>
      {{ t('name') }}
    </div>
    <div v-for="(item, i) in menu.sub" :key="i">
      Sub: {{ item.one({ normalize }) }}
    </div>
  </div>
</template>
Stein-Hakase commented 1 year ago

Nuxt3 with some trick nice work...

<!-- <i18n src="./menu.json"></i18n> -->

<i18n lang="json">
  {
    "id": {
      "name": "Pertama",
      "sub": [
        {
          "one": "Satu"
        }
      ]
    },
    "en": {
      "name": "2nd",
      "sub": [
        {
          "one": "One"
        }
      ]
    }
  }
</i18n>

<script setup lang="ts">
const { locale, t, getLocaleMessage } = useI18n()
const menu = getLocaleMessage(unref(locale))

function normalize([v]: string[]) {
  return v
}
</script>

<template>
  <div>
    <div>
      {{ t('name') }}
    </div>
    <div v-for="(item, i) in menu.sub" :key="i">
      Sub: {{ item.one({ normalize }) }}
    </div>
  </div>
</template>

Use .js file instead of json, then getLocaleMessage should works fine

gazben commented 1 year ago

Any update on this? Is there a solution to the problem?

I'm facing the same error (vue 2.7 migrating to vite)

spectrachrome commented 1 year ago

None of the solutions here really solve my problem. Is this still being worked on?

Adnan-kreiker commented 1 year ago

@gazben @spectrachrome Please check this issue

I found a temporary hack to get around it, hoping for it to get solved one day

kriti-jain-unbiased-co-uk commented 1 year ago

Facing same issue while migrating vite with Vue 2.7. :(

Instinctlol commented 11 months ago

I found a hack to workaround this problem in vue 2.7. I'm using import.meta.glob() to import my .js localization files. They export default an object containing all my translation messages. So they look like this:

export default {
  ok: 'Ok',
....
};

This is how I import my localization .js files:

async function loadLocale(locale) {
  let modules;
  let resp = {};
  if (locale === 'de') {
    modules = import.meta.glob('../locales/de.js');
  } else {
    modules = import.meta.glob('../locales/en.js');
  }
  await modules[Object.keys(modules)[0]]().then((mod) => {
    // console.log(Object.keys(modules)[0], mod.default);
    resp = mod.default;
  });
  applySourcesHack(resp);
  return resp;
}

Before applying the locale(s) to i18n, I have to apply the hack. The problem is, that some keys contain functions instead of strings. This is the hack:

function applySourcesHack(obj) {
  const getSource = (func) => {
    if (typeof func === 'function') {
      return '' + func.source;
    }
    return 'not a function';
  };

  if (typeof obj === 'object') {
    Object.keys(obj).forEach((key) => {
      if (typeof obj[key] === 'object') {
        applySourcesHack(obj[key]);
      } else {
        obj[key] = getSource(obj[key]);
      }
    });
  }
}

I also had to use this bundler for the vite bridge:

vite.config.js

...
resolve: {
    alias: {
      'vue-i18n-bridge': 'vue-i18n-bridge/dist/vue-i18n-bridge.esm-bundler.js',
    },
  },

Hope this will help someone :)

Note: I'm only using vue 2.7 as an intermediate step to upgrade to vue 3.x so I'm fine with using hacks like this.

dgoemans commented 8 months ago

Ran into this today. We were trying to integrate a Vue3 web component (our component, running Vue3 with i18n) into a Magento store (running Vue Storefront with i18n). It seems that something in the Magento/Vue codebase is messing with the message compilation of our component.

A work around that we have now is to enable the JIT compilation, which forces a different message compiler and works. You can do this by setting the global __INTLIFY_JIT_COMPILATION__ flag to true before loading your code.