Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ interface UseModernI18nReturn {

/** Check if language is supported */
isLanguageSupported: (lang: string) => boolean;

/** Indicates if translation resources for current language are ready to use */
isResourcesReady: boolean;
}
```

Expand Down Expand Up @@ -105,6 +108,37 @@ function handleLanguageChange(lang: string) {
}
```

### Resource Loading State

`isResourcesReady` indicates whether the translation resources for the current language are loaded and ready to use. This is particularly useful when using SDK backend to load resources dynamically.

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

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

if (!isResourcesReady) {
return <div>Loading translation resources...</div>;
}

return <div>Translation resources are ready</div>;
}
```

**When to use:**

- Display a loading state while resources are being loaded
- Prevent rendering content that depends on translations before resources are ready
- Handle resource loading errors gracefully

:::info
`isResourcesReady` automatically checks:
- If i18n instance is initialized
- If any resources for the current language are currently loading (SDK backend only)
- If all required namespaces for the current language are loaded in the store
:::

## I18nLink Component

The `I18nLink` component is used to create links with language prefixes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,38 @@ function MyComponent() {

It's recommended to provide fallback options when resource loading fails:

### Check Resource Loading State

When using SDK backend or need to ensure resources are loaded, use `isResourcesReady` from `useModernI18n`:

```tsx
import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
import { useTranslation } from 'react-i18next';

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

// Check if resources are loaded and ready
if (!isResourcesReady) {
return <div>Loading translation resources...</div>;
}

return <div>{t('content', { defaultValue: 'Default content' })}</div>;
}
```

### Alternative: Check i18next Initialization

For simpler cases, you can also check i18next initialization status:

```tsx
import { useTranslation } from 'react-i18next';

function MyComponent() {
const { t, i18n } = useTranslation();

// Check if resources are loaded
// Check if i18n is initialized
if (!i18n.isInitialized) {
return <div>Loading...</div>;
}
Expand All @@ -64,6 +89,10 @@ function MyComponent() {
}
```

:::tip
`isResourcesReady` is more accurate for SDK backend scenarios as it checks if all required resources are actually loaded, not just if the instance is initialized.
:::

## Type Safety

Add type definitions for translation keys to improve development experience:
Expand Down Expand Up @@ -103,4 +132,3 @@ function MyComponent() {
// return <div>{t('invalid')}</div>; // ❌ TypeScript error
}
```

Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,23 @@ i18nPlugin({

`backend` is used to configure resource loading methods:

:::info
**Auto-detection**: The plugin automatically detects and enables backend in the following scenarios:

1. **If you configure `loadPath` or `addPath`**: The backend will be automatically enabled (`enabled: true`) without checking for locales directory, since you've already specified the resource path.

2. **If you don't configure backend**: The plugin will automatically detect if a `locales` directory exists in:
- Project root: `{projectRoot}/locales`
- Config public directory: `{projectRoot}/config/public/locales`
- Public directory configured via `server.publicDir`: `{projectRoot}/{publicDir}/locales`

If the directory exists and contains JSON files, the backend will be automatically enabled.

3. **If you explicitly set `enabled: false`**: No auto-detection will be performed, and the backend will remain disabled.

This automatic detection helps reduce unnecessary backend registration when locales directory doesn't exist, improving performance.
:::

```ts
interface BaseBackendOptions {
/** Whether to enable backend resource loading */
Expand All @@ -96,10 +113,30 @@ interface BaseBackendOptions {
/** Missing translation save path (optional) */
addPath?: string;

/** Cache hit mode for chained backend (only effective when both `loadPath` and `sdk` are provided)
*
* - `'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.
*
* @default 'refreshAndUpdateStore' when both loadPath and sdk are provided
*/
cacheHitMode?: 'none' | 'refresh' | 'refreshAndUpdateStore';

/** SDK loader function (custom backend)
*
* Note: In CLI configuration, can only be set to `true` or a string identifier to enable SDK mode.
* The actual SDK function must be provided through `initOptions.backend.sdk` in runtime configuration (`modern.runtime.ts`).
*
* When both `loadPath` (or FS backend) and `sdk` are provided, the plugin will automatically use `i18next-chained-backend`
* to chain multiple backends. The loading order will be:
* 1. HTTP/FS backend (primary) - loads from `loadPath` or file system first for quick initial display
* 2. SDK backend (update) - loads from the SDK function to update/refresh translations
*
* With `cacheHitMode: 'refreshAndUpdateStore'` (default), FS/HTTP resources will be displayed immediately,
* then SDK resources will be loaded asynchronously to update the translations.
*/
sdk?: I18nSdkLoader | boolean | string;
}
Expand All @@ -110,7 +147,11 @@ interface BackendOptions extends BaseBackendOptions {
}
```

**Example**:
**Examples**:

**1. HTTP/FS backend only**:

You can explicitly enable backend:

```ts
i18nPlugin({
Expand All @@ -121,6 +162,104 @@ i18nPlugin({
});
```

Or simply configure `loadPath` or `addPath`, and backend will be automatically enabled:

```ts
i18nPlugin({
backend: {
// enabled will be automatically set to true
loadPath: '/locales/{{lng}}/{{ns}}.json',
},
});
```

**Auto-detection without configuration**:

If you don't configure backend at all, the plugin will automatically detect locales directory:

```ts
i18nPlugin({
// No backend config - plugin will auto-detect locales directory
localeDetection: {
languages: ['zh', 'en'],
fallbackLanguage: 'en',
},
});
```

If `locales` directory exists with JSON files, backend will be automatically enabled with default `loadPath: '/locales/{{lng}}/{{ns}}.json'`.

**2. Chained backend (recommended)**: Use both HTTP/FS backend and SDK backend

When `backend.enabled = true` and `sdk` is configured, if `loadPath` is not explicitly configured, the default `loadPath` will be used automatically and chained backend will be enabled:

```ts
i18nPlugin({
backend: {
enabled: true,
// When loadPath is not configured, default '/locales/{{lng}}/{{ns}}.json' will be used
sdk: true, // SDK backend
// cacheHitMode: 'refreshAndUpdateStore', // Default value, can be omitted
},
});
```

You can also explicitly configure `loadPath`:

```ts
i18nPlugin({
backend: {
enabled: true,
loadPath: '/locales/{{lng}}/{{ns}}.json', // HTTP/FS backend
sdk: true, // SDK backend
},
});
```

Provide the SDK function in `modern.runtime.ts`:

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

When using chained backend, the system will:

1. First load resources from `/locales/{{lng}}/{{ns}}.json` and display immediately (quick initial display of basic translations)
2. Then asynchronously load resources from SDK and update the i18next store (update/supplement translations)

This ensures users see page content quickly while the latest translation resources are loaded in the background.

**3. SDK backend only**:

If you need to disable HTTP/FS backend and use only SDK backend, you can explicitly set `loadPath: ''`:

```ts
i18nPlugin({
backend: {
enabled: true,
loadPath: '', // Explicitly disable HTTP/FS backend
sdk: true, // Use SDK backend only
},
});
```

:::warning
When using SDK backend only, you must provide the actual SDK function in `modern.runtime.ts`, otherwise it will fallback to HTTP/FS backend.
:::

### Multi-Entry Configuration

If the project has multiple entries, you can configure each entry separately:
Expand Down Expand Up @@ -279,4 +418,3 @@ export default defineRuntimeConfig({
},
});
```

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export default defineConfig({
backend: {
enabled: true, // Enable backend resource loading
// loadPath defaults to '/locales/{{lng}}/{{ns}}.json', usually no need to modify
// Note: You can also omit 'enabled' and just configure 'loadPath' or 'addPath',
// or omit backend config entirely to let the plugin auto-detect locales directory
},
}),
],
Expand Down
Loading