@bleedingdev/modern-js-plugin-i18n 3.2.0-ultramodern.99 → 3.4.0-ultramodern.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +221 -11
- package/dist/cjs/cli/index.js +17 -64
- package/dist/cjs/cli/locales.js +132 -0
- package/dist/cjs/runtime/I18nLink.js +17 -20
- package/dist/cjs/runtime/Link.js +264 -0
- package/dist/cjs/runtime/canonicalRoutes.js +18 -0
- package/dist/cjs/runtime/context.js +9 -5
- package/dist/cjs/runtime/hooks.js +9 -5
- package/dist/cjs/runtime/i18n/backend/config.js +9 -5
- package/dist/cjs/runtime/i18n/backend/defaults.js +20 -11
- package/dist/cjs/runtime/i18n/backend/defaults.node.js +79 -10
- package/dist/cjs/runtime/i18n/backend/index.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.common.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.js +9 -5
- package/dist/cjs/runtime/i18n/backend/middleware.node.js +9 -5
- package/dist/cjs/runtime/i18n/backend/sdk-backend.js +9 -5
- package/dist/cjs/runtime/i18n/backend/sdk-event.js +16 -11
- package/dist/cjs/runtime/i18n/detection/config.js +9 -5
- package/dist/cjs/runtime/i18n/detection/index.js +9 -5
- package/dist/cjs/runtime/i18n/detection/middleware.js +9 -5
- package/dist/cjs/runtime/i18n/detection/middleware.node.js +9 -5
- package/dist/cjs/runtime/i18n/index.js +9 -5
- package/dist/cjs/runtime/i18n/instance.js +17 -13
- package/dist/cjs/runtime/i18n/react-i18next.js +12 -8
- package/dist/cjs/runtime/i18n/utils.js +9 -5
- package/dist/cjs/runtime/index.js +32 -5
- package/dist/cjs/runtime/localizedPaths.js +102 -0
- package/dist/cjs/runtime/routerAdapter.js +11 -7
- package/dist/cjs/runtime/utils.js +31 -17
- package/dist/cjs/server/index.js +10 -14
- package/dist/cjs/shared/deepMerge.js +12 -8
- package/dist/cjs/shared/detection.js +9 -5
- package/dist/cjs/shared/localisedUrls.js +148 -34
- package/dist/cjs/shared/utils.js +15 -11
- package/dist/esm/cli/index.mjs +8 -48
- package/dist/esm/cli/locales.mjs +80 -0
- package/dist/esm/runtime/I18nLink.mjs +7 -14
- package/dist/esm/runtime/Link.mjs +221 -0
- package/dist/esm/runtime/canonicalRoutes.mjs +0 -0
- package/dist/esm/runtime/i18n/backend/defaults.mjs +6 -2
- package/dist/esm/runtime/i18n/backend/defaults.node.mjs +56 -5
- package/dist/esm/runtime/index.mjs +4 -2
- package/dist/esm/runtime/localizedPaths.mjs +55 -0
- package/dist/esm/runtime/routerAdapter.mjs +3 -3
- package/dist/esm/runtime/utils.mjs +19 -12
- package/dist/esm/server/index.mjs +2 -10
- package/dist/esm/shared/localisedUrls.mjs +115 -23
- package/dist/esm-node/cli/index.mjs +8 -48
- package/dist/esm-node/cli/locales.mjs +81 -0
- package/dist/esm-node/runtime/I18nLink.mjs +7 -14
- package/dist/esm-node/runtime/Link.mjs +222 -0
- package/dist/esm-node/runtime/canonicalRoutes.mjs +1 -0
- package/dist/esm-node/runtime/i18n/backend/defaults.mjs +6 -2
- package/dist/esm-node/runtime/i18n/backend/defaults.node.mjs +56 -5
- package/dist/esm-node/runtime/index.mjs +4 -2
- package/dist/esm-node/runtime/localizedPaths.mjs +56 -0
- package/dist/esm-node/runtime/routerAdapter.mjs +3 -3
- package/dist/esm-node/runtime/utils.mjs +19 -12
- package/dist/esm-node/server/index.mjs +2 -10
- package/dist/esm-node/shared/localisedUrls.mjs +115 -23
- package/dist/types/cli/index.d.ts +1 -0
- package/dist/types/cli/locales.d.ts +17 -0
- package/dist/types/runtime/I18nLink.d.ts +4 -13
- package/dist/types/runtime/Link.d.ts +66 -0
- package/dist/types/runtime/canonicalRoutes.d.ts +60 -0
- package/dist/types/runtime/i18n/backend/defaults.d.ts +10 -7
- package/dist/types/runtime/i18n/backend/defaults.node.d.ts +13 -4
- package/dist/types/runtime/index.d.ts +5 -1
- package/dist/types/runtime/localizedPaths.d.ts +39 -0
- package/dist/types/runtime/types.d.ts +1 -1
- package/dist/types/runtime/utils.d.ts +13 -4
- package/dist/types/shared/localisedUrls.d.ts +23 -0
- package/dist/types/shared/type.d.ts +27 -5
- package/package.json +28 -25
- package/rstest.config.mts +7 -2
- package/src/cli/index.ts +25 -98
- package/src/cli/locales.ts +186 -0
- package/src/runtime/I18nLink.tsx +13 -44
- package/src/runtime/Link.tsx +430 -0
- package/src/runtime/canonicalRoutes.ts +93 -0
- package/src/runtime/i18n/backend/defaults.node.ts +112 -7
- package/src/runtime/i18n/backend/defaults.ts +20 -18
- package/src/runtime/index.tsx +24 -2
- package/src/runtime/localizedPaths.ts +107 -0
- package/src/runtime/routerAdapter.tsx +4 -5
- package/src/runtime/types.ts +1 -1
- package/src/runtime/utils.ts +33 -26
- package/src/server/index.ts +7 -17
- package/src/shared/localisedUrls.ts +256 -26
- package/src/shared/type.ts +27 -5
- package/tests/backendDefaults.test.ts +51 -0
- package/tests/i18nUtils.test.ts +10 -3
- package/tests/link.test.tsx +525 -0
- package/tests/linkTypes.test.ts +28 -0
- package/tests/localisedUrls.test.ts +224 -0
- package/tests/routerAdapter.test.tsx +86 -12
- package/tests/type-fixture/linkTypes.fixture.tsx +51 -0
- package/tests/type-fixture/tsconfig.json +15 -0
package/README.md
CHANGED
|
@@ -2,20 +2,236 @@
|
|
|
2
2
|
<a href="https://modernjs.dev" target="blank"><img src="https://lf3-static.bytednsdoc.com/obj/eden-cn/ylaelkeh7nuhfnuhf/modernjs-cover.png" width="300" alt="Modern.js Logo" /></a>
|
|
3
3
|
</p>
|
|
4
4
|
|
|
5
|
-
<h1 align="center"
|
|
5
|
+
<h1 align="center">@modern-js/plugin-i18n</h1>
|
|
6
6
|
|
|
7
7
|
<p align="center">
|
|
8
|
-
|
|
8
|
+
Internationalization plugin for Modern.js, providing multi-language support with locale-aware routing and navigation.
|
|
9
9
|
</p>
|
|
10
10
|
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- **Locale detection**: Supports detection from URL path, Cookie, LocalStorage, request headers, browser settings, and other sources.
|
|
14
|
+
- **Resource loading**: Supports HTTP static files, file system loading for SSR, custom SDK functions, and chained backend progressive loading.
|
|
15
|
+
- **Routing integration**: Automatically adds locale path prefixes and provides the `Link` component for language-agnostic navigation.
|
|
16
|
+
- **SSR support**: Detects the language on the server and injects it into the page to avoid language flicker.
|
|
17
|
+
- **TypeScript support**: Provides complete type definitions and type-safe route navigation.
|
|
18
|
+
|
|
11
19
|
## Getting Started
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
For full documentation, see the [Modern.js Internationalization Guide](https://modernjs.dev/en/guides/advanced-features/international/).
|
|
22
|
+
|
|
23
|
+
## Link Component
|
|
24
|
+
|
|
25
|
+
The `Link` component is the standard way to create navigation links in i18n-enabled applications. It automatically localizes language-agnostic canonical paths and provides type-safe navigation with params validation.
|
|
26
|
+
|
|
27
|
+
### Basic Usage
|
|
28
|
+
|
|
29
|
+
```tsx
|
|
30
|
+
import { Link } from '@modern-js/plugin-i18n/runtime';
|
|
31
|
+
|
|
32
|
+
function Navigation() {
|
|
33
|
+
return (
|
|
34
|
+
<nav>
|
|
35
|
+
<Link to="/">Home</Link>
|
|
36
|
+
<Link to="/about">About</Link>
|
|
37
|
+
<Link to="/talks/$slug" params={{ slug: 'my-talk' }} />
|
|
38
|
+
<Link to="/#work-with-me" />
|
|
39
|
+
</nav>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Key Features
|
|
45
|
+
|
|
46
|
+
**Canonical path localization**: `to` accepts language-agnostic canonical paths. The Link automatically adds the locale prefix and applies `localisedUrls` slug mappings:
|
|
47
|
+
|
|
48
|
+
```tsx
|
|
49
|
+
// When current language is 'cs' and localisedUrls maps '/platform' to '/platforma':
|
|
50
|
+
<Link to="/platform" />
|
|
51
|
+
// renders as: <a href="/cs/platforma">...</a>
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Hash and query preservation**: Hash fragments and query strings in `to` are preserved during localization:
|
|
55
|
+
|
|
56
|
+
```tsx
|
|
57
|
+
<Link to="/#work-with-me" /> // Cross-page anchor → /cs#work-with-me
|
|
58
|
+
<Link to="/talks?sort=date" /> // Search params preserved
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Typed routes and params**: When used with TanStack Router codegen, `to` accepts typed canonical route paths with validated params:
|
|
62
|
+
|
|
63
|
+
```tsx
|
|
64
|
+
// Route /talks/$slug exists → TypeScript validates this:
|
|
65
|
+
<Link to="/talks/$slug" params={{ slug: 'my-talk' }} />
|
|
66
|
+
|
|
67
|
+
// TypeScript error: route does not exist
|
|
68
|
+
<Link to="/talkz" />
|
|
69
|
+
|
|
70
|
+
// TypeScript error: missing required param
|
|
71
|
+
<Link to="/talks/$slug" />
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Language-invariant active state**: The link is marked active when the location matches any localized variant of the canonical route:
|
|
75
|
+
|
|
76
|
+
```tsx
|
|
77
|
+
// Matches /en/talks/my-talk, /cs/prednaska/muj-pohovor, etc.
|
|
78
|
+
<Link to="/talks/$slug" params={{ slug: 'my-talk' }} activeProps={{ className: 'active' }} />
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
**Active state props and classes**:
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
<Link
|
|
85
|
+
to="/about"
|
|
86
|
+
activeProps={{ className: 'active-link' }}
|
|
87
|
+
activeOptions={{ exact: false }} // Nested routes also match
|
|
88
|
+
/>
|
|
89
|
+
// When active, renders: <a href="..." className="active-link" data-status="active" aria-current="page">
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
**External URLs and bare anchors render as plain `<a>` tags**:
|
|
93
|
+
|
|
94
|
+
```tsx
|
|
95
|
+
<Link to="https://example.com" /> // → <a href="https://example.com">
|
|
96
|
+
<Link to="#contact" /> // → <a href="#contact">
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Props
|
|
100
|
+
|
|
101
|
+
- `to` (string): Canonical (language-agnostic) target path, optionally with `#hash` and `?query` suffixes.
|
|
102
|
+
- `params` (object, optional): Route param values for dynamic segments (e.g., `$slug`).
|
|
103
|
+
- `hash` (string, optional): Hash fragment (overrides any `#hash` in `to`).
|
|
104
|
+
- `search` (string | object, optional): Query string or object (overrides any `?query` in `to`).
|
|
105
|
+
- `hashScrollIntoView` (boolean | ScrollIntoViewOptions, optional): TanStack Router scroll behavior.
|
|
106
|
+
- `activeProps` (object, optional): Props applied when the link is active.
|
|
107
|
+
- `activeOptions` (object, optional): `{ exact?: boolean }` for active state matching.
|
|
108
|
+
- All standard `<a>` HTML attributes (className, style, onClick, etc.).
|
|
109
|
+
|
|
110
|
+
## Localization Utilities
|
|
111
|
+
|
|
112
|
+
### `localizePath(pathname, language, config)`
|
|
113
|
+
|
|
114
|
+
Localize a canonical pathname for a given language, applying language prefix and `localisedUrls` mapping:
|
|
115
|
+
|
|
116
|
+
```tsx
|
|
117
|
+
import { localizePath } from '@modern-js/plugin-i18n/runtime';
|
|
118
|
+
|
|
119
|
+
localizePath('/about', 'cs', {
|
|
120
|
+
languages: ['en', 'cs'],
|
|
121
|
+
localisedUrls: { '/about': { en: '/about', cs: '/o-nas' } }
|
|
122
|
+
})
|
|
123
|
+
// → '/cs/o-nas'
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### `canonicalPath(target, config)`
|
|
127
|
+
|
|
128
|
+
Reverse of `localizePath`: strip language prefix and reverse `localisedUrls` mapping:
|
|
129
|
+
|
|
130
|
+
```tsx
|
|
131
|
+
import { canonicalPath } from '@modern-js/plugin-i18n/runtime';
|
|
132
|
+
|
|
133
|
+
canonicalPath('/cs/o-nas', {
|
|
134
|
+
languages: ['en', 'cs'],
|
|
135
|
+
localisedUrls: { '/about': { en: '/about', cs: '/o-nas' } }
|
|
136
|
+
})
|
|
137
|
+
// → '/about'
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### `useLocalizedPaths()`
|
|
141
|
+
|
|
142
|
+
Hook for context-bound `localizePath` and `canonicalPath` (reads plugin config automatically):
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
import { useLocalizedPaths } from '@modern-js/plugin-i18n/runtime';
|
|
146
|
+
|
|
147
|
+
function MyComponent() {
|
|
148
|
+
const { localizePath, canonicalPath } = useLocalizedPaths();
|
|
149
|
+
|
|
150
|
+
const localized = localizePath('/about', 'cs');
|
|
151
|
+
const canonical = canonicalPath('/cs/o-nas');
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### `useLocalizedLocation()`
|
|
156
|
+
|
|
157
|
+
Hook for hreflang tags and language switchers. Returns the current location's canonical path and per-language hrefs:
|
|
158
|
+
|
|
159
|
+
```tsx
|
|
160
|
+
import { useLocalizedLocation } from '@modern-js/plugin-i18n/runtime';
|
|
161
|
+
|
|
162
|
+
function HrefLang() {
|
|
163
|
+
const { language, canonical, alternates } = useLocalizedLocation();
|
|
164
|
+
|
|
165
|
+
return (
|
|
166
|
+
<>
|
|
167
|
+
<link rel="canonical" href={alternates['en']} />
|
|
168
|
+
{Object.entries(alternates).map(([lang, href]) => (
|
|
169
|
+
<link key={lang} rel="alternate" hrefLang={lang} href={href} />
|
|
170
|
+
))}
|
|
171
|
+
</>
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function LanguageSwitcher() {
|
|
176
|
+
const { alternates } = useLocalizedLocation();
|
|
177
|
+
|
|
178
|
+
return (
|
|
179
|
+
<select onChange={(e) => window.location.href = alternates[e.target.value]}>
|
|
180
|
+
{Object.entries(alternates).map(([lang, href]) => (
|
|
181
|
+
<option key={lang} value={lang}>{lang}</option>
|
|
182
|
+
))}
|
|
183
|
+
</select>
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
## Migration from Deprecated I18nLink
|
|
189
|
+
|
|
190
|
+
The deprecated `I18nLink` component is now an alias for `Link`. Update your code:
|
|
191
|
+
|
|
192
|
+
| Old Pattern | New Pattern | Notes |
|
|
193
|
+
|-------------|-------------|-------|
|
|
194
|
+
| `import { I18nLink }` | `import { Link }` | Drop the `I18n` prefix. |
|
|
195
|
+
| `<I18nLink to="/">` | `<Link to="/">` | Identical props and behavior. |
|
|
196
|
+
| Hand-rolled `localizePath` helper | `useLocalizedPaths()` hook | Reads config from context automatically. |
|
|
197
|
+
| Hand-rolled `matchPattern` for active state | `Link` with `activeProps`/`activeOptions` | Language-invariant matching. |
|
|
198
|
+
| Manual hreflang blocks | `useLocalizedLocation()` hook | Generates per-language hrefs automatically. |
|
|
199
|
+
|
|
200
|
+
**I18nLink deprecation notice:** `I18nLink` is deprecated and will be removed in a future version. A one-time development console warning is logged when used.
|
|
201
|
+
|
|
202
|
+
```tsx
|
|
203
|
+
// ❌ Old
|
|
204
|
+
import { I18nLink } from '@modern-js/plugin-i18n/runtime';
|
|
205
|
+
<I18nLink to="/about">About</I18nLink>
|
|
206
|
+
|
|
207
|
+
// ✅ New
|
|
208
|
+
import { Link } from '@modern-js/plugin-i18n/runtime';
|
|
209
|
+
<Link to="/about">About</Link>
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Exported Types
|
|
213
|
+
|
|
214
|
+
For TypeScript projects, the following types are exported from `@modern-js/plugin-i18n/runtime`:
|
|
215
|
+
|
|
216
|
+
- `LinkProps<TTo>`: Props type for the `Link` component with typed `to` and `params`.
|
|
217
|
+
- `LinkBaseProps`: Base props shared across router frameworks.
|
|
218
|
+
- `LinkParams`: Record of route param values.
|
|
219
|
+
- `LinkActiveOptions`: Active state matching options.
|
|
220
|
+
- `UltramodernCanonicalRoutes`: Canonical route map (emitted by TanStack Router codegen).
|
|
221
|
+
- `CanonicalRoutePath`: Typed canonical route path.
|
|
222
|
+
- `AllowedLinkTarget`: Union of all valid canonical routes (when codegen is active).
|
|
223
|
+
|
|
224
|
+
## Exported Utilities
|
|
225
|
+
|
|
226
|
+
- `buildLocalizedUrl(pathname, language, languages, localisedUrls)`: Localize a pathname (used internally by `Link`).
|
|
227
|
+
- `splitUrlTarget(urlString)`: Parse a URL into pathname, search, and hash.
|
|
14
228
|
|
|
15
229
|
## Documentation
|
|
16
230
|
|
|
17
|
-
|
|
18
|
-
|
|
231
|
+
For complete documentation, including locale detection, resource loading, SSR, and custom route configuration, see:
|
|
232
|
+
|
|
233
|
+
- [English Documentation](https://modernjs.dev/en/guides/advanced-features/international/)
|
|
234
|
+
- [中文文档](https://modernjs.dev/guides/advanced-features/international/)
|
|
19
235
|
|
|
20
236
|
## Contributing
|
|
21
237
|
|
|
@@ -24,9 +240,3 @@ Please read the [Contributing Guide](https://github.com/web-infra-dev/modern.js/
|
|
|
24
240
|
## License
|
|
25
241
|
|
|
26
242
|
Modern.js is [MIT licensed](https://github.com/web-infra-dev/modern.js/blob/main/LICENSE).
|
|
27
|
-
|
|
28
|
-
## Credist
|
|
29
|
-
|
|
30
|
-
Thanks to:
|
|
31
|
-
|
|
32
|
-
- [@loadable/webpack-plugin](https://github.com/gregberge/loadable-components) to create a webpack plugin prepare for loadable usage in ssr.
|
package/dist/cjs/cli/index.js
CHANGED
|
@@ -1,20 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_require__ = {};
|
|
3
3
|
(()=>{
|
|
4
|
-
__webpack_require__.
|
|
5
|
-
var
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
(
|
|
13
|
-
__webpack_require__.d = (exports1, definition)=>{
|
|
14
|
-
for(var key in definition)if (__webpack_require__.o(definition, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
15
|
-
enumerable: true,
|
|
16
|
-
get: definition[key]
|
|
17
|
-
});
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
18
13
|
};
|
|
19
14
|
})();
|
|
20
15
|
(()=>{
|
|
@@ -37,42 +32,10 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
37
32
|
i18nPlugin: ()=>i18nPlugin
|
|
38
33
|
});
|
|
39
34
|
const server_core_namespaceObject = require("@modern-js/server-core");
|
|
40
|
-
const external_fs_namespaceObject = require("fs");
|
|
41
|
-
var external_fs_default = /*#__PURE__*/ __webpack_require__.n(external_fs_namespaceObject);
|
|
42
|
-
const external_path_namespaceObject = require("path");
|
|
43
|
-
var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
|
|
44
35
|
const localisedUrls_js_namespaceObject = require("../shared/localisedUrls.js");
|
|
45
36
|
const utils_js_namespaceObject = require("../shared/utils.js");
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
if (!external_fs_default().existsSync(dirPath) || !external_fs_default().statSync(dirPath).isDirectory()) return false;
|
|
49
|
-
const entries = external_fs_default().readdirSync(dirPath);
|
|
50
|
-
for (const entry of entries){
|
|
51
|
-
const entryPath = external_path_default().join(dirPath, entry);
|
|
52
|
-
const stat = external_fs_default().statSync(entryPath);
|
|
53
|
-
if (stat.isFile() && entry.endsWith('.json')) return true;
|
|
54
|
-
if (stat.isDirectory()) {
|
|
55
|
-
if (hasJsonFiles(entryPath)) return true;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
} catch {
|
|
60
|
-
return false;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
function detectLocalesDirectory(appDirectory, normalizedConfig) {
|
|
64
|
-
const rootLocalesPath = external_path_default().join(appDirectory, 'locales');
|
|
65
|
-
if (hasJsonFiles(rootLocalesPath)) return true;
|
|
66
|
-
const configPublicPath = external_path_default().join(appDirectory, 'config', 'public', 'locales');
|
|
67
|
-
if (hasJsonFiles(configPublicPath)) return true;
|
|
68
|
-
const publicDir = normalizedConfig?.server?.publicDir;
|
|
69
|
-
if (publicDir) {
|
|
70
|
-
const publicDirPath = Array.isArray(publicDir) ? publicDir[0] : publicDir;
|
|
71
|
-
const localesPath = external_path_default().isAbsolute(publicDirPath) ? external_path_default().join(publicDirPath, 'locales') : external_path_default().join(appDirectory, publicDirPath, 'locales');
|
|
72
|
-
if (hasJsonFiles(localesPath)) return true;
|
|
73
|
-
}
|
|
74
|
-
return false;
|
|
75
|
-
}
|
|
37
|
+
const external_locales_js_namespaceObject = require("./locales.js");
|
|
38
|
+
require("../runtime/types.js");
|
|
76
39
|
const i18nPlugin = (options = {})=>({
|
|
77
40
|
name: '@modern-js/plugin-i18n',
|
|
78
41
|
setup: (api)=>{
|
|
@@ -82,26 +45,16 @@ const i18nPlugin = (options = {})=>({
|
|
|
82
45
|
let backendOptions;
|
|
83
46
|
const { appDirectory } = api.getAppContext();
|
|
84
47
|
const normalizedConfig = api.getNormalizedConfig();
|
|
48
|
+
const detectedLocales = (0, external_locales_js_namespaceObject.detectLocalesDirectory)(appDirectory, normalizedConfig);
|
|
85
49
|
if (backend) {
|
|
86
50
|
const entryBackendOptions = (0, utils_js_namespaceObject.getBackendOptions)(entrypoint.entryName, backend);
|
|
87
|
-
|
|
88
|
-
else if (entryBackendOptions?.loadPath || entryBackendOptions?.addPath) backendOptions = {
|
|
51
|
+
backendOptions = entryBackendOptions?.enabled === false ? entryBackendOptions : detectedLocales ? (0, external_locales_js_namespaceObject.applyDetectedBackendPaths)(entryBackendOptions, detectedLocales) : entryBackendOptions?.loadPath || entryBackendOptions?.addPath ? {
|
|
89
52
|
...entryBackendOptions,
|
|
90
53
|
enabled: true
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
...entryBackendOptions,
|
|
96
|
-
enabled: true
|
|
97
|
-
} : entryBackendOptions;
|
|
98
|
-
} else backendOptions = entryBackendOptions;
|
|
99
|
-
} else {
|
|
100
|
-
const hasLocales = detectLocalesDirectory(appDirectory, normalizedConfig);
|
|
101
|
-
if (hasLocales) backendOptions = (0, utils_js_namespaceObject.getBackendOptions)(entrypoint.entryName, {
|
|
102
|
-
enabled: true
|
|
103
|
-
});
|
|
104
|
-
}
|
|
54
|
+
} : entryBackendOptions;
|
|
55
|
+
} else if (detectedLocales) backendOptions = (0, external_locales_js_namespaceObject.applyDetectedBackendPaths)((0, utils_js_namespaceObject.getBackendOptions)(entrypoint.entryName, {
|
|
56
|
+
enabled: true
|
|
57
|
+
}), detectedLocales);
|
|
105
58
|
const { metaName } = api.getAppContext();
|
|
106
59
|
let extendedConfig = restOptions;
|
|
107
60
|
if (transformRuntimeConfig) extendedConfig = transformRuntimeConfig(restOptions, entrypoint);
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __webpack_require__ = {};
|
|
3
|
+
(()=>{
|
|
4
|
+
__webpack_require__.n = (module)=>{
|
|
5
|
+
var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
|
|
6
|
+
__webpack_require__.d(getter, {
|
|
7
|
+
a: getter
|
|
8
|
+
});
|
|
9
|
+
return getter;
|
|
10
|
+
};
|
|
11
|
+
})();
|
|
12
|
+
(()=>{
|
|
13
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
14
|
+
var define = (defs, kind)=>{
|
|
15
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
[kind]: defs[key]
|
|
18
|
+
});
|
|
19
|
+
};
|
|
20
|
+
define(getters, "get");
|
|
21
|
+
define(values, "value");
|
|
22
|
+
};
|
|
23
|
+
})();
|
|
24
|
+
(()=>{
|
|
25
|
+
__webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
|
|
26
|
+
})();
|
|
27
|
+
(()=>{
|
|
28
|
+
__webpack_require__.r = (exports1)=>{
|
|
29
|
+
if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
|
|
30
|
+
value: 'Module'
|
|
31
|
+
});
|
|
32
|
+
Object.defineProperty(exports1, '__esModule', {
|
|
33
|
+
value: true
|
|
34
|
+
});
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
var __webpack_exports__ = {};
|
|
38
|
+
__webpack_require__.r(__webpack_exports__);
|
|
39
|
+
__webpack_require__.d(__webpack_exports__, {
|
|
40
|
+
applyDetectedBackendPaths: ()=>applyDetectedBackendPaths,
|
|
41
|
+
detectLocalesDirectory: ()=>detectLocalesDirectory
|
|
42
|
+
});
|
|
43
|
+
const server_core_namespaceObject = require("@modern-js/server-core");
|
|
44
|
+
const external_fs_namespaceObject = require("fs");
|
|
45
|
+
var external_fs_default = /*#__PURE__*/ __webpack_require__.n(external_fs_namespaceObject);
|
|
46
|
+
const external_path_namespaceObject = require("path");
|
|
47
|
+
var external_path_default = /*#__PURE__*/ __webpack_require__.n(external_path_namespaceObject);
|
|
48
|
+
const LOCALES_RESOURCE_PATTERN = '{{lng}}/{{ns}}.json';
|
|
49
|
+
function hasJsonFiles(dirPath) {
|
|
50
|
+
try {
|
|
51
|
+
if (!external_fs_default().existsSync(dirPath) || !external_fs_default().statSync(dirPath).isDirectory()) return false;
|
|
52
|
+
const entries = external_fs_default().readdirSync(dirPath);
|
|
53
|
+
for (const entry of entries){
|
|
54
|
+
const entryPath = external_path_default().join(dirPath, entry);
|
|
55
|
+
const stat = external_fs_default().statSync(entryPath);
|
|
56
|
+
if (stat.isFile() && entry.endsWith('.json')) return true;
|
|
57
|
+
if (stat.isDirectory() && hasJsonFiles(entryPath)) return true;
|
|
58
|
+
}
|
|
59
|
+
return false;
|
|
60
|
+
} catch {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function toPosixPath(filePath) {
|
|
65
|
+
return filePath.split(external_path_default().sep).join(external_path_default().posix.sep);
|
|
66
|
+
}
|
|
67
|
+
function getPublicDirOutputPath(publicDir) {
|
|
68
|
+
return (0, server_core_namespaceObject.normalizePublicDirPath)(publicDir);
|
|
69
|
+
}
|
|
70
|
+
function buildDetectedLocalesDirectory(clientBasePath, serverBasePath, serverBasePathCandidates = [
|
|
71
|
+
serverBasePath
|
|
72
|
+
]) {
|
|
73
|
+
const normalizedClientBasePath = clientBasePath.startsWith('/') ? clientBasePath : `/${clientBasePath}`;
|
|
74
|
+
const normalizedServerBasePath = toPosixPath(serverBasePath).replace(/\/$/, '');
|
|
75
|
+
const normalizedServerBasePathCandidates = Array.from(new Set(serverBasePathCandidates.map((candidate)=>toPosixPath(candidate).replace(/\/$/, ''))));
|
|
76
|
+
const serverLoadPaths = normalizedServerBasePathCandidates.map((candidate)=>`${candidate}/${LOCALES_RESOURCE_PATTERN}`);
|
|
77
|
+
const serverAddPaths = normalizedServerBasePathCandidates.map((candidate)=>`${candidate}/${LOCALES_RESOURCE_PATTERN}`);
|
|
78
|
+
return {
|
|
79
|
+
loadPath: `${normalizedClientBasePath}/${LOCALES_RESOURCE_PATTERN}`,
|
|
80
|
+
addPath: `${normalizedClientBasePath}/${LOCALES_RESOURCE_PATTERN}`,
|
|
81
|
+
serverLoadPath: `${normalizedServerBasePath}/${LOCALES_RESOURCE_PATTERN}`,
|
|
82
|
+
serverAddPath: `${normalizedServerBasePath}/${LOCALES_RESOURCE_PATTERN}`,
|
|
83
|
+
serverLoadPaths,
|
|
84
|
+
serverAddPaths
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
function detectLocalesDirectory(appDirectory, normalizedConfig) {
|
|
88
|
+
const publicDirs = (0, server_core_namespaceObject.normalizePublicDir)(normalizedConfig?.server?.publicDir);
|
|
89
|
+
const publicDirPrefixes = (0, server_core_namespaceObject.getPublicDirRoutePrefixes)(normalizedConfig?.server?.publicDir);
|
|
90
|
+
for(let index = 0; index < publicDirs.length; index++){
|
|
91
|
+
const publicDir = publicDirs[index];
|
|
92
|
+
if (external_path_default().isAbsolute(publicDir)) continue;
|
|
93
|
+
const publicDirPath = external_path_default().join(appDirectory, publicDir);
|
|
94
|
+
const publicDirPrefix = publicDirPrefixes[index] || `/${(0, server_core_namespaceObject.normalizePublicDirPath)(publicDir)}`;
|
|
95
|
+
const publicDirOutputPath = getPublicDirOutputPath(publicDir);
|
|
96
|
+
if ('locales' === external_path_default().basename((0, server_core_namespaceObject.normalizePublicDirPath)(publicDir)) && hasJsonFiles(publicDirPath)) return buildDetectedLocalesDirectory(publicDirPrefix, `./${publicDirOutputPath}`);
|
|
97
|
+
const localesPath = external_path_default().join(publicDirPath, 'locales');
|
|
98
|
+
if (hasJsonFiles(localesPath)) return buildDetectedLocalesDirectory(`${publicDirPrefix}/locales`, `./${publicDirOutputPath}/locales`);
|
|
99
|
+
}
|
|
100
|
+
const configPublicPath = external_path_default().join(appDirectory, 'config', 'public', 'locales');
|
|
101
|
+
if (hasJsonFiles(configPublicPath)) return buildDetectedLocalesDirectory('/locales', './public/locales', [
|
|
102
|
+
'./config/public/locales',
|
|
103
|
+
'./public/locales'
|
|
104
|
+
]);
|
|
105
|
+
const rootLocalesPath = external_path_default().join(appDirectory, 'locales');
|
|
106
|
+
if (hasJsonFiles(rootLocalesPath)) return buildDetectedLocalesDirectory('/locales', './locales');
|
|
107
|
+
}
|
|
108
|
+
function applyDetectedBackendPaths(backendOptions, detectedLocales) {
|
|
109
|
+
if (!detectedLocales) return backendOptions;
|
|
110
|
+
const backendWithDetectedPaths = {
|
|
111
|
+
...backendOptions,
|
|
112
|
+
enabled: true,
|
|
113
|
+
loadPath: backendOptions.loadPath ?? detectedLocales.loadPath,
|
|
114
|
+
addPath: backendOptions.addPath ?? detectedLocales.addPath,
|
|
115
|
+
serverLoadPath: backendOptions.serverLoadPath ?? detectedLocales.serverLoadPath,
|
|
116
|
+
serverLoadPaths: backendOptions.serverLoadPath || backendOptions.serverLoadPaths ? backendOptions.serverLoadPaths : detectedLocales.serverLoadPaths,
|
|
117
|
+
serverAddPath: backendOptions.serverAddPath ?? detectedLocales.serverAddPath,
|
|
118
|
+
serverAddPaths: backendOptions.serverAddPath || backendOptions.serverAddPaths ? backendOptions.serverAddPaths : detectedLocales.serverAddPaths,
|
|
119
|
+
_detectedLoadPath: detectedLocales.loadPath,
|
|
120
|
+
_detectedAddPath: detectedLocales.addPath
|
|
121
|
+
};
|
|
122
|
+
return backendWithDetectedPaths;
|
|
123
|
+
}
|
|
124
|
+
exports.applyDetectedBackendPaths = __webpack_exports__.applyDetectedBackendPaths;
|
|
125
|
+
exports.detectLocalesDirectory = __webpack_exports__.detectLocalesDirectory;
|
|
126
|
+
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
127
|
+
"applyDetectedBackendPaths",
|
|
128
|
+
"detectLocalesDirectory"
|
|
129
|
+
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
130
|
+
Object.defineProperty(exports, '__esModule', {
|
|
131
|
+
value: true
|
|
132
|
+
});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
var __webpack_require__ = {};
|
|
3
3
|
(()=>{
|
|
4
|
-
__webpack_require__.d = (exports1,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
__webpack_require__.d = (exports1, getters, values)=>{
|
|
5
|
+
var define = (defs, kind)=>{
|
|
6
|
+
for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
[kind]: defs[key]
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
define(getters, "get");
|
|
12
|
+
define(values, "value");
|
|
9
13
|
};
|
|
10
14
|
})();
|
|
11
15
|
(()=>{
|
|
@@ -28,22 +32,15 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
28
32
|
default: ()=>runtime_I18nLink
|
|
29
33
|
});
|
|
30
34
|
const jsx_runtime_namespaceObject = require("react/jsx-runtime");
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
const external_utils_js_namespaceObject = require("./utils.js");
|
|
35
|
+
const external_Link_js_namespaceObject = require("./Link.js");
|
|
36
|
+
let warnedDeprecation = false;
|
|
34
37
|
const I18nLink = ({ to, children, ...props })=>{
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
href: localizedTo,
|
|
42
|
-
...props,
|
|
43
|
-
children: children
|
|
44
|
-
});
|
|
45
|
-
return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(Link, {
|
|
46
|
-
to: localizedTo,
|
|
38
|
+
if ('development' === process.env.NODE_ENV && !warnedDeprecation) {
|
|
39
|
+
warnedDeprecation = true;
|
|
40
|
+
console.warn("[plugin-i18n] I18nLink is deprecated. Import { Link } from '@modern-js/plugin-i18n/runtime' instead — it accepts the same language-agnostic `to` values.");
|
|
41
|
+
}
|
|
42
|
+
return /*#__PURE__*/ (0, jsx_runtime_namespaceObject.jsx)(external_Link_js_namespaceObject.Link, {
|
|
43
|
+
to: to,
|
|
47
44
|
...props,
|
|
48
45
|
children: children
|
|
49
46
|
});
|