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.
This was forked from alienfast/vite-plugin-i18next-loader, simplified options and better vite HMR support. Thanks to the original authors and contributors.
npm add -D @kainstar/vite-plugin-i18next-loader
pnpm add -D @kainstar/vite-plugin-i18next-loader
yarn add -D @kainstar/vite-plugin-i18next-loader
- Glob based file filtering
- YAML and JSON support
- HMR support
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.
└── app
└── src
│ └── index.js
└── locales
├── de
│ ├── foo.json
│ └── bar.yaml
└── en
├── foo.json
└── bar.yaml
import { defineConfig } from 'vite';
import i18nextLoader from '@kainstar/vite-plugin-i18next-loader';
export default defineConfig({
plugins: [i18nextLoader({ paths: ['./node_modules/foo/locales', './locales'] })],
});
// 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');
export interface Options {
/**
* Enable debug logging
*/
debug?: boolean;
/**
* 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[];
}
You can filter files in your file structure by specifying any glob supported by globby
. By default, any json|yaml|yml
in the paths
directories will be loaded.
{
include: ['**/*.json'];
}
{
include: ['**/*.json', '!**/excludeThis.json'];
}
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.
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 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;
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:
declare module 'virtual:i18next-loader' {
declare const resources: import('i18next').Resource;
export default resources;
}
If you are using i18n-ally in your project, you can configure it to use the same file structure as this plugin.
{
"i18n-ally.namespace": true,
"i18n-ally.pathMatcher": "{locale}/{namespaces}.{ext}",
"i18n-ally.keystyle": "nested",
"i18n-ally.extract.keyPrefix": "{fileNameWithoutExt}"
}