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 @@ -38,12 +38,8 @@ interface UseModernI18nReturn {
import { useModernI18n } from '@modern-js/plugin-i18n/runtime';

function LanguageSwitcher() {
const {
language,
changeLanguage,
supportedLanguages,
isLanguageSupported,
} = useModernI18n();
const { language, changeLanguage, supportedLanguages, isLanguageSupported } =
useModernI18n();

return (
<div>
Expand Down Expand Up @@ -90,6 +86,7 @@ await changeLanguage('zh');

:::info
`changeLanguage` is an async function that returns a Promise.

:::

### Language Support Check
Expand Down Expand Up @@ -134,9 +131,11 @@ function MyComponent() {

:::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
Expand Down Expand Up @@ -267,14 +266,27 @@ interface I18nInitOptions {

```ts
interface LanguageDetectorOptions {
/** Detection order */
order?: string[];
/** Query parameter key name, default 'lng' */
lookupQuerystring?: string;
/** Cookie key name, default 'i18next' */
lookupCookie?: string;
/** LocalStorage key name, default 'i18nextLng' (browser only) */
lookupLocalStorage?: string;
/** SessionStorage key name (browser only) */
lookupSession?: string;
/** Starting index in path for language detection, default 0 */
lookupFromPathIndex?: number;
/** Cache method, can be false or string array (e.g., ['cookie', 'localStorage']) */
caches?: boolean | string[];
/** Cookie expiration time (minutes) */
cookieMinutes?: number;
/** Cookie expiration date (Date object, takes precedence over cookieMinutes) */
cookieExpirationDate?: Date;
/** Cookie domain */
cookieDomain?: string;
/** Request header key name, default 'accept-language' */
lookupHeader?: string;
}
```
Expand All @@ -289,3 +301,7 @@ type Resources = {
};
```

:::info
The namespace value can be a string (for simple key-value pairs) or an object (for nested translation structures).

:::
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ interface BaseLocaleDetectionOptions {

/** Custom detection configuration */
detection?: LanguageDetectorOptions;

/** Routes to ignore automatic redirection (array of path patterns or function)
*
* Can be a string array (path patterns) or a function to determine if redirection should be ignored.
* Supports exact match and prefix match (e.g., '/api' will match '/api' and '/api/users').
*
* @example
* // String array
* ignoreRedirectRoutes: ['/api', '/admin']
*
* // Function
* ignoreRedirectRoutes: (pathname) => pathname.startsWith('/api')
*/
ignoreRedirectRoutes?: string[] | ((pathname: string) => boolean);
}

interface LocaleDetectionOptions extends BaseLocaleDetectionOptions {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,25 +133,34 @@ i18nPlugin({

## Detection Priority

When multiple detection methods are enabled, the detection priority follows the order configured in `detection.order`:
The plugin's language detection follows the following priority order (from highest to lowest):

1. **Path Detection** (if `localePathRedirect` is `true`)
2. **i18next Detector** (following the order configured in `order`)
3. **Fallback Language** (`fallbackLanguage`)
1. **SSR Data** (highest priority): Read language from `window._SSR_DATA` set during server-side rendering, applicable to both SSR and CSR projects
2. **Path Detection**: If `localePathRedirect` is `true`, detect language prefix from URL path
3. **i18next Detector**: Execute detection according to the order configured in `detection.order` (Cookie, LocalStorage, query parameters, request headers, etc.)
4. **User Configured Language**: Use the language configured in `initOptions.lng`
5. **Fallback Language**: Use `fallbackLanguage` as the final fallback

:::info
SSR data detection has the highest priority to ensure the client uses the language detected during server-side rendering, avoiding language flickering issues caused by client-side re-detection.
:::

**Example**:

```ts
// Configured detection order
// Configured detection order (only affects priority within i18next detector)
detection: {
order: ['path', 'cookie', 'querystring', 'header'],
}

// Actual detection flow:
// 1. First check URL path (if localePathRedirect is enabled)
// 2. Then check Cookie
// 3. Then check query parameters
// 4. Then check request headers
// 1. First check SSR data (window._SSR_DATA)
// 2. Then check URL path (if localePathRedirect is enabled)
// 3. Then check i18next detector according to order:
// - Cookie
// - Query parameters
// - Request headers
// 4. Then use initOptions.lng (if configured)
// 5. Finally use fallbackLanguage
```

Expand All @@ -172,7 +181,7 @@ Specifies the order of language detection, optional values:
- `subdomain`: Detect from subdomain

:::warning
`path` detection requires `localePathRedirect` to be `true`. `localStorage`, `navigator`, and `htmlTag` are only available in browser environments.
`path` detection requires `localePathRedirect` to be `true`. `localStorage`, `sessionStorage`, `navigator`, and `htmlTag` are only available in browser environments.
:::

### caches (Cache Method)
Expand All @@ -184,11 +193,84 @@ Specifies where the detected language should be cached, optional values:
- `['localStorage']`: Cache to LocalStorage (browser only)
- `['cookie', 'localStorage']`: Cache to both Cookie and LocalStorage

### lookupQuerystring, lookupCookie, lookupHeader
### lookupQuerystring, lookupCookie, lookupLocalStorage, lookupSession, lookupHeader

Specifies the key name used when reading language from query parameters, cookies, or request headers:
Specifies the key name used when reading language from query parameters, cookies, LocalStorage, SessionStorage, or request headers:

- `lookupQuerystring`: Default `'lng'`, e.g., `?lng=en`
- `lookupCookie`: Default `'i18next'`
- `lookupLocalStorage`: Default `'i18nextLng'` (browser only)
- `lookupSession`: SessionStorage key name (browser only)
- `lookupHeader`: Default `'accept-language'`

### lookupFromPathIndex

Specifies which position in the URL path to start detecting language (when `'path'` is included in `order`):

- `lookupFromPathIndex`: Path segment index, defaults to `0` (first path segment)

**Example**:

```ts
// URL: /api/v1/en/users
// If lookupFromPathIndex = 2, detection starts from the third path segment ('en')
detection: {
order: ['path'],
lookupFromPathIndex: 2,
}
```

### cookieMinutes, cookieExpirationDate

Controls Cookie expiration time:

- `cookieMinutes`: Cookie expiration time (minutes), default `525600` (1 year)
- `cookieExpirationDate`: Cookie expiration date (Date object), takes precedence over `cookieMinutes`

**Example**:

```ts
detection: {
cookieMinutes: 60 * 24 * 7, // Expires in 7 days
// or
cookieExpirationDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000), // Expires in 7 days
}
```

### ignoreRedirectRoutes

Specifies which routes should ignore automatic language redirection. This is very useful for API routes, static resources, and other paths that don't need language prefixes.

**Configuration**:

```ts
i18nPlugin({
localeDetection: {
localePathRedirect: true,
languages: ['zh', 'en'],
fallbackLanguage: 'en',
// String array: supports exact match and prefix match
ignoreRedirectRoutes: ['/api', '/admin', '/static'],
// Or use function for more flexible judgment
ignoreRedirectRoutes: pathname => {
return pathname.startsWith('/api') || pathname.startsWith('/admin');
},
},
});
```

**Matching Rules**:

- String array: Supports exact match (`'/api'`) and prefix match (`'/api'` will match `/api` and `/api/users`)
- Function: Receives pathname (with language prefix removed), returns `true` to indicate ignoring redirection

**Example**:

```ts
// Ignore all API routes and static resources
ignoreRedirectRoutes: ['/api', '/static', '/assets'];

// Use function to ignore all paths starting with /api
ignoreRedirectRoutes: pathname => pathname.startsWith('/api');
```

Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ pnpm add @modern-js/plugin-i18n i18next react-i18next

:::info
`i18next` and `react-i18next` are peer dependencies and need to be installed manually.

:::

## Basic Configuration

### 1. Configure Plugin in `modern.config.ts`
### Configure Plugin in `modern.config.ts`

```ts
import { appTools, defineConfig } from '@modern-js/app-tools';
Expand Down Expand Up @@ -51,9 +52,10 @@ export default defineConfig({

:::info
`server.publicDir` is a required configuration used to specify the actual location of resource files. Even when using the default `loadPath`, this configuration is still required.

:::

### 2. Create Language Resource Files
### Create Language Resource Files

Create a `locales` folder in the project root and organize resource files by language:

Expand Down Expand Up @@ -85,7 +87,7 @@ locales/
}
```

### 3. Use in Components
### Use in Components

```tsx
import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
Expand Down Expand Up @@ -122,4 +124,3 @@ export default App;
- Learn detailed [Configuration](./configuration) instructions
- Learn about multiple [Locale Detection](./locale-detection) methods
- Check [Resource Loading](./resource-loading) configuration options

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

:::warning
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,34 @@ i18nPlugin({
localePathRedirect: true,
languages: ['zh', 'en'],
fallbackLanguage: 'en',
// Optional: ignore automatic redirection for certain routes
ignoreRedirectRoutes: ['/api', '/admin'],
},
});
```

### Ignore Redirection Routes

Some routes (such as API routes, static resources, etc.) don't need language prefixes. You can use the `ignoreRedirectRoutes` configuration to ignore automatic redirection for these routes:

```ts
i18nPlugin({
localeDetection: {
localePathRedirect: true,
languages: ['zh', 'en'],
fallbackLanguage: 'en',
// String array: supports exact match and prefix match
ignoreRedirectRoutes: ['/api', '/admin', '/static'],
// Or use function for more flexible judgment
ignoreRedirectRoutes: pathname => {
return pathname.startsWith('/api') || pathname.startsWith('/admin');
},
},
});
```

For more details, please refer to the `ignoreRedirectRoutes` configuration in the [Locale Detection documentation](./locale-detection#ignoreredirectroutes).

## Route Configuration

After enabling path redirection, you need to add `:lang` dynamic parameter to the route configuration.
Expand Down Expand Up @@ -115,6 +139,7 @@ function App() {

:::info
Convention-based routing will automatically generate corresponding routes based on the file structure. It's recommended to use convention-based routing. Only use custom routing when special route control is needed.

:::

## I18nLink Component
Expand Down Expand Up @@ -185,4 +210,3 @@ interface I18nLinkProps {
Contact
</I18nLink>
```

Loading