The plugin supports three resource loading methods: HTTP backend, File System backend (FS Backend), and SDK backend. Additionally, the plugin supports chained backend, which allows combining multiple backends.
HTTP backend loads resource files through HTTP requests, suitable for client-side rendering (CSR) scenarios.
i18nPlugin({
backend: {
enabled: true,
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});Resource files need to be placed in the config/public directory or the directory configured through server.publicDir:
config/public/
└── locales/
├── en/
│ └── translation.json
└── zh/
└── translation.jsonOr configure a custom directory through server.publicDir:
export default defineConfig({
server: {
publicDir: './locales', // Specify resource file directory
},
plugins: [
i18nPlugin({
backend: {
enabled: true,
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
}),
],
});Resource File Format:
{
"key1": "value1",
"key2": "value2",
"nested": {
"key": "value"
}
}loadPath supports the following variables:
{{lng}}: Language code (e.g., en, zh){{ns}}: Namespace (e.g., translation, common)Examples:
// Default path format
loadPath: '/locales/{{lng}}/{{ns}}.json';
// Actual loading paths:
// /locales/en/translation.json
// /locales/zh/translation.json
// Custom path format
loadPath: '/i18n/{{lng}}/{{ns}}.json';
// Actual loading paths:
// /i18n/en/translation.json
// /i18n/zh/translation.jsonFile System backend reads resource files directly from the file system, suitable for server-side rendering (SSR) scenarios.
In SSR scenarios, the plugin will automatically use the file system backend. Resource files need to be placed in the project directory:
Project Root/
└── locales/
├── en/
│ └── translation.json
└── zh/
└── translation.jsonThe default path format for file system backend is a relative path:
./locales/{{lng}}/{{ns}}.jsonYou can customize the path through loadPath:
i18nPlugin({
backend: {
enabled: true,
loadPath: '/locales/{{lng}}/{{ns}}.json', // Use absolute path (recommended)
},
});
The loadPath configuration is used for both HTTP backend (frontend) and file system backend (server-side). If configured as an absolute path starting with / (e.g., /locales/{{lng}}/{{ns}}.json), the file system backend will automatically convert it to a relative path (./locales/{{lng}}/{{ns}}.json). Therefore, it's recommended to use absolute paths in the configuration, which can meet both frontend and backend requirements.
SDK backend allows loading resources through custom functions, suitable for scenarios where translation resources need to be loaded from external services, databases, or other custom sources.
Step 1: Enable SDK mode in modern.config.ts
i18nPlugin({
backend: {
enabled: true,
sdk: true, // Enable SDK mode
},
});Step 2: Implement SDK function in modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
import type { I18nSdkLoader, Resources } from '@modern-js/plugin-i18n/runtime';
const mySdkLoader: I18nSdkLoader = async options => {
// Implement resource loading logic
if (options.all) {
// Load all resources
return await loadAllResources();
}
if (options.lng && options.ns) {
// Load single resource
return await loadSingleResource(options.lng, options.ns);
}
return {};
};
export default defineRuntimeConfig({
i18n: {
initOptions: {
backend: {
sdk: mySdkLoader,
},
},
},
});The SDK function receives an I18nSdkLoadOptions parameter and needs to return data in Resources format:
interface I18nSdkLoadOptions {
/** Single language code */
lng?: string;
/** Single namespace */
ns?: string;
/** Multiple language codes */
lngs?: string[];
/** Multiple namespaces */
nss?: string[];
/** Load all resources */
all?: boolean;
}
type Resources = {
[lng: string]: {
[ns: string]: Record<string, string>;
};
};SDK backend supports multiple loading modes:
1. Load single resource:
const sdkLoader: I18nSdkLoader = async options => {
if (options.lng && options.ns) {
const response = await fetch(`/api/i18n/${options.lng}/${options.ns}`);
const data = await response.json();
return {
[options.lng]: {
[options.ns]: data,
},
};
}
return {};
};2. Batch load multiple languages:
const sdkLoader: I18nSdkLoader = async options => {
if (options.lngs && options.ns) {
const resources: Resources = {};
for (const lng of options.lngs) {
const response = await fetch(`/api/i18n/${lng}/${options.ns}`);
resources[lng] = {
[options.ns]: await response.json(),
};
}
return resources;
}
return {};
};3. Batch load multiple namespaces:
const sdkLoader: I18nSdkLoader = async options => {
if (options.lng && options.nss) {
const resources: Resources = {
[options.lng]: {},
};
for (const ns of options.nss) {
const response = await fetch(`/api/i18n/${options.lng}/${ns}`);
resources[options.lng][ns] = await response.json();
}
return resources;
}
return {};
};4. Load all resources:
const sdkLoader: I18nSdkLoader = async options => {
if (options.all) {
const response = await fetch('/api/i18n/all');
return await response.json();
}
return {};
};When using SDK backend, you can check if resources are loaded using isResourcesReady:
import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
function MyComponent() {
const { isResourcesReady } = useModernI18n();
if (!isResourcesReady) {
return <div>Loading translation resources...</div>;
}
return <div>Resources are ready!</div>;
}This is particularly useful when resources are loaded asynchronously, as it ensures all required namespaces for the current language are loaded before rendering content that depends on translations.
Chained backend allows using multiple backends simultaneously, enabling progressive resource loading and updates. When both loadPath (or FS backend) and sdk are configured, the plugin automatically uses i18next-chained-backend to chain resource loading.
The chained backend workflow:
This ensures users see page content quickly while the latest translation resources are loaded in the background.
Step 1: Configure chained backend in modern.config.ts
i18nPlugin({
backend: {
enabled: true,
loadPath: '/locales/{{lng}}/{{ns}}.json', // HTTP/FS backend
sdk: true, // SDK backend
// cacheHitMode: 'refreshAndUpdateStore', // Default value, can be omitted
},
});Step 2: Implement SDK function in modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig({
i18n: {
initOptions: {
backend: {
sdk: async options => {
// SDK implementation
if (options.lng && options.ns) {
return await mySdk.getResource(options.lng, options.ns);
}
},
},
},
},
});The cacheHitMode option controls the behavior of chained backend:
'none' (default, only when chained backend is not configured): If the first backend returns resources, stop and don't try the next backend'refresh': Try to refresh the cache by loading from the next backend and update the cache'refreshAndUpdateStore' (default for chained backend): Try to refresh the cache by loading from the next backend, update the cache and also update the i18next resource store. This allows FS/HTTP resources to be displayed first, then SDK resources will update them asynchronously.Configuration example:
i18nPlugin({
backend: {
enabled: true,
loadPath: '/locales/{{lng}}/{{ns}}.json',
sdk: true,
cacheHitMode: 'refreshAndUpdateStore', // Explicitly specify (default value)
},
});Chained backend is particularly suitable for the following scenarios:
// modern.config.ts
i18nPlugin({
backend: {
enabled: true,
loadPath: '/locales/{{lng}}/{{ns}}.json', // Local resources
sdk: true, // Remote SDK resources
cacheHitMode: 'refreshAndUpdateStore',
},
});
// modern.runtime.ts
import { defineRuntimeConfig } from '@modern-js/runtime';
export default defineRuntimeConfig({
i18n: {
initOptions: {
backend: {
sdk: async options => {
if (options.lng && options.ns) {
// Load latest translations from remote service
const response = await fetch(
`https://api.example.com/i18n/${options.lng}/${options.ns}`,
);
return {
[options.lng]: {
[options.ns]: await response.json(),
},
};
}
return {};
},
},
},
},
});In this example:
/locales/{{lng}}/{{ns}}.json and displayed immediatelyhttps://api.example.com/i18n/... in the background