npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@cdmbase/vite-plugin-i18next-loader

v2.0.12

Published

Vite plugin loader for client embedded i18next locales composited from one to many json or yaml files.

Downloads

535

Readme

MIT Version CI PRs Welcome

vite-plugin-i18next-loader

yarn add -D vite-plugin-i18next-loader

Vite plugin to client bundle i18next locales composited from one to many json/yaml files from one to many libraries. Zero config HMR support included.

This vite-plugin i18next loader generates the resources structure necessary for i18next. The structure is made available as a virtual module to the client bundle at build time, thus avoiding loading any language resources via extra HTTP requests.

Features

  • [x] glob based file filtering
  • [x] one to many overrides supporting reuse cases (white labeling)
  • [x] yaml and json support
  • [x] hot module reloading (Basic with full reload works, HMR may be improved with vite 3.2 api - see #5)
  • [ ] chunking/tree shaking may already be possible, see #4 - needs more trial/discussion.

Given a locales directory, by default, the loader will find and parse any json|yaml|yml file and attribute the contents to the containing lang folder e.g. en. There is no need to add lang such as en or de inside your json or yaml files.

See the test/data directory for structure and example data.

Usage

Sample app structure

└── app
    └── src
    │  └── index.js
    └── locales
       ├── de
       │   ├── foo.json
       │   └── bar.yaml
       └── en
           ├── foo.json
           └── bar.yaml

vite.config.ts

import { defineConfig } from 'vite'
import i18nextLoader from 'vite-plugin-i18next-loader'

export default defineConfig({
  plugins: [i18nextLoader({ paths: ['./node_modules/foo/locales', './locales'] })],
})

app.ts

// File: app.ts
import i18n from 'i18next'
import resources from 'virtual:i18next-loader'

i18n.init({
  resources,
})

// Use the resources as documented on i18next.com
i18n.t('key')

Options

export interface Options {
  /**
   * Set to 'info' for noisy information.
   *
   * Default: 'warn'
   */
  logLevel?: LogLevel

  /**
   * Glob patterns to match files
   *
   * Default: ['**\/*.json', '**\/*.yml', '**\/*.yaml']
   */
  include?: string[]

  /**
   * Locale top level directory paths ordered from least specialized to most specialized
   *  e.g. lib locale -> app locale
   *
   * Locales loaded later will overwrite any duplicated key via a deep merge strategy.
   */
  paths: string[]

  /**
   * Default: none
   */
  namespaceResolution?: 'basename' | 'relativePath'
}

include to filtering files read

You can filter files in your file structure by specifying any glob supported by glob-all. By default, any json|yaml|yml in the paths directories will be loaded.

Only json

{
  include: ['**/*.json']
}

All json except one file

{
  include: ['**/*.json', '!**/excludeThis.json']
}

paths for overriding/white labeling

Applications that reuse libraries e.g. white labeling, can utilize one to many sets of locale directories that the app will override.

{
  paths: ['../node_modules/lib1/locales', './locales'] // from least to most specialized
}

This configures the loader to work on a file structure like the following:

└── app
    ├── src
    │  └── app.js
    ├── locales
    │  └── en
    │      ├── foo.json
    │      └── bar.yaml
    └── node_modules
        └── lib1
            └── locales
               └── en
                   ├── foo.json
                   └── bar.yaml

Everything from ./locales will override anything specified in one to many libraries.

namespaceResolution

Namespace resolution will impact the structure of the bundle. If you want the files' basename or relative path to be injected, look at the following options.

namespaceResolution: 'basename'

{
  namespaceResolution: 'basename'
}

The following file structure would result in resources loaded as below:

└── app
    ├── src
    │  └── index.js
    └── locales
       └── en
           ├── foo.json
           └── bar.yaml

foo.json

{
  "header": {
    "title": "TITLE"
  }
}

bar.yaml

footer:
  aboutUs: About us

Results in this object loaded:

{
  "en": {
    "foo": {
      "header": {
        "title": "TITLE"
      }
    },
    "bar": {
      "footer": {
        "aboutUs": "About us"
      }
    }
  }
}

namespaceResolution: 'relativePath'

{
  namespaceResolution: 'relativePath'
}

The following file structure would result in resources loaded as below:

└── app
    └── locales
       ├── index.js
       └── en
           ├── green.yaml
           ├── blue
           ├──── foo.yaml

green.yaml

tree:
  species: Oak

blue/foo.yaml

water:
  ocean: Quite large

Results in this object loaded:

{
  "en": {
    "green": {
      "tree": {
        "species": "Oak"
      }
    },
    "blue": {
      "foo": {
        "water": {
          "ocean": "Quite large"
        }
      }
    }
  }
}

NOTE: If you have a file and a folder with the same name, you MIGHT overwrite one with the other. For example:

└── app
    └── locales
       ├── index.js
       └── en
           ├── blue.yaml
           ├── blue
           ├──── foo.yaml

blue.yaml

foo: Welcome

blue/foo.yaml

eggs: delicious

Results in this object loaded:

{
  "en": {
    "blue": {
      "foo": {
        "eggs": "delicious"
      }
    }
  }
}

But it's just overwriting based on the return value of glob-all, so you shouldn't depend on it.

Output

Note that the virtual module generated has contents that conform to the i18next resource format.

While using the output with import resources from 'virtual:i18next-loader' will not be tree-shaken, it is possible to use the named outputs with a dynamic import for tree shaking/chunking optimizations. If you take advantage of this, please see #4 and take a moment to update this doc with more information.

NOTE as shown by the test output below, due to ES syntactical rules, we cannot use hyphenated lang codes. I'm open to ideas, but in the interim, affected lang codes are exported with the hyphen converted to underscore e.g. zh-cn has a named export of zh_cn. I noted that vite allows for tree-shaking of JSON files, perhaps that is worth looking at to consider how it might help us and inform our output?

export const en = {
  foo: { test: 'app foo.test en' },
  main: {
    test: 'app test en',
    sub: {
      slug: 'app sub.slug en',
      test: 'lib sub.test en',
      subsub: { slugslug: 'app sub.subsub.slugsub en', test: 'lib sub.subsub.test en' },
    },
  },
}
export const zh_cn = {
  foo: { test: 'app foo.test zh-cn' },
  main: {
    test: 'app test zh-cn',
    sub: {
      slug: 'app sub.slug zh-cn',
      test: 'lib sub.test zh-cn',
      subsub: { slugslug: 'app sub.subsub.slugsub zh-cn', test: 'lib sub.subsub.test zh-cn' },
    },
  },
}
const resources = {
  en,
  'zh-cn': zh_cn,
}
export default resources

Vite typescript definitions

In order for the vite virtual module to be typechecked, you will need to a declaration. Below is an example of a common type file included in a project for vite:

// https://vitejs.dev/guide/api-hmr.html
interface ViteHotContext {
  readonly data: any

  // accept(): void
  accept(cb?: (mod: ModuleNamespace | undefined) => void): void
  accept(dep: string, cb: (mod: ModuleNamespace | undefined) => void): void
  accept(deps: readonly string[], cb: (mods: Array<ModuleNamespace | undefined>) => void): void

  dispose(cb: (data: any) => void): void
  decline(): void
  invalidate(): void

  // `InferCustomEventPayload` provides types for built-in Vite events
  on<T extends string>(event: T, cb: (payload: InferCustomEventPayload<T>) => void): void
  send<T extends string>(event: T, data?: InferCustomEventPayload<T>): void
}

// Allow for virtual module imports
// https://vitejs.dev/guide/api-plugin.html#virtual-modules-convention
declare module 'virtual:*'

Credit

This was forked from @alienfast/i18next-loader, converted to be a vite plugin and improved. Thanks to the original authors and contributors.