资源加载

插件支持三种资源加载方式:HTTP 后端、文件系统后端(FS Backend)和 SDK 后端。同时,插件还支持链式后端,可以组合使用多个后端。

HTTP 后端

HTTP 后端通过 HTTP 请求加载资源文件,适用于客户端渲染(CSR)场景。

配置方式

i18nPlugin({
  backend: {
    enabled: true,
    loadPath: '/locales/{{lng}}/{{ns}}.json',
  },
});

资源文件结构

资源文件需要放在 config/public 目录或通过 server.publicDir 配置的目录中:

config/public/
└── locales/
    ├── en/
    │   └── translation.json
    └── zh/
        └── translation.json

或者通过 server.publicDir 配置自定义目录:

export default defineConfig({
  server: {
    publicDir: './locales', // 指定资源文件目录
  },
  plugins: [
    i18nPlugin({
      backend: {
        enabled: true,
        loadPath: '/locales/{{lng}}/{{ns}}.json',
      },
    }),
  ],
});

资源文件格式

{
  "key1": "value1",
  "key2": "value2",
  "nested": {
    "key": "value"
  }
}

路径变量

loadPath 支持以下变量:

  • {{lng}}:语言代码(如 enzh
  • {{ns}}:命名空间(如 translationcommon

示例

// 默认路径格式
loadPath: '/locales/{{lng}}/{{ns}}.json';
// 实际加载路径:
// /locales/en/translation.json
// /locales/zh/translation.json

// 自定义路径格式
loadPath: '/i18n/{{lng}}/{{ns}}.json';
// 实际加载路径:
// /i18n/en/translation.json
// /i18n/zh/translation.json

文件系统后端(FS Backend)

文件系统后端直接从文件系统读取资源文件,适用于服务端渲染(SSR)场景。

配置方式

在 SSR 场景下,插件会自动使用文件系统后端。资源文件需要放在项目目录中:

项目根目录/
└── locales/
    ├── en/
    │   └── translation.json
    └── zh/
        └── translation.json

资源文件路径

文件系统后端的默认路径格式为相对路径:

./locales/{{lng}}/{{ns}}.json

可以通过 loadPath 自定义路径:

i18nPlugin({
  backend: {
    enabled: true,
    loadPath: '/locales/{{lng}}/{{ns}}.json', // 使用绝对路径(推荐)
  },
});
Warning

loadPath 配置会同时用于 HTTP 后端(前端)和文件系统后端(服务端)。如果配置的是以 / 开头的绝对路径(如 /locales/{{lng}}/{{ns}}.json),文件系统后端会自动将其转换为相对路径(./locales/{{lng}}/{{ns}}.json)。因此,建议在配置中使用绝对路径,这样可以同时满足前后端的需求。

SDK 后端

SDK 后端允许通过自定义函数加载资源,适用于需要从外部服务、数据库或其他自定义来源加载翻译资源的场景。

启用 SDK 模式

步骤 1:在 modern.config.ts 中启用 SDK 模式

i18nPlugin({
  backend: {
    enabled: true,
    sdk: true, // 启用 SDK 模式
  },
});

步骤 2:在 modern.runtime.ts 中实现 SDK 函数

import { defineRuntimeConfig } from '@modern-js/runtime';
import type { I18nSdkLoader, Resources } from '@modern-js/plugin-i18n/runtime';

const mySdkLoader: I18nSdkLoader = async options => {
  // 实现资源加载逻辑
  if (options.all) {
    // 加载所有资源
    return await loadAllResources();
  }

  if (options.lng && options.ns) {
    // 加载单个资源
    return await loadSingleResource(options.lng, options.ns);
  }

  return {};
};

export default defineRuntimeConfig({
  i18n: {
    initOptions: {
      backend: {
        sdk: mySdkLoader,
      },
    },
  },
});

实现 SDK 加载函数

SDK 函数接收一个 I18nSdkLoadOptions 参数,需要返回 Resources 格式的数据:

interface I18nSdkLoadOptions {
  /** 单个语言代码 */
  lng?: string;
  /** 单个命名空间 */
  ns?: string;
  /** 多个语言代码 */
  lngs?: string[];
  /** 多个命名空间 */
  nss?: string[];
  /** 加载所有资源 */
  all?: boolean;
}

type Resources = {
  [lng: string]: {
    [ns: string]: Record<string, string>;
  };
};

批量加载示例

SDK 后端支持多种加载模式:

1. 加载单个资源

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. 批量加载多个语言

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. 批量加载多个命名空间

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. 加载所有资源

const sdkLoader: I18nSdkLoader = async options => {
  if (options.all) {
    const response = await fetch('/api/i18n/all');
    return await response.json();
  }
  return {};
};

检查资源加载状态

使用 SDK 后端时,可以使用 isResourcesReady 检查资源是否已加载:

import { useModernI18n } from '@modern-js/plugin-i18n/runtime';

function MyComponent() {
  const { isResourcesReady } = useModernI18n();

  if (!isResourcesReady) {
    return <div>正在加载翻译资源...</div>;
  }

  return <div>资源已准备好!</div>;
}

这在资源异步加载时特别有用,因为它确保当前语言的所有必需命名空间都已加载完成,然后再渲染依赖翻译的内容。

链式后端(Chained Backend)

链式后端允许同时使用多个后端,实现资源的渐进式加载和更新。当同时配置 loadPath(或 FS 后端)和 sdk 时,插件会自动使用 i18next-chained-backend 来链式加载资源。

工作原理

链式后端的工作流程:

  1. 初始加载:首先从 HTTP/FS 后端加载资源并立即显示(快速显示基础翻译)
  2. 异步更新:然后从 SDK 后端异步加载资源并更新 i18next store(更新/补充翻译)

这样可以确保用户快速看到页面内容,同时后台加载最新的翻译资源。

配置方式

步骤 1:在 modern.config.ts 中配置链式后端

i18nPlugin({
  backend: {
    enabled: true,
    loadPath: '/locales/{{lng}}/{{ns}}.json', // HTTP/FS 后端
    sdk: true, // SDK 后端
    // cacheHitMode: 'refreshAndUpdateStore', // 默认值,可省略
  },
});

步骤 2:在 modern.runtime.ts 中实现 SDK 函数

import { defineRuntimeConfig } from '@modern-js/runtime';

export default defineRuntimeConfig({
  i18n: {
    initOptions: {
      backend: {
        sdk: async options => {
          // SDK 实现
          if (options.lng && options.ns) {
            return await mySdk.getResource(options.lng, options.ns);
          }
        },
      },
    },
  },
});

缓存命中模式(cacheHitMode)

cacheHitMode 选项控制链式后端的行为:

  • 'none'(默认,仅当未配置链式后端时):如果第一个后端返回了资源,则停止,不再尝试下一个后端
  • 'refresh':尝试从下一个后端刷新缓存并更新缓存
  • 'refreshAndUpdateStore'(链式后端的默认值):尝试从下一个后端刷新缓存,更新缓存并同时更新 i18next 资源存储。这允许先显示 FS/HTTP 资源,然后 SDK 资源会异步更新它们。

配置示例

i18nPlugin({
  backend: {
    enabled: true,
    loadPath: '/locales/{{lng}}/{{ns}}.json',
    sdk: true,
    cacheHitMode: 'refreshAndUpdateStore', // 显式指定(默认值)
  },
});

使用场景

链式后端特别适用于以下场景:

  1. 渐进式加载:先显示本地/静态资源,然后从远程服务加载最新翻译
  2. 离线支持:本地资源作为离线 fallback,SDK 资源提供在线更新
  3. 性能优化:快速显示基础翻译,后台加载完整翻译内容
  4. A/B 测试:本地资源作为默认值,SDK 提供动态翻译变体

完整示例

// modern.config.ts
i18nPlugin({
  backend: {
    enabled: true,
    loadPath: '/locales/{{lng}}/{{ns}}.json', // 本地资源
    sdk: true, // 远程 SDK 资源
    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) {
            // 从远程服务加载最新翻译
            const response = await fetch(
              `https://api.example.com/i18n/${options.lng}/${options.ns}`,
            );
            return {
              [options.lng]: {
                [options.ns]: await response.json(),
              },
            };
          }
          return {};
        },
      },
    },
  },
});

在这个示例中:

  • 页面加载时,首先从 /locales/{{lng}}/{{ns}}.json 加载资源并立即显示
  • 后台异步从 https://api.example.com/i18n/... 加载最新翻译
  • SDK 资源加载完成后,自动更新 i18next store,界面文案自动更新