i18n icon indicating copy to clipboard operation
i18n copied to clipboard

Support for file prefix in locales.files config

Open icai opened this issue 8 months ago • 11 comments

Describe the feature

Currently, the files property in the locales configuration only supports an array of file paths. I would like to propose supporting a prefix option for each file to namespace its translations when loaded.

i18n: {
  locales: [
    {
      code: "en",
      name: "English",
      files: [
        { path: "en/common.json", prefix: "common" },
        { path: "en/checkout.json", prefix: "checkout" },
        { path: "en/order.json", prefix: "order" }
      ]
    }
  ]
}

With this configuration, the keys inside en/checkout.json would be accessible via t('checkout.key'), while en/common.json would be t('common.key'), and so on.

Additional information

  • [ ] Would you be willing to help implement this feature?
  • [ ] Could this feature be implemented as a module?

Final checks

icai avatar May 11 '25 07:05 icai

The files entry is about support multiple country languages where you can share a lot of messages from base language.

You can use specific keys inside the json file, check elk.zone file for example es.json, es-ES.json and es-419.json in the locales folder: https://github.com/elk-zone/elk/tree/main/locales

You can use multiple files using a custom key or keys, the way you use those keys won't change.

userquin avatar May 11 '25 08:05 userquin

@userquin

I have the following localization directory structure:

client/locales
├── en/
│   ├── activity.json
│   ├── cart.json
│   ├── checkout.json
│   └── ... (other page/module-specific files)
├── en.ts

Each language folder (like en/) contains JSON files that are split by page or module.

What I want is lazy loading of translations based on both language and page/module, rather than loading all translations for a language at once. This would improve performance by only loading the translation files needed for the current page.

icai avatar May 12 '25 11:05 icai

The big problem with your approach is using multiple files in the same page, what should i18n load?

What you're describing is localize pages using multiple i18n feature files... In the worst case, you will end up loading all the files.

userquin avatar May 12 '25 16:05 userquin

I am working on optimizations in v10 that aim to achieve the same thing but without the need for splitting translation files per page yourself by loading/stripping messages based on translation keys used during SSR

BobbieGoede avatar May 12 '25 17:05 BobbieGoede

So far there is no localization file subloading when creating a deeper architecture, so we do the following example: create locales/en/product/some.json

and in order for us to address the path product.some.accountInfo.email, we need the following architecture inside the some.json file:

{
 "product" : {
    "some" : {
         "accountInfo" : {
           "email" : "some"
        }
     }
   }
}

here's how I fixed it.

RGon-c avatar May 13 '25 07:05 RGon-c

I am working on optimizations in v10 that aim to achieve the same thing but without the need for splitting translation files per page yourself by loading/stripping messages based on translation keys used during SSR

So, this feature will just tree-shake messages per page? nice ❤

userquin avatar May 13 '25 11:05 userquin

@RGon-c Now I only use the config with the following like, but not use files config:

    defaultLocale: 'en',
    detectBrowserLanguage: {
      useCookie: true,
      fallbackLocale: 'en',
      cookieKey: 'i18n_redirected'
    },
    strategy: 'prefix_except_default',
    vueI18n: path.resolve(__dirname, './client/locales/i18n.config.ts'),

client/locales/en.ts

// Import JSON files
const modules = import.meta.glob('./en/*.json', { eager: true });

// Specify a more concrete JSON format type
const translations: Record<string, any> = {};

// Process each JSON module
Object.entries(modules).forEach(([path, module]) => {
  const moduleName = path.replace('./en/', '').replace('.json', '');
  translations[moduleName] = module;
});

export default translations;```  

icai avatar May 13 '25 17:05 icai

@BobbieGoede In 9.x I can use

// Import JSON files
const modules = import.meta.glob('./en/*.json', { eager: true });

// Specify a more concrete JSON format type
const translations: Record<string, any> = {};

// Process each JSON module
Object.entries(modules).forEach(([path, module]) => {
  const moduleName = path.replace('./en/', '').replace('.json', '');
  translations[moduleName] = module;
});

export default translations;

but In 10.x version caught error

 ERROR  [uncaughtException] globalThis._importMeta_.glob is not a function

    at .nuxt/dev/index.mjs:112516:43
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async loadESM (node:internal/process/esm_loader:34:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)
ℹ Error: Cannot access 'renderer$1' before initialization

 ⁃ (.nuxt/dev/index.mjs:111715:65)

   111710 ┃      components: {}
   111711 ┃    };
   111712 ┃    return ctx;
   111713 ┃  }
   111714 ┃  
 ❯ 111715 ┃  const _lazy_aocgtT = () => Promise.resolve().then(function () { return renderer$1; });
   111716 ┃  
   111717 ┃  const handlers = [
   111718 ┃    { route: '/__nuxt_error', handler: _lazy_aocgtT, lazy: true, middleware: false, method: undefined },
   111719 ┃    { route: '', handler: _x4Lo86, lazy: false, middleware: false, method: undefined },
   111720 ┃    { route: '/_i18n/:locale/messages.json', handler: _v_3KeN, lazy: false, middleware: false, method: undefined },

 ⁃ at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
 ⁃ (async file://node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:2003:19)
 ⁃ at async Object.callAsync (node_modules/.pnpm/[email protected]/node_modules/unctx/dist/index.mjs:72:16)
 ⁃ at async Server.toNodeHandle (node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:2295:7)

icai avatar Jul 12 '25 10:07 icai

@icai Could you open a new issue with a minimal reproduction? It sounds like a regression and it should be addressed.

BobbieGoede avatar Jul 14 '25 17:07 BobbieGoede

@BobbieGoede In 9.x I can use

// Import JSON files const modules = import.meta.glob('./en/*.json', { eager: true });

// Specify a more concrete JSON format type const translations: Record<string, any> = {};

// Process each JSON module Object.entries(modules).forEach(([path, module]) => { const moduleName = path.replace('./en/', '').replace('.json', ''); translations[moduleName] = module; });

export default translations; but In 10.x version caught error

 ERROR  [uncaughtException] globalThis._importMeta_.glob is not a function

    at .nuxt/dev/index.mjs:112516:43
    at ModuleJob.run (node:internal/modules/esm/module_job:218:25)
    at async ModuleLoader.import (node:internal/modules/esm/loader:329:24)
    at async loadESM (node:internal/process/esm_loader:34:7)
    at async handleMainPromise (node:internal/modules/run_main:113:12)
ℹ Error: Cannot access 'renderer$1' before initialization

 ⁃ (.nuxt/dev/index.mjs:111715:65)

   111710 ┃      components: {}
   111711 ┃    };
   111712 ┃    return ctx;
   111713 ┃  }
   111714 ┃  
 ❯ 111715 ┃  const _lazy_aocgtT = () => Promise.resolve().then(function () { return renderer$1; });
   111716 ┃  
   111717 ┃  const handlers = [
   111718 ┃    { route: '/__nuxt_error', handler: _lazy_aocgtT, lazy: true, middleware: false, method: undefined },
   111719 ┃    { route: '', handler: _x4Lo86, lazy: false, middleware: false, method: undefined },
   111720 ┃    { route: '/_i18n/:locale/messages.json', handler: _v_3KeN, lazy: false, middleware: false, method: undefined },

 ⁃ at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
 ⁃ (async file://node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:2003:19)
 ⁃ at async Object.callAsync (node_modules/.pnpm/[email protected]/node_modules/unctx/dist/index.mjs:72:16)
 ⁃ at async Server.toNodeHandle (node_modules/.pnpm/[email protected]/node_modules/h3/dist/index.mjs:2295:7)

I encountered the same issue: the code works in v9, and in v10 it works during development, but when I build and preview the project using the preview command, I get the same error.

Here is my code:

// en-US.ts

export default defineI18nLocale(() => {
  const importAllTranslations = import.meta.glob(
    ['./en-US/*.ts'],
    { eager: true, import: 'default' }
  );

  const combinedTranslations = Object.values(importAllTranslations).reduce(
    (acc: any, cur: any) => Object.assign(acc, cur),
    {},
  );

  return combinedTranslations;
});

Red-Asuka avatar Oct 09 '25 07:10 Red-Asuka

import.meta.glob does not work with nitro (v2) https://github.com/nitrojs/nitro/issues/1671, this limitation was less noticeable in nuxt-i18n v9 since messages did not necessarily load from the nitro side, which does happen by default in v10.

BobbieGoede avatar Oct 26 '25 19:10 BobbieGoede