@fluenti/next 0.1.0
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/LICENSE +21 -0
- package/README.md +325 -0
- package/client.d.ts +9 -0
- package/dist/client-provider.d.ts +17 -0
- package/dist/client-provider.d.ts.map +1 -0
- package/dist/generate-server-module.d.ts +11 -0
- package/dist/generate-server-module.d.ts.map +1 -0
- package/dist/index.cjs +128 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +183 -0
- package/dist/index.js.map +1 -0
- package/dist/loader.cjs +2 -0
- package/dist/loader.cjs.map +1 -0
- package/dist/loader.js +29 -0
- package/dist/loader.js.map +1 -0
- package/dist/provider.cjs +8 -0
- package/dist/provider.cjs.map +1 -0
- package/dist/provider.d.ts +3 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +197 -0
- package/dist/provider.js.map +1 -0
- package/dist/read-config.d.ts +6 -0
- package/dist/read-config.d.ts.map +1 -0
- package/dist/scope-transform.d.ts +3 -0
- package/dist/scope-transform.d.ts.map +1 -0
- package/dist/server.cjs +2 -0
- package/dist/server.cjs.map +1 -0
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +14 -0
- package/dist/server.js.map +1 -0
- package/dist/trans-transform.d.ts +3 -0
- package/dist/trans-transform.d.ts.map +1 -0
- package/dist/types.d.ts +59 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/with-fluenti.d.ts +26 -0
- package/dist/with-fluenti.d.ts.map +1 -0
- package/dist/with-locale.d.ts +28 -0
- package/dist/with-locale.d.ts.map +1 -0
- package/package.json +101 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Fluenti Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
# @fluenti/next
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@fluenti/next)
|
|
4
|
+
[](https://bundlephobia.com/package/@fluenti/next)
|
|
5
|
+
[](https://github.com/usefluenti/fluenti/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
**Compile-time i18n for Next.js.** App Router native. RSC + streaming + server actions out of the box.
|
|
8
|
+
|
|
9
|
+
```tsx
|
|
10
|
+
// Server Component — zero client JS
|
|
11
|
+
import { t } from '@fluenti/react'
|
|
12
|
+
|
|
13
|
+
export default async function Page() {
|
|
14
|
+
return <h1>{t`Welcome, ${user.name}!`}</h1>
|
|
15
|
+
}
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
// Client Component — same syntax
|
|
20
|
+
'use client'
|
|
21
|
+
import { t } from '@fluenti/react'
|
|
22
|
+
|
|
23
|
+
export default function Counter() {
|
|
24
|
+
return <p>{t`You have ${count} items in your cart.`}</p>
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
No runtime parsing. No bundle bloat. Messages are compiled at build time and tree-shaken per locale.
|
|
29
|
+
|
|
30
|
+
## Features
|
|
31
|
+
|
|
32
|
+
- **App Router native** — first-class RSC, streaming SSR, and server actions support
|
|
33
|
+
- **Next.js 14 & 15** compatible (`next >= 14.0.0`)
|
|
34
|
+
- **`t\`\`` tagged templates** — write messages inline, extract them with the CLI
|
|
35
|
+
- **Binding-aware transforms** — the webpack loader rewrites tagged templates only for proven Fluenti bindings
|
|
36
|
+
- **`FluentProvider`** — async server component that sets up both server and client i18n in one place
|
|
37
|
+
- **`withLocale()`** — per-component locale isolation in RSC
|
|
38
|
+
- **ICU MessageFormat** — plurals, selects, nested arguments, custom formatters
|
|
39
|
+
- **Code splitting** — messages split per locale, loaded on demand
|
|
40
|
+
- **Cookie-based locale detection** — reads `locale` cookie in server components by default
|
|
41
|
+
|
|
42
|
+
## Quick Start
|
|
43
|
+
|
|
44
|
+
### 1. Install
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
pnpm add @fluenti/next @fluenti/core @fluenti/react
|
|
48
|
+
pnpm add -D @fluenti/cli
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### 2. Configure `next.config.ts`
|
|
52
|
+
|
|
53
|
+
```ts
|
|
54
|
+
import type { NextConfig } from 'next'
|
|
55
|
+
import { withFluenti } from '@fluenti/next'
|
|
56
|
+
|
|
57
|
+
const nextConfig: NextConfig = {
|
|
58
|
+
reactStrictMode: true,
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default withFluenti()(nextConfig)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
`withFluenti()` adds a webpack loader that rewrites direct-import authoring APIs in supported client/server scopes and generates a server module for RSC i18n. It reads your `fluenti.config.ts` automatically.
|
|
65
|
+
|
|
66
|
+
You can pass overrides directly:
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
export default withFluenti({
|
|
70
|
+
locales: ['en', 'ja', 'zh-CN'],
|
|
71
|
+
defaultLocale: 'en',
|
|
72
|
+
})(nextConfig)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 3. Set up `FluentProvider` in your root layout
|
|
76
|
+
|
|
77
|
+
```tsx
|
|
78
|
+
// app/layout.tsx
|
|
79
|
+
import { cookies } from 'next/headers'
|
|
80
|
+
import { FluentProvider } from '@fluenti/next/__generated'
|
|
81
|
+
|
|
82
|
+
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
|
83
|
+
const cookieStore = await cookies()
|
|
84
|
+
const locale = cookieStore.get('locale')?.value ?? 'en'
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<html lang={locale}>
|
|
88
|
+
<body>
|
|
89
|
+
<FluentProvider locale={locale}>
|
|
90
|
+
{children}
|
|
91
|
+
</FluentProvider>
|
|
92
|
+
</body>
|
|
93
|
+
</html>
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
`FluentProvider` is an async server component. It initializes the server-side i18n instance (via `React.cache`) and wraps children in a client-side `I18nProvider` for hydration.
|
|
99
|
+
|
|
100
|
+
### 4. Use `t\`\`` in your pages
|
|
101
|
+
|
|
102
|
+
**Server Component** (default in `app/`):
|
|
103
|
+
|
|
104
|
+
```tsx
|
|
105
|
+
// app/rsc/page.tsx
|
|
106
|
+
import { t } from '@fluenti/react'
|
|
107
|
+
|
|
108
|
+
export default async function RSCPage() {
|
|
109
|
+
return (
|
|
110
|
+
<div>
|
|
111
|
+
<h1>{t`Server rendered`}</h1>
|
|
112
|
+
<p>{t`This page is a React Server Component.`}</p>
|
|
113
|
+
</div>
|
|
114
|
+
)
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
**Client Component**:
|
|
119
|
+
|
|
120
|
+
```tsx
|
|
121
|
+
// app/page.tsx
|
|
122
|
+
'use client'
|
|
123
|
+
import { t, useI18n } from '@fluenti/react'
|
|
124
|
+
|
|
125
|
+
export default function Home() {
|
|
126
|
+
const { setLocale, preloadLocale } = useI18n()
|
|
127
|
+
const name = 'World'
|
|
128
|
+
return <h1>{t`Hello, ${name}!`}</h1>
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`import { t }` is the primary compile-time API. It supports tagged templates and descriptor calls, but not `t('message.id')` lookup. In Next apps using `withFluenti()`, ordinary authoring imports come from `@fluenti/react` on both the client and the server. For runtime lookup or full imperative access, use `await getI18n()` on the server or `useI18n()` on the client.
|
|
133
|
+
|
|
134
|
+
### 5. Extract and compile messages
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Extract messages from source files
|
|
138
|
+
pnpm fluenti extract
|
|
139
|
+
|
|
140
|
+
# Translate your PO files, then compile
|
|
141
|
+
pnpm fluenti compile
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
## Next.js-Specific Features
|
|
145
|
+
|
|
146
|
+
### React Server Components
|
|
147
|
+
|
|
148
|
+
Server components use `t\`\`` with zero client-side JavaScript. The loader detects server context automatically (files in `app/` without `'use client'`).
|
|
149
|
+
|
|
150
|
+
For direct access to the i18n instance in RSC:
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
import { setLocale, getI18n } from '@fluenti/next/__generated'
|
|
154
|
+
|
|
155
|
+
export default async function Page({ searchParams }) {
|
|
156
|
+
const params = await searchParams
|
|
157
|
+
if (params.lang) setLocale(params.lang)
|
|
158
|
+
|
|
159
|
+
const { t, locale } = await getI18n()
|
|
160
|
+
return <p>{t`Current server locale: ${locale}`}</p>
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Streaming SSR
|
|
165
|
+
|
|
166
|
+
Works with `Suspense` boundaries — streamed content is translated on the server:
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
import { Suspense } from 'react'
|
|
170
|
+
|
|
171
|
+
async function SlowContent() {
|
|
172
|
+
const { getI18n } = await import('@fluenti/next/__generated')
|
|
173
|
+
const { t } = await getI18n()
|
|
174
|
+
await fetchData()
|
|
175
|
+
return <p>{t`Streamed content loaded!`}</p>
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export default async function StreamingPage() {
|
|
179
|
+
return (
|
|
180
|
+
<Suspense fallback={<p>Loading...</p>}>
|
|
181
|
+
<SlowContent />
|
|
182
|
+
</Suspense>
|
|
183
|
+
)
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### Server Actions
|
|
188
|
+
|
|
189
|
+
Direct-import `t` works in `'use server'` functions:
|
|
190
|
+
|
|
191
|
+
```ts
|
|
192
|
+
'use server'
|
|
193
|
+
|
|
194
|
+
import { t } from '@fluenti/react'
|
|
195
|
+
|
|
196
|
+
export async function greetAction(): Promise<string> {
|
|
197
|
+
return t`Hello from server action`
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
### Metadata i18n
|
|
202
|
+
|
|
203
|
+
Translate Next.js metadata using the server i18n instance:
|
|
204
|
+
|
|
205
|
+
```tsx
|
|
206
|
+
import { getI18n } from '@fluenti/next/__generated'
|
|
207
|
+
|
|
208
|
+
export async function generateMetadata() {
|
|
209
|
+
const i18n = await getI18n()
|
|
210
|
+
return {
|
|
211
|
+
title: i18n.t('My App — Internationalized'),
|
|
212
|
+
description: i18n.t('A fully localized Next.js application'),
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Middleware Locale Detection
|
|
218
|
+
|
|
219
|
+
Detect locale from headers, cookies, or URL and set it via cookie:
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// middleware.ts
|
|
223
|
+
import { NextResponse } from 'next/server'
|
|
224
|
+
import type { NextRequest } from 'next/server'
|
|
225
|
+
|
|
226
|
+
const SUPPORTED_LOCALES = ['en', 'ja', 'zh-CN']
|
|
227
|
+
const DEFAULT_LOCALE = 'en'
|
|
228
|
+
|
|
229
|
+
export function middleware(request: NextRequest) {
|
|
230
|
+
const response = NextResponse.next()
|
|
231
|
+
|
|
232
|
+
// Already has a locale cookie
|
|
233
|
+
if (request.cookies.get('locale')?.value) {
|
|
234
|
+
return response
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Detect from Accept-Language header
|
|
238
|
+
const acceptLang = request.headers.get('accept-language') ?? ''
|
|
239
|
+
const detected = acceptLang
|
|
240
|
+
.split(',')
|
|
241
|
+
.map((s) => s.split(';')[0]!.trim())
|
|
242
|
+
.find((lang) => SUPPORTED_LOCALES.includes(lang))
|
|
243
|
+
|
|
244
|
+
response.cookies.set('locale', detected ?? DEFAULT_LOCALE)
|
|
245
|
+
return response
|
|
246
|
+
}
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Per-Component Locale Isolation
|
|
250
|
+
|
|
251
|
+
Render a subtree in a different locale using `withLocale()`:
|
|
252
|
+
|
|
253
|
+
```tsx
|
|
254
|
+
import { withLocale } from '@fluenti/next/server'
|
|
255
|
+
|
|
256
|
+
export default async function Page() {
|
|
257
|
+
return (
|
|
258
|
+
<div>
|
|
259
|
+
<h1>{t`Main content`}</h1>
|
|
260
|
+
{await withLocale('ja', async () => (
|
|
261
|
+
<JapaneseWidget />
|
|
262
|
+
))}
|
|
263
|
+
</div>
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
## API Reference
|
|
269
|
+
|
|
270
|
+
### `withFluenti(config?)`
|
|
271
|
+
|
|
272
|
+
Wraps your Next.js config with Fluenti support. Accepts an optional `WithFluentConfig`:
|
|
273
|
+
|
|
274
|
+
| Option | Type | Description |
|
|
275
|
+
|--------|------|-------------|
|
|
276
|
+
| `locales` | `string[]` | Override locales from `fluenti.config.ts` |
|
|
277
|
+
| `defaultLocale` | `string` | Override source locale |
|
|
278
|
+
| `compiledDir` | `string` | Override compiled messages directory |
|
|
279
|
+
| `serverModule` | `string` | Custom server module path (skip auto-generation) |
|
|
280
|
+
| `resolveLocale` | `() => string \| Promise<string>` | Custom locale resolver for server actions |
|
|
281
|
+
| `dateFormats` | `DateFormatOptions` | Custom date format styles |
|
|
282
|
+
| `numberFormats` | `NumberFormatOptions` | Custom number format styles |
|
|
283
|
+
| `fallbackChain` | `Record<string, Locale[]>` | Fallback chain per locale |
|
|
284
|
+
|
|
285
|
+
### `FluentProvider`
|
|
286
|
+
|
|
287
|
+
Async server component (imported from `@fluenti/next/__generated`). Place in your root layout.
|
|
288
|
+
|
|
289
|
+
| Prop | Type | Description |
|
|
290
|
+
|------|------|-------------|
|
|
291
|
+
| `locale` | `string?` | Active locale. Defaults to `defaultLocale` from config. |
|
|
292
|
+
| `children` | `ReactNode` | Application tree |
|
|
293
|
+
|
|
294
|
+
### `withLocale(locale, fn)`
|
|
295
|
+
|
|
296
|
+
Server utility (imported from `@fluenti/next/server`). Executes `fn` with a temporarily switched locale.
|
|
297
|
+
|
|
298
|
+
### Generated Server Module
|
|
299
|
+
|
|
300
|
+
`@fluenti/next/__generated` exports:
|
|
301
|
+
|
|
302
|
+
| Export | Description |
|
|
303
|
+
|--------|-------------|
|
|
304
|
+
| `FluentProvider` | Async server component for layouts |
|
|
305
|
+
| `setLocale(locale)` | Set the request-scoped locale |
|
|
306
|
+
| `getI18n()` | Get the i18n instance (async) |
|
|
307
|
+
| `t` | Compile-time translation API, preserved for advanced/server-specific imports |
|
|
308
|
+
| `Trans` | Server component for rich text |
|
|
309
|
+
| `Plural` | Server component for plurals |
|
|
310
|
+
| `Select` | Server component for categorical selection |
|
|
311
|
+
| `DateTime` | Server component for date formatting |
|
|
312
|
+
| `NumberFormat` | Server component for number formatting |
|
|
313
|
+
|
|
314
|
+
## Documentation
|
|
315
|
+
|
|
316
|
+
Full docs at [fluenti.dev](https://fluenti.dev).
|
|
317
|
+
|
|
318
|
+
- [Next.js Quick Start](https://fluenti.dev/start/quick-start-nextjs/)
|
|
319
|
+
- [Next.js Guide](https://fluenti.dev/frameworks/react/nextjs/)
|
|
320
|
+
- [Server Components](https://fluenti.dev/frameworks/react/server-components/)
|
|
321
|
+
- [API Reference](https://fluenti.dev/api/next/)
|
|
322
|
+
|
|
323
|
+
## License
|
|
324
|
+
|
|
325
|
+
[MIT](https://github.com/usefluenti/fluenti/blob/main/LICENSE)
|
package/client.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Global tagged template function transformed by @fluenti/next webpack loader.
|
|
3
|
+
* Transforms `t`message`` into compiled i18n calls at build time.
|
|
4
|
+
*
|
|
5
|
+
* @deprecated Prefer `const { t } = useI18n()` which provides the same
|
|
6
|
+
* tagged template support without a magic global. The plugin will optimize
|
|
7
|
+
* tagged templates via AST scope analysis when `t` comes from `useI18n()`.
|
|
8
|
+
*/
|
|
9
|
+
declare function t(strings: TemplateStringsArray, ...values: unknown[]): string
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { ReactNode } from 'react';
|
|
2
|
+
import { AllMessages, DateFormatOptions, NumberFormatOptions, Locale } from '@fluenti/core';
|
|
3
|
+
export interface ClientI18nProviderProps {
|
|
4
|
+
locale: string;
|
|
5
|
+
fallbackLocale: string;
|
|
6
|
+
messages: AllMessages;
|
|
7
|
+
fallbackChain?: Record<string, Locale[]>;
|
|
8
|
+
dateFormats?: DateFormatOptions;
|
|
9
|
+
numberFormats?: NumberFormatOptions;
|
|
10
|
+
children: ReactNode;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Client-side I18nProvider wrapper.
|
|
14
|
+
* Used internally by FluentProvider to hydrate client components.
|
|
15
|
+
*/
|
|
16
|
+
export declare function ClientI18nProvider({ locale, fallbackLocale, messages, fallbackChain, dateFormats, numberFormats, children, }: ClientI18nProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
17
|
+
//# sourceMappingURL=client-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-provider.d.ts","sourceRoot":"","sources":["../src/client-provider.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAEtC,OAAO,KAAK,EAAE,WAAW,EAAE,iBAAiB,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAA;AAEhG,MAAM,WAAW,uBAAuB;IACtC,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,MAAM,CAAA;IACtB,QAAQ,EAAE,WAAW,CAAA;IACrB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAAA;IACxC,WAAW,CAAC,EAAE,iBAAiB,CAAA;IAC/B,aAAa,CAAC,EAAE,mBAAmB,CAAA;IACnC,QAAQ,EAAE,SAAS,CAAA;CACpB;AAED;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EACN,cAAc,EACd,QAAQ,EACR,aAAa,EACb,WAAW,EACX,aAAa,EACb,QAAQ,GACT,EAAE,uBAAuB,2CAazB"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { ResolvedFluentConfig } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Generate the server module that provides:
|
|
4
|
+
* - setLocale / getI18n
|
|
5
|
+
* - Trans / Plural / Select / DateTime / NumberFormat (server components)
|
|
6
|
+
* - FluentProvider (async server component for layouts)
|
|
7
|
+
*
|
|
8
|
+
* @returns Absolute path to the generated server module.
|
|
9
|
+
*/
|
|
10
|
+
export declare function generateServerModule(projectRoot: string, config: ResolvedFluentConfig): string;
|
|
11
|
+
//# sourceMappingURL=generate-server-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-server-module.d.ts","sourceRoot":"","sources":["../src/generate-server-module.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,SAAS,CAAA;AAEnD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAClC,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,oBAAoB,GAC3B,MAAM,CAyLR"}
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`node:path`),t=require(`node:fs`),n=require(`node:module`);var r=typeof __filename==`string`?__filename:{}.url,{createJiti:i}=(0,n.createRequire)(r)(`jiti`);function a(t,n){let r=o(t),i=n?.defaultLocale??r?.sourceLocale??`en`,a=n?.locales??r?.locales??[i],s=n?.compiledDir??r?.compileOutDir??`./src/locales/compiled`,c=n?.serverModuleOutDir??(0,e.join)(`node_modules`,`.fluenti`),l={locales:a,defaultLocale:i,compiledDir:s,serverModule:n?.serverModule??null,serverModuleOutDir:c};return n?.resolveLocale&&(l.resolveLocale=n.resolveLocale),n?.dateFormats&&(l.dateFormats=n.dateFormats),n?.numberFormats&&(l.numberFormats=n.numberFormats),n?.fallbackChain&&(l.fallbackChain=n.fallbackChain),l}function o(n){let a=i(r,{moduleCache:!1,interopDefault:!0});for(let r of[`fluenti.config.ts`,`fluenti.config.js`,`fluenti.config.mjs`]){let i=(0,e.resolve)(n,r);if((0,t.existsSync)(i))try{return c(a(i))}catch{return s(i,a)||null}}return null}function s(n,r){let i=(0,t.readFileSync)(n,`utf8`),a=i.match(/import\s*\{\s*defineConfig(?:\s+as\s+([A-Za-z_$][\w$]*))?\s*\}\s*from\s*['"]@fluenti\/cli['"]\s*;?/);if(!a)return null;let o=a[1]??`defineConfig`,s=i.replace(a[0],``),l=(0,e.join)((0,e.dirname)(n),`.${(0,e.basename)(n,(0,e.extname)(n))}.next-plugin-read-config${(0,e.extname)(n)||`.ts`}`);(0,t.writeFileSync)(l,`const ${o} = (config) => config\n${s}`,`utf8`);try{return c(r(l))}catch{return null}finally{(0,t.rmSync)(l,{force:!0})}}function c(e){return typeof e==`object`&&e&&`default`in e?e.default??{}:e}function l(n,r){if(r.serverModule)return(0,e.resolve)(n,r.serverModule);let i=(0,e.resolve)(n,r.serverModuleOutDir),a=(0,e.resolve)(i,`server.js`),o=(0,e.resolve)(i,`server.d.ts`);(0,t.existsSync)(i)||(0,t.mkdirSync)(i,{recursive:!0});let s=u((0,e.relative)(i,(0,e.resolve)(n,r.compiledDir))),c=r.locales.map(e=>` case '${e}': return import('${s}/${e}')`).join(`
|
|
2
|
+
`),l=r.fallbackChain?JSON.stringify(r.fallbackChain):`undefined`;(0,t.writeFileSync)((0,e.resolve)(i,`client-provider.js`),`"use client";
|
|
3
|
+
// Auto-generated by @fluenti/next — do not edit
|
|
4
|
+
import { createElement } from 'react'
|
|
5
|
+
import { I18nProvider } from '@fluenti/react'
|
|
6
|
+
${r.locales.map(e=>`import ${e.replace(/[^a-zA-Z0-9]/g,`_`)} from '${s}/${e}'`).join(`
|
|
7
|
+
`)}
|
|
8
|
+
|
|
9
|
+
const __allMessages = { ${r.locales.map(e=>`'${e}': ${e.replace(/[^a-zA-Z0-9]/g,`_`)}`).join(`, `)} }
|
|
10
|
+
|
|
11
|
+
export function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {
|
|
12
|
+
return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain }, children)
|
|
13
|
+
}
|
|
14
|
+
`,`utf-8`);let d=r.resolveLocale?`import __resolveLocale from '${r.resolveLocale}'`:null,f=r.resolveLocale?`resolveLocale: __resolveLocale,`:`resolveLocale: async () => {
|
|
15
|
+
try {
|
|
16
|
+
const { cookies } = await import('next/headers')
|
|
17
|
+
return (await cookies()).get('locale')?.value ?? '${r.defaultLocale}'
|
|
18
|
+
} catch {
|
|
19
|
+
return '${r.defaultLocale}'
|
|
20
|
+
}
|
|
21
|
+
},`,p=`// Auto-generated by @fluenti/next — do not edit
|
|
22
|
+
import { createServerI18n } from '@fluenti/react/server'
|
|
23
|
+
import { createElement } from 'react'
|
|
24
|
+
${d?`${d}\n`:``}
|
|
25
|
+
const serverI18n = createServerI18n({
|
|
26
|
+
loadMessages: async (locale) => {
|
|
27
|
+
switch (locale) {
|
|
28
|
+
${c}
|
|
29
|
+
default: return import('${s}/${r.defaultLocale}')
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
fallbackLocale: '${r.defaultLocale}',
|
|
33
|
+
fallbackChain: ${l},
|
|
34
|
+
${f}
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
export const setLocale = serverI18n.setLocale
|
|
38
|
+
export const getI18n = serverI18n.getI18n
|
|
39
|
+
export const t = (..._args) => {
|
|
40
|
+
throw new Error(
|
|
41
|
+
"[fluenti] \`t\` imported from '@fluenti/next/__generated' is a compile-time API. " +
|
|
42
|
+
'Use it only with the Fluenti loader inside an async server scope.',
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
export const Trans = serverI18n.Trans
|
|
46
|
+
export const Plural = serverI18n.Plural
|
|
47
|
+
export const Select = serverI18n.Select
|
|
48
|
+
export const DateTime = serverI18n.DateTime
|
|
49
|
+
export const NumberFormat = serverI18n.NumberFormat
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Async server component for root layouts.
|
|
53
|
+
*
|
|
54
|
+
* Sets up both server-side (React.cache) and client-side (I18nProvider) i18n.
|
|
55
|
+
*/
|
|
56
|
+
export async function FluentProvider({ locale, children }) {
|
|
57
|
+
const activeLocale = locale ?? '${r.defaultLocale}'
|
|
58
|
+
|
|
59
|
+
// 1. Initialize server-side i18n (React.cache scoped)
|
|
60
|
+
serverI18n.setLocale(activeLocale)
|
|
61
|
+
await serverI18n.getI18n()
|
|
62
|
+
|
|
63
|
+
// 2. Import the local 'use client' provider that has messages statically bundled.
|
|
64
|
+
// Messages contain functions (interpolation) which can't be serialized across the RSC boundary.
|
|
65
|
+
const { ClientI18nProvider } = await import('./client-provider.js')
|
|
66
|
+
|
|
67
|
+
return createElement(ClientI18nProvider, {
|
|
68
|
+
locale: activeLocale,
|
|
69
|
+
fallbackLocale: '${r.defaultLocale}',
|
|
70
|
+
fallbackChain: ${l},
|
|
71
|
+
}, children)
|
|
72
|
+
}
|
|
73
|
+
`;return(0,t.writeFileSync)(a,p,`utf-8`),(0,t.writeFileSync)(o,`// Auto-generated by @fluenti/next — do not edit
|
|
74
|
+
import type { ReactNode, ReactElement } from 'react'
|
|
75
|
+
import type { CompileTimeT, FluentInstanceExtended } from '@fluenti/core'
|
|
76
|
+
|
|
77
|
+
export declare function setLocale(locale: string): void
|
|
78
|
+
export declare function getI18n(): Promise<FluentInstanceExtended & { locale: string }>
|
|
79
|
+
export declare const t: CompileTimeT
|
|
80
|
+
|
|
81
|
+
export declare function Trans(props: {
|
|
82
|
+
children: ReactNode
|
|
83
|
+
id?: string
|
|
84
|
+
context?: string
|
|
85
|
+
comment?: string
|
|
86
|
+
render?: (translation: ReactNode) => ReactNode
|
|
87
|
+
}): Promise<ReactElement>
|
|
88
|
+
|
|
89
|
+
export declare function Plural(props: {
|
|
90
|
+
value: number
|
|
91
|
+
id?: string
|
|
92
|
+
context?: string
|
|
93
|
+
comment?: string
|
|
94
|
+
zero?: ReactNode
|
|
95
|
+
one?: ReactNode
|
|
96
|
+
two?: ReactNode
|
|
97
|
+
few?: ReactNode
|
|
98
|
+
many?: ReactNode
|
|
99
|
+
other: ReactNode
|
|
100
|
+
offset?: number
|
|
101
|
+
}): Promise<ReactElement>
|
|
102
|
+
|
|
103
|
+
export declare function Select(props: {
|
|
104
|
+
value: string
|
|
105
|
+
id?: string
|
|
106
|
+
context?: string
|
|
107
|
+
comment?: string
|
|
108
|
+
other: ReactNode
|
|
109
|
+
options?: Record<string, ReactNode>
|
|
110
|
+
[key: string]: ReactNode | Record<string, ReactNode> | string | undefined
|
|
111
|
+
}): Promise<ReactElement>
|
|
112
|
+
|
|
113
|
+
export declare function DateTime(props: {
|
|
114
|
+
value: Date | number
|
|
115
|
+
style?: string
|
|
116
|
+
}): Promise<ReactElement>
|
|
117
|
+
|
|
118
|
+
export declare function NumberFormat(props: {
|
|
119
|
+
value: number
|
|
120
|
+
style?: string
|
|
121
|
+
}): Promise<ReactElement>
|
|
122
|
+
|
|
123
|
+
export declare function FluentProvider(props: {
|
|
124
|
+
locale?: string
|
|
125
|
+
children: ReactNode
|
|
126
|
+
}): Promise<ReactElement>
|
|
127
|
+
`,`utf-8`),a}function u(e){return e.split(`\\`).join(`/`)}function d(e){if(e&&f(e))return p({},e);let t=e??{};return function(e){return p(t,e??{})}}function f(e){return[`reactStrictMode`,`experimental`,`images`,`env`,`webpack`,`rewrites`,`redirects`,`headers`,`pageExtensions`,`output`,`basePath`,`i18n`,`trailingSlash`,`compiler`,`transpilePackages`].some(t=>t in e)}function p(t,n){let r=process.cwd(),i=l(r,a(r,t)),o=(0,e.resolve)(typeof __dirname<`u`?__dirname:(0,e.dirname)(new URL({}.url).pathname),`loader.js`),s=n.webpack;return{...n,webpack(e,t){return e.module.rules.push({test:/\.[jt]sx?$/,enforce:`pre`,exclude:[/node_modules/,/\.next/],use:[{loader:o,options:{serverModulePath:i}}]}),e.resolve=e.resolve??{},e.resolve.alias=e.resolve.alias??{},e.resolve.alias[`@fluenti/next/__generated`]=i,s?s(e,t):e}}}exports.withFluenti=d;
|
|
128
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/read-config.ts","../src/generate-server-module.ts","../src/with-fluenti.ts"],"sourcesContent":["import { existsSync, readFileSync, rmSync, writeFileSync } from 'node:fs'\nimport { createRequire } from 'node:module'\nimport { basename, dirname, extname, join, resolve } from 'node:path'\nimport type { FluentiConfig } from '@fluenti/core'\nimport type { WithFluentConfig, ResolvedFluentConfig } from './types'\n\nconst runtimeModulePath = typeof __filename === 'string'\n ? __filename\n : import.meta.url\nconst require = createRequire(runtimeModulePath)\nconst { createJiti } = require('jiti') as {\n createJiti: (\n url: string,\n options?: { moduleCache?: boolean; interopDefault?: boolean },\n ) => (path: string) => unknown\n}\n\n/**\n * Read fluenti.config.ts and merge with withFluenti() overrides.\n */\nexport function resolveConfig(\n projectRoot: string,\n overrides?: WithFluentConfig,\n): ResolvedFluentConfig {\n const fileConfig = readFluentConfigSync(projectRoot)\n\n const defaultLocale = overrides?.defaultLocale\n ?? fileConfig?.sourceLocale\n ?? 'en'\n\n const locales = overrides?.locales\n ?? fileConfig?.locales\n ?? [defaultLocale]\n\n const compiledDir = overrides?.compiledDir\n ?? fileConfig?.compileOutDir\n ?? './src/locales/compiled'\n\n const serverModuleOutDir = overrides?.serverModuleOutDir\n ?? join('node_modules', '.fluenti')\n\n const resolved: ResolvedFluentConfig = {\n locales,\n defaultLocale,\n compiledDir,\n serverModule: overrides?.serverModule ?? null,\n serverModuleOutDir,\n }\n if (overrides?.resolveLocale) resolved.resolveLocale = overrides.resolveLocale\n if (overrides?.dateFormats) resolved.dateFormats = overrides.dateFormats\n if (overrides?.numberFormats) resolved.numberFormats = overrides.numberFormats\n if (overrides?.fallbackChain) resolved.fallbackChain = overrides.fallbackChain\n return resolved\n}\n\n/**\n * Attempt to read fluenti.config.ts synchronously.\n * Returns null if file doesn't exist or can't be parsed.\n */\nfunction readFluentConfigSync(projectRoot: string): FluentiConfig | null {\n const jiti = createJiti(runtimeModulePath, {\n moduleCache: false,\n interopDefault: true,\n })\n const candidates = [\n 'fluenti.config.ts',\n 'fluenti.config.js',\n 'fluenti.config.mjs',\n ]\n\n for (const name of candidates) {\n const configPath = resolve(projectRoot, name)\n if (existsSync(configPath)) {\n try {\n return normalizeLoadedConfig(jiti(configPath) as FluentiConfig | { default?: FluentiConfig })\n } catch {\n const rewritten = tryLoadConfigViaDefineConfigShim(configPath, jiti)\n if (rewritten) {\n return rewritten\n }\n return null\n }\n }\n }\n\n return null\n}\n\nfunction tryLoadConfigViaDefineConfigShim(\n configPath: string,\n jiti: (path: string) => unknown,\n): FluentiConfig | null {\n const source = readFileSync(configPath, 'utf8')\n const importMatch = source.match(\n /import\\s*\\{\\s*defineConfig(?:\\s+as\\s+([A-Za-z_$][\\w$]*))?\\s*\\}\\s*from\\s*['\"]@fluenti\\/cli['\"]\\s*;?/,\n )\n if (!importMatch) {\n return null\n }\n\n const helperName = importMatch[1] ?? 'defineConfig'\n const rewrittenSource = source.replace(importMatch[0], '')\n const tempPath = join(\n dirname(configPath),\n `.${basename(configPath, extname(configPath))}.next-plugin-read-config${extname(configPath) || '.ts'}`,\n )\n\n writeFileSync(tempPath, `const ${helperName} = (config) => config\\n${rewrittenSource}`, 'utf8')\n\n try {\n return normalizeLoadedConfig(jiti(tempPath) as FluentiConfig | { default?: FluentiConfig })\n } catch {\n return null\n } finally {\n rmSync(tempPath, { force: true })\n }\n}\n\nfunction normalizeLoadedConfig(\n mod: FluentiConfig | { default?: FluentiConfig },\n): FluentiConfig {\n return typeof mod === 'object' && mod !== null && 'default' in mod\n ? (mod.default ?? {}) as FluentiConfig\n : mod as FluentiConfig\n}\n","import { writeFileSync, mkdirSync, existsSync } from 'node:fs'\nimport { resolve, relative } from 'node:path'\nimport type { ResolvedFluentConfig } from './types'\n\n/**\n * Generate the server module that provides:\n * - setLocale / getI18n\n * - Trans / Plural / Select / DateTime / NumberFormat (server components)\n * - FluentProvider (async server component for layouts)\n *\n * @returns Absolute path to the generated server module.\n */\nexport function generateServerModule(\n projectRoot: string,\n config: ResolvedFluentConfig,\n): string {\n if (config.serverModule) {\n return resolve(projectRoot, config.serverModule)\n }\n\n const outDir = resolve(projectRoot, config.serverModuleOutDir)\n const outPath = resolve(outDir, 'server.js')\n const dtsPath = resolve(outDir, 'server.d.ts')\n\n if (!existsSync(outDir)) {\n mkdirSync(outDir, { recursive: true })\n }\n\n const compiledDirAbs = resolve(projectRoot, config.compiledDir)\n const compiledRelative = toForwardSlash(relative(outDir, compiledDirAbs))\n\n const localeImports = config.locales\n .map((locale) => ` case '${locale}': return import('${compiledRelative}/${locale}')`)\n .join('\\n')\n\n const fallbackChainStr = config.fallbackChain\n ? JSON.stringify(config.fallbackChain)\n : 'undefined'\n\n // Generate a 'use client' provider that imports messages statically.\n // Messages contain functions (interpolation) which can't cross the RSC boundary.\n const clientProviderPath = resolve(outDir, 'client-provider.js')\n\n const clientStaticImports = config.locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `import ${safe} from '${compiledRelative}/${locale}'`\n })\n .join('\\n')\n\n const clientAllMessagesEntries = config.locales\n .map((locale) => {\n const safe = locale.replace(/[^a-zA-Z0-9]/g, '_')\n return `'${locale}': ${safe}`\n })\n .join(', ')\n\n const clientProviderSource = `\"use client\";\n// Auto-generated by @fluenti/next — do not edit\nimport { createElement } from 'react'\nimport { I18nProvider } from '@fluenti/react'\n${clientStaticImports}\n\nconst __allMessages = { ${clientAllMessagesEntries} }\n\nexport function ClientI18nProvider({ locale, fallbackLocale, fallbackChain, children }) {\n return createElement(I18nProvider, { locale, fallbackLocale, messages: __allMessages, fallbackChain }, children)\n}\n`\n writeFileSync(clientProviderPath, clientProviderSource, 'utf-8')\n\n const resolveLocaleImport = config.resolveLocale\n ? `import __resolveLocale from '${config.resolveLocale}'`\n : null\n\n const resolveLocaleFn = config.resolveLocale\n ? `resolveLocale: __resolveLocale,`\n : `resolveLocale: async () => {\n try {\n const { cookies } = await import('next/headers')\n return (await cookies()).get('locale')?.value ?? '${config.defaultLocale}'\n } catch {\n return '${config.defaultLocale}'\n }\n },`\n\n const moduleSource = `// Auto-generated by @fluenti/next — do not edit\nimport { createServerI18n } from '@fluenti/react/server'\nimport { createElement } from 'react'\n${resolveLocaleImport ? `${resolveLocaleImport}\\n` : ''}\nconst serverI18n = createServerI18n({\n loadMessages: async (locale) => {\n switch (locale) {\n${localeImports}\n default: return import('${compiledRelative}/${config.defaultLocale}')\n }\n },\n fallbackLocale: '${config.defaultLocale}',\n fallbackChain: ${fallbackChainStr},\n ${resolveLocaleFn}\n})\n\nexport const setLocale = serverI18n.setLocale\nexport const getI18n = serverI18n.getI18n\nexport const t = (..._args) => {\n throw new Error(\n \"[fluenti] \\`t\\` imported from '@fluenti/next/__generated' is a compile-time API. \" +\n 'Use it only with the Fluenti loader inside an async server scope.',\n )\n}\nexport const Trans = serverI18n.Trans\nexport const Plural = serverI18n.Plural\nexport const Select = serverI18n.Select\nexport const DateTime = serverI18n.DateTime\nexport const NumberFormat = serverI18n.NumberFormat\n\n/**\n * Async server component for root layouts.\n *\n * Sets up both server-side (React.cache) and client-side (I18nProvider) i18n.\n */\nexport async function FluentProvider({ locale, children }) {\n const activeLocale = locale ?? '${config.defaultLocale}'\n\n // 1. Initialize server-side i18n (React.cache scoped)\n serverI18n.setLocale(activeLocale)\n await serverI18n.getI18n()\n\n // 2. Import the local 'use client' provider that has messages statically bundled.\n // Messages contain functions (interpolation) which can't be serialized across the RSC boundary.\n const { ClientI18nProvider } = await import('./client-provider.js')\n\n return createElement(ClientI18nProvider, {\n locale: activeLocale,\n fallbackLocale: '${config.defaultLocale}',\n fallbackChain: ${fallbackChainStr},\n }, children)\n}\n`\n\n const dtsSource = `// Auto-generated by @fluenti/next — do not edit\nimport type { ReactNode, ReactElement } from 'react'\nimport type { CompileTimeT, FluentInstanceExtended } from '@fluenti/core'\n\nexport declare function setLocale(locale: string): void\nexport declare function getI18n(): Promise<FluentInstanceExtended & { locale: string }>\nexport declare const t: CompileTimeT\n\nexport declare function Trans(props: {\n children: ReactNode\n id?: string\n context?: string\n comment?: string\n render?: (translation: ReactNode) => ReactNode\n}): Promise<ReactElement>\n\nexport declare function Plural(props: {\n value: number\n id?: string\n context?: string\n comment?: string\n zero?: ReactNode\n one?: ReactNode\n two?: ReactNode\n few?: ReactNode\n many?: ReactNode\n other: ReactNode\n offset?: number\n}): Promise<ReactElement>\n\nexport declare function Select(props: {\n value: string\n id?: string\n context?: string\n comment?: string\n other: ReactNode\n options?: Record<string, ReactNode>\n [key: string]: ReactNode | Record<string, ReactNode> | string | undefined\n}): Promise<ReactElement>\n\nexport declare function DateTime(props: {\n value: Date | number\n style?: string\n}): Promise<ReactElement>\n\nexport declare function NumberFormat(props: {\n value: number\n style?: string\n}): Promise<ReactElement>\n\nexport declare function FluentProvider(props: {\n locale?: string\n children: ReactNode\n}): Promise<ReactElement>\n`\n\n writeFileSync(outPath, moduleSource, 'utf-8')\n writeFileSync(dtsPath, dtsSource, 'utf-8')\n\n return outPath\n}\n\nfunction toForwardSlash(p: string): string {\n return p.split('\\\\').join('/')\n}\n","import { resolve, dirname } from 'node:path'\nimport type { WithFluentConfig } from './types'\nimport { resolveConfig } from './read-config'\nimport { generateServerModule } from './generate-server-module'\n\ntype NextConfig = Record<string, unknown>\n\n/**\n * Wrap your Next.js config with Fluenti support.\n *\n * Adds a webpack loader that transforms `t\\`\\`` and `t()` calls,\n * and generates a server module for RSC i18n.\n *\n * @example\n * ```ts\n * // next.config.ts — function style (recommended)\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti()({ reactStrictMode: true })\n * ```\n *\n * @example\n * ```ts\n * // next.config.ts — direct style\n * import { withFluenti } from '@fluenti/next'\n * export default withFluenti({ reactStrictMode: true })\n * ```\n */\nexport function withFluenti(fluentConfig?: WithFluentConfig): (nextConfig?: NextConfig) => NextConfig\nexport function withFluenti(nextConfig: NextConfig): NextConfig\nexport function withFluenti(\n configOrNext?: WithFluentConfig | NextConfig,\n): NextConfig | ((nextConfig?: NextConfig) => NextConfig) {\n if (configOrNext && isNextConfig(configOrNext as NextConfig)) {\n return applyFluenti({}, configOrNext as NextConfig)\n }\n\n const fluentConfig = (configOrNext ?? {}) as WithFluentConfig\n return function wrappedConfig(nextConfig?: NextConfig): NextConfig {\n return applyFluenti(fluentConfig, nextConfig ?? {})\n }\n}\n\nfunction isNextConfig(obj: NextConfig): boolean {\n const nextKeys = [\n 'reactStrictMode', 'experimental', 'images', 'env', 'webpack',\n 'rewrites', 'redirects', 'headers', 'pageExtensions', 'output',\n 'basePath', 'i18n', 'trailingSlash', 'compiler', 'transpilePackages',\n ]\n return nextKeys.some((key) => key in obj)\n}\n\nfunction applyFluenti(\n fluentConfig: WithFluentConfig,\n nextConfig: NextConfig,\n): NextConfig {\n const projectRoot = process.cwd()\n const resolved = resolveConfig(projectRoot, fluentConfig)\n\n // Generate server module for RSC\n const serverModulePath = generateServerModule(projectRoot, resolved)\n\n // Resolve the loader path — use import.meta.url for ESM compatibility\n const thisDir = typeof __dirname !== 'undefined'\n ? __dirname\n : dirname(new URL(import.meta.url).pathname)\n const loaderPath = resolve(thisDir, 'loader.js')\n\n const existingWebpack = nextConfig['webpack'] as\n | ((config: WebpackConfig, options: WebpackOptions) => WebpackConfig)\n | undefined\n\n return {\n ...nextConfig,\n webpack(config: WebpackConfig, options: WebpackOptions) {\n // Add fluenti loader (enforce: pre — runs before other loaders)\n config.module.rules.push({\n test: /\\.[jt]sx?$/,\n enforce: 'pre' as const,\n exclude: [/node_modules/, /\\.next/],\n use: [\n {\n loader: loaderPath,\n options: {\n serverModulePath,\n },\n },\n ],\n })\n\n // Add resolve alias so loader can import from generated server module\n config.resolve = config.resolve ?? {} as WebpackConfig['resolve']\n config.resolve.alias = config.resolve.alias ?? {}\n config.resolve.alias['@fluenti/next/__generated'] = serverModulePath\n\n // Call user's webpack config if provided\n if (existingWebpack) {\n return existingWebpack(config, options)\n }\n\n return config\n },\n }\n}\n\n// Minimal webpack types for the config function\ninterface WebpackConfig {\n module: {\n rules: Array<{\n test: RegExp\n enforce?: 'pre' | 'post'\n exclude?: Array<RegExp>\n use: Array<{ loader: string; options: Record<string, unknown> }>\n }>\n }\n resolve: {\n alias?: Record<string, string>\n }\n}\n\ninterface WebpackOptions {\n isServer: boolean\n dev: boolean\n}\n"],"mappings":"4IAMA,IAAM,EAAoB,OAAO,YAAe,SAC5C,WAAA,EAAA,CACY,IAEV,CAAE,eAAA,EAAA,EAAA,eADsB,EAAkB,CACjB,OAAO,CAUtC,SAAgB,EACd,EACA,EACsB,CACtB,IAAM,EAAa,EAAqB,EAAY,CAE9C,EAAgB,GAAW,eAC5B,GAAY,cACZ,KAEC,EAAU,GAAW,SACtB,GAAY,SACZ,CAAC,EAAc,CAEd,EAAc,GAAW,aAC1B,GAAY,eACZ,yBAEC,EAAqB,GAAW,qBAAA,EAAA,EAAA,MAC5B,eAAgB,WAAW,CAE/B,EAAiC,CACrC,UACA,gBACA,cACA,aAAc,GAAW,cAAgB,KACzC,qBACD,CAKD,OAJI,GAAW,gBAAe,EAAS,cAAgB,EAAU,eAC7D,GAAW,cAAa,EAAS,YAAc,EAAU,aACzD,GAAW,gBAAe,EAAS,cAAgB,EAAU,eAC7D,GAAW,gBAAe,EAAS,cAAgB,EAAU,eAC1D,EAOT,SAAS,EAAqB,EAA2C,CACvE,IAAM,EAAO,EAAW,EAAmB,CACzC,YAAa,GACb,eAAgB,GACjB,CAAC,CAOF,IAAK,IAAM,IANQ,CACjB,oBACA,oBACA,qBACD,CAE8B,CAC7B,IAAM,GAAA,EAAA,EAAA,SAAqB,EAAa,EAAK,CAC7C,IAAA,EAAA,EAAA,YAAe,EAAW,CACxB,GAAI,CACF,OAAO,EAAsB,EAAK,EAAW,CAAgD,MACvF,CAKN,OAJkB,EAAiC,EAAY,EAAK,EAI7D,MAKb,OAAO,KAGT,SAAS,EACP,EACA,EACsB,CACtB,IAAM,GAAA,EAAA,EAAA,cAAsB,EAAY,OAAO,CACzC,EAAc,EAAO,MACzB,qGACD,CACD,GAAI,CAAC,EACH,OAAO,KAGT,IAAM,EAAa,EAAY,IAAM,eAC/B,EAAkB,EAAO,QAAQ,EAAY,GAAI,GAAG,CACpD,GAAA,EAAA,EAAA,OAAA,EAAA,EAAA,SACI,EAAW,CACnB,KAAA,EAAA,EAAA,UAAa,GAAA,EAAA,EAAA,SAAoB,EAAW,CAAC,CAAC,2BAAA,EAAA,EAAA,SAAkC,EAAW,EAAI,QAChG,EAED,EAAA,EAAA,eAAc,EAAU,SAAS,EAAW,yBAAyB,IAAmB,OAAO,CAE/F,GAAI,CACF,OAAO,EAAsB,EAAK,EAAS,CAAgD,MACrF,CACN,OAAO,YACC,EACR,EAAA,EAAA,QAAO,EAAU,CAAE,MAAO,GAAM,CAAC,EAIrC,SAAS,EACP,EACe,CACf,OAAO,OAAO,GAAQ,UAAY,GAAgB,YAAa,EAC1D,EAAI,SAAW,EAAE,CAClB,EC/GN,SAAgB,EACd,EACA,EACQ,CACR,GAAI,EAAO,aACT,OAAA,EAAA,EAAA,SAAe,EAAa,EAAO,aAAa,CAGlD,IAAM,GAAA,EAAA,EAAA,SAAiB,EAAa,EAAO,mBAAmB,CACxD,GAAA,EAAA,EAAA,SAAkB,EAAQ,YAAY,CACtC,GAAA,EAAA,EAAA,SAAkB,EAAQ,cAAc,EAE1C,EAAA,EAAA,YAAY,EAAO,GACrB,EAAA,EAAA,WAAU,EAAQ,CAAE,UAAW,GAAM,CAAC,CAIxC,IAAM,EAAmB,GAAA,EAAA,EAAA,UAAwB,GAAA,EAAA,EAAA,SADlB,EAAa,EAAO,YAAY,CACS,CAAC,CAEnE,EAAgB,EAAO,QAC1B,IAAK,GAAW,eAAe,EAAO,oBAAoB,EAAiB,GAAG,EAAO,IAAI,CACzF,KAAK;EAAK,CAEP,EAAmB,EAAO,cAC5B,KAAK,UAAU,EAAO,cAAc,CACpC,aAgCJ,EAAA,EAAA,gBAAA,EAAA,EAAA,SA5BmC,EAAQ,qBAAqB,CAgBnC;;;;EAdD,EAAO,QAChC,IAAK,GAEG,UADM,EAAO,QAAQ,gBAAiB,IAAI,CAC3B,SAAS,EAAiB,GAAG,EAAO,GAC1D,CACD,KAAK;EAAK,CAaO;;0BAXa,EAAO,QACrC,IAAK,GAEG,IAAI,EAAO,KADL,EAAO,QAAQ,gBAAiB,IAAI,GAEjD,CACD,KAAK,KAAK,CAQoC;;;;;EAMO,QAAQ,CAEhE,IAAM,EAAsB,EAAO,cAC/B,gCAAgC,EAAO,cAAc,GACrD,KAEE,EAAkB,EAAO,cAC3B,kCACA;;;0DAGoD,EAAO,cAAc;;gBAE/D,EAAO,cAAc;;MAI7B,EAAe;;;EAGrB,EAAsB,GAAG,EAAoB,IAAM,GAAG;;;;EAItD,EAAc;gCACgB,EAAiB,GAAG,EAAO,cAAc;;;qBAGpD,EAAO,cAAc;mBACvB,EAAiB;IAChC,EAAgB;;;;;;;;;;;;;;;;;;;;;;;oCAuBgB,EAAO,cAAc;;;;;;;;;;;;uBAYlC,EAAO,cAAc;qBACvB,EAAiB;;;EAgEpC,OAHA,EAAA,EAAA,eAAc,EAAS,EAAc,QAAQ,EAC7C,EAAA,EAAA,eAAc,EAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAAW,QAAQ,CAEnC,EAGT,SAAS,EAAe,EAAmB,CACzC,OAAO,EAAE,MAAM,KAAK,CAAC,KAAK,IAAI,CC9KhC,SAAgB,EACd,EACwD,CACxD,GAAI,GAAgB,EAAa,EAA2B,CAC1D,OAAO,EAAa,EAAE,CAAE,EAA2B,CAGrD,IAAM,EAAgB,GAAgB,EAAE,CACxC,OAAO,SAAuB,EAAqC,CACjE,OAAO,EAAa,EAAc,GAAc,EAAE,CAAC,EAIvD,SAAS,EAAa,EAA0B,CAM9C,MALiB,CACf,kBAAmB,eAAgB,SAAU,MAAO,UACpD,WAAY,YAAa,UAAW,iBAAkB,SACtD,WAAY,OAAQ,gBAAiB,WAAY,oBAClD,CACe,KAAM,GAAQ,KAAO,EAAI,CAG3C,SAAS,EACP,EACA,EACY,CACZ,IAAM,EAAc,QAAQ,KAAK,CAI3B,EAAmB,EAAqB,EAH7B,EAAc,EAAa,EAAa,CAGW,CAM9D,GAAA,EAAA,EAAA,SAHU,OAAO,UAAc,IACjC,WAAA,EAAA,EAAA,SACQ,IAAI,IAAA,EAAA,CAAgB,IAAI,CAAC,SAAS,CACV,YAAY,CAE1C,EAAkB,EAAW,QAInC,MAAO,CACL,GAAG,EACH,QAAQ,EAAuB,EAAyB,CA0BtD,OAxBA,EAAO,OAAO,MAAM,KAAK,CACvB,KAAM,aACN,QAAS,MACT,QAAS,CAAC,eAAgB,SAAS,CACnC,IAAK,CACH,CACE,OAAQ,EACR,QAAS,CACP,mBACD,CACF,CACF,CACF,CAAC,CAGF,EAAO,QAAU,EAAO,SAAW,EAAE,CACrC,EAAO,QAAQ,MAAQ,EAAO,QAAQ,OAAS,EAAE,CACjD,EAAO,QAAQ,MAAM,6BAA+B,EAGhD,EACK,EAAgB,EAAQ,EAAQ,CAGlC,GAEV"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fluenti/next — Next.js plugin for Fluenti
|
|
3
|
+
*
|
|
4
|
+
* Provides:
|
|
5
|
+
* - `withFluenti()` — wraps next.config.ts with t`` transform support
|
|
6
|
+
* - FluentProvider — async server component (exported from generated module)
|
|
7
|
+
* - Webpack loader for strict, binding-aware tagged-template optimization
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* // next.config.ts
|
|
12
|
+
* import { withFluenti } from '@fluenti/next'
|
|
13
|
+
* export default withFluenti()({ reactStrictMode: true })
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export { withFluenti } from './with-fluenti';
|
|
17
|
+
export type { WithFluentConfig, FluentProviderProps } from './types';
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAA;AAC5C,YAAY,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,SAAS,CAAA"}
|