@fluenti/nuxt 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.
Files changed (77) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -0
  3. package/dist/detectors-CFJvuwzi.cjs +2 -0
  4. package/dist/detectors-CFJvuwzi.cjs.map +1 -0
  5. package/dist/detectors-DhkfHBHr.js +84 -0
  6. package/dist/detectors-DhkfHBHr.js.map +1 -0
  7. package/dist/locale-head-BfsESdd7.cjs +2 -0
  8. package/dist/locale-head-BfsESdd7.cjs.map +1 -0
  9. package/dist/locale-head-CqUlvy3O.js +38 -0
  10. package/dist/locale-head-CqUlvy3O.js.map +1 -0
  11. package/dist/module.cjs +2 -0
  12. package/dist/module.cjs.map +1 -0
  13. package/dist/module.js +104 -0
  14. package/dist/module.js.map +1 -0
  15. package/dist/page-extend-DJ7wpuVj.js +25 -0
  16. package/dist/page-extend-DJ7wpuVj.js.map +1 -0
  17. package/dist/page-extend-OvV-ZhFB.cjs +2 -0
  18. package/dist/page-extend-OvV-ZhFB.cjs.map +1 -0
  19. package/dist/path-utils-BQIsp_or.cjs +2 -0
  20. package/dist/path-utils-BQIsp_or.cjs.map +1 -0
  21. package/dist/path-utils-BcvXLCGi.js +28 -0
  22. package/dist/path-utils-BcvXLCGi.js.map +1 -0
  23. package/dist/runtime/client.cjs +2 -0
  24. package/dist/runtime/client.cjs.map +1 -0
  25. package/dist/runtime/client.d.ts +25 -0
  26. package/dist/runtime/client.d.ts.map +1 -0
  27. package/dist/runtime/client.js +18 -0
  28. package/dist/runtime/client.js.map +1 -0
  29. package/dist/runtime/components/NuxtLinkLocale.cjs +2 -0
  30. package/dist/runtime/components/NuxtLinkLocale.cjs.map +1 -0
  31. package/dist/runtime/components/NuxtLinkLocale.d.ts +44 -0
  32. package/dist/runtime/components/NuxtLinkLocale.d.ts.map +1 -0
  33. package/dist/runtime/components/NuxtLinkLocale.js +28 -0
  34. package/dist/runtime/components/NuxtLinkLocale.js.map +1 -0
  35. package/dist/runtime/composables.cjs +2 -0
  36. package/dist/runtime/composables.cjs.map +1 -0
  37. package/dist/runtime/composables.d.ts +37 -0
  38. package/dist/runtime/composables.d.ts.map +1 -0
  39. package/dist/runtime/composables.js +25 -0
  40. package/dist/runtime/composables.js.map +1 -0
  41. package/dist/runtime/detectors/cookie.d.ts +4 -0
  42. package/dist/runtime/detectors/cookie.d.ts.map +1 -0
  43. package/dist/runtime/detectors/header.d.ts +4 -0
  44. package/dist/runtime/detectors/header.d.ts.map +1 -0
  45. package/dist/runtime/detectors/index.d.ts +12 -0
  46. package/dist/runtime/detectors/index.d.ts.map +1 -0
  47. package/dist/runtime/detectors/path.d.ts +4 -0
  48. package/dist/runtime/detectors/path.d.ts.map +1 -0
  49. package/dist/runtime/detectors/query.d.ts +4 -0
  50. package/dist/runtime/detectors/query.d.ts.map +1 -0
  51. package/dist/runtime/index.cjs +1 -0
  52. package/dist/runtime/index.d.ts +8 -0
  53. package/dist/runtime/index.d.ts.map +1 -0
  54. package/dist/runtime/index.js +6 -0
  55. package/dist/runtime/locale-head.d.ts +30 -0
  56. package/dist/runtime/locale-head.d.ts.map +1 -0
  57. package/dist/runtime/middleware/locale-redirect.cjs +2 -0
  58. package/dist/runtime/middleware/locale-redirect.cjs.map +1 -0
  59. package/dist/runtime/middleware/locale-redirect.d.ts +10 -0
  60. package/dist/runtime/middleware/locale-redirect.d.ts.map +1 -0
  61. package/dist/runtime/middleware/locale-redirect.js +19 -0
  62. package/dist/runtime/middleware/locale-redirect.js.map +1 -0
  63. package/dist/runtime/page-extend.d.ts +19 -0
  64. package/dist/runtime/page-extend.d.ts.map +1 -0
  65. package/dist/runtime/path-utils.d.ts +19 -0
  66. package/dist/runtime/path-utils.d.ts.map +1 -0
  67. package/dist/runtime/plugin.cjs +2 -0
  68. package/dist/runtime/plugin.cjs.map +1 -0
  69. package/dist/runtime/plugin.d.ts +9 -0
  70. package/dist/runtime/plugin.d.ts.map +1 -0
  71. package/dist/runtime/plugin.js +30 -0
  72. package/dist/runtime/plugin.js.map +1 -0
  73. package/dist/runtime/standalone-composables.d.ts +19 -0
  74. package/dist/runtime/standalone-composables.d.ts.map +1 -0
  75. package/dist/types.d.ts +112 -0
  76. package/dist/types.d.ts.map +1 -0
  77. package/package.json +90 -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,311 @@
1
+ # @fluenti/nuxt
2
+
3
+ [![npm version](https://img.shields.io/npm/v/@fluenti/nuxt?color=4f46e5&label=npm)](https://www.npmjs.com/package/@fluenti/nuxt)
4
+ [![bundle size](https://img.shields.io/bundlephobia/minzip/@fluenti/nuxt?color=16a34a&label=size)](https://bundlephobia.com/package/@fluenti/nuxt)
5
+ [![license](https://img.shields.io/npm/l/@fluenti/nuxt?color=888)](https://github.com/usefluenti/fluenti/blob/main/LICENSE)
6
+
7
+ **Compile-time i18n for Nuxt 3** -- locale routing, SEO head tags, and auto-imported composables that feel native to your Nuxt app. Zero runtime parsing, zero boilerplate.
8
+
9
+ ## Why @fluenti/nuxt?
10
+
11
+ - **Auto-imported composables** -- `useLocalePath`, `useSwitchLocalePath`, `useLocaleHead`, and `useI18n` are available everywhere with no imports
12
+ - **SEO-ready** -- `hreflang` alternates, `og:locale` meta, and `html[lang]` generated automatically
13
+ - **Locale routing** -- four URL strategies (`prefix`, `prefix_except_default`, `prefix_and_default`, `no_prefix`)
14
+ - **Smart locale detection** -- configurable chain of path, cookie, header, and query detectors
15
+ - **SSR / SSG / SPA / ISR** -- works in every Nuxt rendering mode out of the box
16
+ - **Compile-time messages** -- translations are compiled at build time via `@fluenti/vite-plugin`, not interpreted at runtime
17
+
18
+ ## Quick Start
19
+
20
+ ### 1. Install
21
+
22
+ ```bash
23
+ pnpm add @fluenti/nuxt @fluenti/core @fluenti/vue
24
+ ```
25
+
26
+ ### 2. Configure
27
+
28
+ ```ts
29
+ // nuxt.config.ts
30
+ export default defineNuxtConfig({
31
+ modules: ['@fluenti/nuxt'],
32
+ fluenti: {
33
+ locales: ['en', 'ja', 'zh'],
34
+ defaultLocale: 'en',
35
+ strategy: 'prefix_except_default',
36
+ },
37
+ })
38
+ ```
39
+
40
+ ### 3. Use in Pages
41
+
42
+ ```vue
43
+ <script setup>
44
+ // All composables are auto-imported -- no import needed
45
+ const { t, locale, setLocale } = useI18n()
46
+ const localePath = useLocalePath()
47
+ const switchLocalePath = useSwitchLocalePath()
48
+ </script>
49
+
50
+ <template>
51
+ <h1>{{ t`Hello, world!` }}</h1>
52
+
53
+ <nav>
54
+ <NuxtLinkLocale to="/about">{{ t`About` }}</NuxtLinkLocale>
55
+ <NuxtLink :to="localePath('/contact')">{{ t`Contact` }}</NuxtLink>
56
+ </nav>
57
+
58
+ <div>
59
+ <button @click="setLocale('en')">English</button>
60
+ <button @click="setLocale('ja')">日本語</button>
61
+ </div>
62
+ </template>
63
+ ```
64
+
65
+ That's it. Locale routing, cookie persistence, and SEO tags work automatically.
66
+
67
+ ## Syntax Sugar
68
+
69
+ ### `v-t` Directive
70
+
71
+ The `v-t` directive compiles translations at build time -- no runtime overhead:
72
+
73
+ ```vue
74
+ <template>
75
+ <h1 v-t>Hello, world!</h1>
76
+ <p v-t>Welcome to {name}'s site</p>
77
+ </template>
78
+ ```
79
+
80
+ ### `<Trans>` Component
81
+
82
+ For rich text with embedded HTML and components:
83
+
84
+ ```vue
85
+ <template>
86
+ <Trans>
87
+ Read the <NuxtLink to="/docs">documentation</NuxtLink> to get started.
88
+ </Trans>
89
+ </template>
90
+ ```
91
+
92
+ ### Template Literal Tag
93
+
94
+ Use `` t`...` `` in script and template expressions:
95
+
96
+ ```vue
97
+ <script setup>
98
+ const { t } = useI18n()
99
+ const greeting = computed(() => t`Hello, {name}!`)
100
+ </script>
101
+
102
+ <template>
103
+ <p>{{ t`You have {count, plural, one {# item} other {# items}}` }}</p>
104
+ </template>
105
+ ```
106
+
107
+ ## Auto-imported APIs
108
+
109
+ Every API below is auto-imported by the module -- no `import` statements required:
110
+
111
+ | API | Source | Purpose |
112
+ |-----|--------|---------|
113
+ | `useI18n()` | `@fluenti/vue` | Access `t`, `locale`, `setLocale`, and message catalogs |
114
+ | `useLocalePath()` | `@fluenti/nuxt` | Generate locale-prefixed paths |
115
+ | `useSwitchLocalePath()` | `@fluenti/nuxt` | Get the current page path in another locale |
116
+ | `useLocaleHead()` | `@fluenti/nuxt` | Generate SEO `<head>` metadata |
117
+ | `<NuxtLinkLocale>` | `@fluenti/nuxt` | Locale-aware `<NuxtLink>` component |
118
+
119
+ ## Locale Routing
120
+
121
+ Four URL strategies to match your project requirements:
122
+
123
+ | Strategy | Default locale | Other locales | Best for |
124
+ |----------|---------------|---------------|----------|
125
+ | `prefix_except_default` | `/about` | `/ja/about` | Most apps -- clean URLs for default locale |
126
+ | `prefix` | `/en/about` | `/ja/about` | Multilingual-first sites |
127
+ | `prefix_and_default` | `/about` + `/en/about` | `/ja/about` | Migration from prefix to no-prefix |
128
+ | `no_prefix` | `/about` | `/about` | Cookie/header detection only |
129
+
130
+ The module extends your Nuxt pages automatically -- locale-prefixed route variants are generated at build time with zero manual route config.
131
+
132
+ ### Locale Redirect Middleware
133
+
134
+ With the `prefix` strategy, a global middleware redirects unprefixed URLs:
135
+
136
+ ```
137
+ GET /about --> 302 --> /en/about (detected via cookie/header/fallback)
138
+ GET /ja/about --> (no redirect)
139
+ ```
140
+
141
+ ## SEO Head Tags
142
+
143
+ Generate `hreflang` alternates and Open Graph locale meta with one composable:
144
+
145
+ ```vue
146
+ <script setup>
147
+ const head = useLocaleHead({
148
+ addSeoAttributes: true,
149
+ baseUrl: 'https://example.com',
150
+ })
151
+
152
+ useHead(head.value)
153
+ </script>
154
+ ```
155
+
156
+ Output:
157
+
158
+ ```html
159
+ <html lang="en">
160
+ <head>
161
+ <link rel="alternate" hreflang="en" href="https://example.com/about" />
162
+ <link rel="alternate" hreflang="ja" href="https://example.com/ja/about" />
163
+ <link rel="alternate" hreflang="x-default" href="https://example.com/about" />
164
+ <meta property="og:locale" content="en" />
165
+ <meta property="og:locale:alternate" content="ja" />
166
+ </head>
167
+ ```
168
+
169
+ ## Cookie-Based Locale Detection
170
+
171
+ Persist locale preference across visits with zero setup:
172
+
173
+ ```ts
174
+ // nuxt.config.ts
175
+ export default defineNuxtConfig({
176
+ fluenti: {
177
+ locales: ['en', 'ja', 'zh'],
178
+ defaultLocale: 'en',
179
+ detectBrowserLanguage: {
180
+ useCookie: true,
181
+ cookieKey: 'fluenti_locale',
182
+ fallbackLocale: 'en',
183
+ },
184
+ },
185
+ })
186
+ ```
187
+
188
+ The detection chain runs in order (`detectOrder: ['path', 'cookie', 'header']` by default). The first detector to resolve a locale wins.
189
+
190
+ | Detector | Reads from | Example |
191
+ |----------|-----------|---------|
192
+ | `path` | URL prefix | `/ja/about` -> `ja` |
193
+ | `cookie` | Cookie value | `fluenti_locale=ja` -> `ja` |
194
+ | `header` | `Accept-Language` | `ja,en;q=0.5` -> `ja` |
195
+ | `query` | Query parameter | `?locale=ja` -> `ja` |
196
+
197
+ ## Components
198
+
199
+ ### `<NuxtLinkLocale>`
200
+
201
+ A locale-aware drop-in replacement for `<NuxtLink>`:
202
+
203
+ ```vue
204
+ <template>
205
+ <!-- Automatically prefixes with current locale -->
206
+ <NuxtLinkLocale to="/about">About</NuxtLinkLocale>
207
+
208
+ <!-- Override locale -->
209
+ <NuxtLinkLocale to="/about" locale="ja">About (JA)</NuxtLinkLocale>
210
+ </template>
211
+ ```
212
+
213
+ All standard `<NuxtLink>` props are forwarded.
214
+
215
+ ## Composables
216
+
217
+ ### `useLocalePath()`
218
+
219
+ ```vue
220
+ <script setup>
221
+ const localePath = useLocalePath()
222
+ </script>
223
+
224
+ <template>
225
+ <NuxtLink :to="localePath('/about')">About</NuxtLink>
226
+ <NuxtLink :to="localePath('/about', 'ja')">About (JA)</NuxtLink>
227
+ </template>
228
+ ```
229
+
230
+ ### `useSwitchLocalePath()`
231
+
232
+ ```vue
233
+ <script setup>
234
+ const switchLocalePath = useSwitchLocalePath()
235
+ </script>
236
+
237
+ <template>
238
+ <NuxtLink :to="switchLocalePath('en')">English</NuxtLink>
239
+ <NuxtLink :to="switchLocalePath('ja')">日本語</NuxtLink>
240
+ </template>
241
+ ```
242
+
243
+ ### `useLocaleHead()`
244
+
245
+ ```ts
246
+ const head = useLocaleHead({
247
+ addSeoAttributes: true, // hreflang + og:locale
248
+ baseUrl: 'https://example.com',
249
+ })
250
+ useHead(head.value)
251
+ ```
252
+
253
+ ## Module Options
254
+
255
+ ```ts
256
+ // nuxt.config.ts
257
+ export default defineNuxtConfig({
258
+ fluenti: {
259
+ // Required
260
+ locales: ['en', 'ja', 'zh'],
261
+ defaultLocale: 'en',
262
+
263
+ // Routing
264
+ strategy: 'prefix_except_default',
265
+
266
+ // Locale detection
267
+ detectOrder: ['path', 'cookie', 'header'],
268
+ detectBrowserLanguage: {
269
+ useCookie: true,
270
+ cookieKey: 'fluenti_locale',
271
+ fallbackLocale: 'en',
272
+ },
273
+
274
+ // Build
275
+ autoVitePlugin: true,
276
+ componentPrefix: '',
277
+
278
+ // ISR
279
+ isr: { enabled: true, ttl: 3600 },
280
+ },
281
+ })
282
+ ```
283
+
284
+ | Option | Type | Default | Description |
285
+ |--------|------|---------|-------------|
286
+ | `locales` | `string[]` | -- | Supported locale codes (required) |
287
+ | `defaultLocale` | `string` | `'en'` | Default locale code (required) |
288
+ | `strategy` | `Strategy` | `'prefix_except_default'` | URL routing strategy |
289
+ | `detectOrder` | `string[]` | `['path', 'cookie', 'header']` | Ordered list of locale detectors |
290
+ | `detectBrowserLanguage` | `object` | -- | Cookie and fallback settings |
291
+ | `autoVitePlugin` | `boolean` | `true` | Auto-register `@fluenti/vite-plugin` |
292
+ | `componentPrefix` | `string` | `''` | Prefix for i18n components |
293
+ | `isr` | `{ enabled, ttl? }` | -- | ISR route rules generation |
294
+ | `compat` | `boolean` | `false` | Enable vue-i18n bridge mode |
295
+
296
+ ## SSR / SSG / SPA / ISR
297
+
298
+ The module works in every Nuxt rendering mode:
299
+
300
+ - **SSR** -- Full detection chain on the server; locale is hydrated to the client via payload
301
+ - **SSG** -- Locale-prefixed routes are pre-rendered automatically (`crawlLinks: true`)
302
+ - **SPA** -- Client-side detection from URL path, cookie, then defaults
303
+ - **ISR** -- Auto-generated `routeRules` with configurable TTL per locale pattern
304
+
305
+ ## Documentation
306
+
307
+ Full docs at [fluenti.dev](https://fluenti.dev).
308
+
309
+ ## License
310
+
311
+ [MIT](https://github.com/usefluenti/fluenti/blob/main/LICENSE)
@@ -0,0 +1,2 @@
1
+ const e=require(`./path-utils-BQIsp_or.cjs`);let t=require(`#imports`);function n(t){if(t.strategy===`no_prefix`)return;let{locale:n}=e.t(t.path,t.locales);n&&t.setLocale(n)}function r(e){if(!e.detectBrowserLanguage?.useCookie)return;let n=e.detectBrowserLanguage.cookieKey??`fluenti_locale`;try{let r=(0,t.useCookie)(n);r.value&&e.locales.includes(r.value)&&e.setLocale(r.value)}catch{}}function i(e){if(e.isServer)try{let n=(0,t.useRequestHeaders)([`accept-language`])[`accept-language`];if(n){let t=a(n,e.locales);t&&e.setLocale(t)}}catch{}}function a(e,t){let n=e.split(`,`).map(e=>{let[t,n]=e.trim().split(`;q=`);return{lang:t.trim().toLowerCase(),q:n?parseFloat(n):1}}).sort((e,t)=>t.q-e.q);for(let{lang:e}of n){if(t.includes(e))return e;let n=e.split(`-`)[0];if(t.includes(n))return n}return null}function o(e){try{let n=(0,t.useRoute)().query.locale;n&&e.locales.includes(n)&&e.setLocale(n)}catch{}}var s={path:n,cookie:r,header:i,query:o};async function c(e,t,n,r){let i=null,a=!1,o={path:e,locales:t.locales,defaultLocale:t.defaultLocale,strategy:t.strategy,...t.detectBrowserLanguage?{detectBrowserLanguage:t.detectBrowserLanguage}:{},detectedLocale:null,setLocale(e){t.locales.includes(e)&&(i=e,o.detectedLocale=e,a=!0)},isServer:{}.server??!1};for(let e of t.detectOrder){if(a)break;let t=s[e]??n?.get(e);t&&await t(o)}return r&&!a&&await r(o),i??t.detectBrowserLanguage?.fallbackLocale??t.defaultLocale}Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return c}});
2
+ //# sourceMappingURL=detectors-CFJvuwzi.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectors-CFJvuwzi.cjs","names":[],"sources":["../src/runtime/detectors/path.ts","../src/runtime/detectors/cookie.ts","../src/runtime/detectors/header.ts","../src/runtime/detectors/query.ts","../src/runtime/detectors/index.ts"],"sourcesContent":["import type { LocaleDetectContext } from '../../types'\nimport { extractLocaleFromPath } from '../path-utils'\n\n/** Detect locale from URL path prefix (e.g. /ja/about → 'ja') */\nexport default function detectPath(ctx: LocaleDetectContext): void {\n if (ctx.strategy === 'no_prefix') return\n const { locale } = extractLocaleFromPath(ctx.path, ctx.locales)\n if (locale) {\n ctx.setLocale(locale)\n }\n}\n","import { useCookie } from '#imports'\nimport type { LocaleDetectContext } from '../../types'\n\n/** Detect locale from cookie value */\nexport default function detectCookie(ctx: LocaleDetectContext): void {\n if (!ctx.detectBrowserLanguage?.useCookie) return\n const cookieKey = ctx.detectBrowserLanguage.cookieKey ?? 'fluenti_locale'\n try {\n const cookie = useCookie(cookieKey)\n if (cookie.value && ctx.locales.includes(cookie.value)) {\n ctx.setLocale(cookie.value)\n }\n } catch {\n // useCookie may fail outside Nuxt context\n }\n}\n","import { useRequestHeaders } from '#imports'\nimport type { LocaleDetectContext } from '../../types'\n\n/** Detect locale from Accept-Language header (SSR only) */\nexport default function detectHeader(ctx: LocaleDetectContext): void {\n if (!ctx.isServer) return\n try {\n const headers = useRequestHeaders(['accept-language'])\n const acceptLang = headers['accept-language']\n if (acceptLang) {\n const matched = negotiateLocale(acceptLang, ctx.locales)\n if (matched) {\n ctx.setLocale(matched)\n }\n }\n } catch {\n // May fail if not in a request context\n }\n}\n\nfunction negotiateLocale(acceptLanguage: string, locales: string[]): string | null {\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n const [lang, q] = part.trim().split(';q=')\n return { lang: lang!.trim().toLowerCase(), q: q ? parseFloat(q) : 1 }\n })\n .sort((a, b) => b.q - a.q)\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) return lang\n const prefix = lang.split('-')[0]!\n if (locales.includes(prefix)) return prefix\n }\n\n return null\n}\n","import { useRoute } from '#imports'\nimport type { LocaleDetectContext } from '../../types'\n\n/** Detect locale from query parameter (e.g. ?locale=ja) */\nexport default function detectQuery(ctx: LocaleDetectContext): void {\n try {\n const route = useRoute()\n const queryLocale = route.query['locale'] as string | undefined\n if (queryLocale && ctx.locales.includes(queryLocale)) {\n ctx.setLocale(queryLocale)\n }\n } catch {\n // May fail outside Nuxt context\n }\n}\n","import type { LocaleDetectContext, LocaleDetectorFn, FluentNuxtRuntimeConfig } from '../../types'\nimport detectPath from './path'\nimport detectCookie from './cookie'\nimport detectHeader from './header'\nimport detectQuery from './query'\n\n/** Map of built-in detector names to their implementations */\nconst builtinDetectors: Record<string, LocaleDetectorFn> = {\n path: detectPath,\n cookie: detectCookie,\n header: detectHeader,\n query: detectQuery,\n}\n\n/**\n * Run the detection chain: iterate through detectOrder, then fire the hook.\n *\n * Returns the detected locale or the defaultLocale as fallback.\n */\nexport async function runDetectors(\n path: string,\n config: FluentNuxtRuntimeConfig,\n customDetectors?: Map<string, LocaleDetectorFn>,\n hookFn?: (ctx: LocaleDetectContext) => void | Promise<void>,\n): Promise<string> {\n let resolved: string | null = null\n let stopped = false\n\n const ctx: LocaleDetectContext = {\n path,\n locales: config.locales,\n defaultLocale: config.defaultLocale,\n strategy: config.strategy,\n ...(config.detectBrowserLanguage ? { detectBrowserLanguage: config.detectBrowserLanguage } : {}),\n detectedLocale: null,\n setLocale(locale: string) {\n if (config.locales.includes(locale)) {\n resolved = locale\n ctx.detectedLocale = locale\n stopped = true\n }\n },\n isServer: import.meta.server ?? false,\n }\n\n // 1. Run detectors in order\n for (const name of config.detectOrder) {\n if (stopped) break\n\n const detector = builtinDetectors[name] ?? customDetectors?.get(name)\n if (detector) {\n await detector(ctx)\n }\n }\n\n // 2. Fire the hook — allows overriding or supplementing the detection chain\n if (hookFn && !stopped) {\n await hookFn(ctx)\n }\n\n // 3. Fallback\n return resolved ?? config.detectBrowserLanguage?.fallbackLocale ?? config.defaultLocale\n}\n\nexport { builtinDetectors }\nexport type { LocaleDetectContext, LocaleDetectorFn }\n"],"mappings":"uEAIA,SAAwB,EAAW,EAAgC,CACjE,GAAI,EAAI,WAAa,YAAa,OAClC,GAAM,CAAE,UAAW,EAAA,EAAsB,EAAI,KAAM,EAAI,QAAQ,CAC3D,GACF,EAAI,UAAU,EAAO,CCJzB,SAAwB,EAAa,EAAgC,CACnE,GAAI,CAAC,EAAI,uBAAuB,UAAW,OAC3C,IAAM,EAAY,EAAI,sBAAsB,WAAa,iBACzD,GAAI,CACF,IAAM,GAAA,EAAA,EAAA,WAAmB,EAAU,CAC/B,EAAO,OAAS,EAAI,QAAQ,SAAS,EAAO,MAAM,EACpD,EAAI,UAAU,EAAO,MAAM,MAEvB,GCRV,SAAwB,EAAa,EAAgC,CAC9D,KAAI,SACT,GAAI,CAEF,IAAM,GAAA,EAAA,EAAA,mBAD4B,CAAC,kBAAkB,CAAC,CAC3B,mBAC3B,GAAI,EAAY,CACd,IAAM,EAAU,EAAgB,EAAY,EAAI,QAAQ,CACpD,GACF,EAAI,UAAU,EAAQ,OAGpB,GAKV,SAAS,EAAgB,EAAwB,EAAkC,CACjF,IAAM,EAAY,EACf,MAAM,IAAI,CACV,IAAK,GAAS,CACb,GAAM,CAAC,EAAM,GAAK,EAAK,MAAM,CAAC,MAAM,MAAM,CAC1C,MAAO,CAAE,KAAM,EAAM,MAAM,CAAC,aAAa,CAAE,EAAG,EAAI,WAAW,EAAE,CAAG,EAAG,EACrE,CACD,MAAM,EAAG,IAAM,EAAE,EAAI,EAAE,EAAE,CAE5B,IAAK,GAAM,CAAE,UAAU,EAAW,CAChC,GAAI,EAAQ,SAAS,EAAK,CAAE,OAAO,EACnC,IAAM,EAAS,EAAK,MAAM,IAAI,CAAC,GAC/B,GAAI,EAAQ,SAAS,EAAO,CAAE,OAAO,EAGvC,OAAO,KC/BT,SAAwB,EAAY,EAAgC,CAClE,GAAI,CAEF,IAAM,GAAA,EAAA,EAAA,WADkB,CACE,MAAM,OAC5B,GAAe,EAAI,QAAQ,SAAS,EAAY,EAClD,EAAI,UAAU,EAAY,MAEtB,GCJV,IAAM,EAAqD,CACzD,KAAM,EACN,OAAQ,EACR,OAAQ,EACR,MAAO,EACR,CAOD,eAAsB,EACpB,EACA,EACA,EACA,EACiB,CACjB,IAAI,EAA0B,KAC1B,EAAU,GAER,EAA2B,CAC/B,OACA,QAAS,EAAO,QAChB,cAAe,EAAO,cACtB,SAAU,EAAO,SACjB,GAAI,EAAO,sBAAwB,CAAE,sBAAuB,EAAO,sBAAuB,CAAG,EAAE,CAC/F,eAAgB,KAChB,UAAU,EAAgB,CACpB,EAAO,QAAQ,SAAS,EAAO,GACjC,EAAW,EACX,EAAI,eAAiB,EACrB,EAAU,KAGd,SAAA,EAAA,CAAsB,QAAU,GACjC,CAGD,IAAK,IAAM,KAAQ,EAAO,YAAa,CACrC,GAAI,EAAS,MAEb,IAAM,EAAW,EAAiB,IAAS,GAAiB,IAAI,EAAK,CACjE,GACF,MAAM,EAAS,EAAI,CAUvB,OALI,GAAU,CAAC,GACb,MAAM,EAAO,EAAI,CAIZ,GAAY,EAAO,uBAAuB,gBAAkB,EAAO"}
@@ -0,0 +1,84 @@
1
+ import { t as e } from "./path-utils-BcvXLCGi.js";
2
+ import { useCookie as t, useRequestHeaders as n, useRoute as r } from "#imports";
3
+ //#region src/runtime/detectors/path.ts
4
+ function i(t) {
5
+ if (t.strategy === "no_prefix") return;
6
+ let { locale: n } = e(t.path, t.locales);
7
+ n && t.setLocale(n);
8
+ }
9
+ //#endregion
10
+ //#region src/runtime/detectors/cookie.ts
11
+ function a(e) {
12
+ if (!e.detectBrowserLanguage?.useCookie) return;
13
+ let n = e.detectBrowserLanguage.cookieKey ?? "fluenti_locale";
14
+ try {
15
+ let r = t(n);
16
+ r.value && e.locales.includes(r.value) && e.setLocale(r.value);
17
+ } catch {}
18
+ }
19
+ //#endregion
20
+ //#region src/runtime/detectors/header.ts
21
+ function o(e) {
22
+ if (e.isServer) try {
23
+ let t = n(["accept-language"])["accept-language"];
24
+ if (t) {
25
+ let n = s(t, e.locales);
26
+ n && e.setLocale(n);
27
+ }
28
+ } catch {}
29
+ }
30
+ function s(e, t) {
31
+ let n = e.split(",").map((e) => {
32
+ let [t, n] = e.trim().split(";q=");
33
+ return {
34
+ lang: t.trim().toLowerCase(),
35
+ q: n ? parseFloat(n) : 1
36
+ };
37
+ }).sort((e, t) => t.q - e.q);
38
+ for (let { lang: e } of n) {
39
+ if (t.includes(e)) return e;
40
+ let n = e.split("-")[0];
41
+ if (t.includes(n)) return n;
42
+ }
43
+ return null;
44
+ }
45
+ //#endregion
46
+ //#region src/runtime/detectors/query.ts
47
+ function c(e) {
48
+ try {
49
+ let t = r().query.locale;
50
+ t && e.locales.includes(t) && e.setLocale(t);
51
+ } catch {}
52
+ }
53
+ //#endregion
54
+ //#region src/runtime/detectors/index.ts
55
+ var l = {
56
+ path: i,
57
+ cookie: a,
58
+ header: o,
59
+ query: c
60
+ };
61
+ async function u(e, t, n, r) {
62
+ let i = null, a = !1, o = {
63
+ path: e,
64
+ locales: t.locales,
65
+ defaultLocale: t.defaultLocale,
66
+ strategy: t.strategy,
67
+ ...t.detectBrowserLanguage ? { detectBrowserLanguage: t.detectBrowserLanguage } : {},
68
+ detectedLocale: null,
69
+ setLocale(e) {
70
+ t.locales.includes(e) && (i = e, o.detectedLocale = e, a = !0);
71
+ },
72
+ isServer: import.meta.server ?? !1
73
+ };
74
+ for (let e of t.detectOrder) {
75
+ if (a) break;
76
+ let t = l[e] ?? n?.get(e);
77
+ t && await t(o);
78
+ }
79
+ return r && !a && await r(o), i ?? t.detectBrowserLanguage?.fallbackLocale ?? t.defaultLocale;
80
+ }
81
+ //#endregion
82
+ export { u as t };
83
+
84
+ //# sourceMappingURL=detectors-DhkfHBHr.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"detectors-DhkfHBHr.js","names":[],"sources":["../src/runtime/detectors/path.ts","../src/runtime/detectors/cookie.ts","../src/runtime/detectors/header.ts","../src/runtime/detectors/query.ts","../src/runtime/detectors/index.ts"],"sourcesContent":["import type { LocaleDetectContext } from '../../types'\nimport { extractLocaleFromPath } from '../path-utils'\n\n/** Detect locale from URL path prefix (e.g. /ja/about → 'ja') */\nexport default function detectPath(ctx: LocaleDetectContext): void {\n if (ctx.strategy === 'no_prefix') return\n const { locale } = extractLocaleFromPath(ctx.path, ctx.locales)\n if (locale) {\n ctx.setLocale(locale)\n }\n}\n","import { useCookie } from '#imports'\nimport type { LocaleDetectContext } from '../../types'\n\n/** Detect locale from cookie value */\nexport default function detectCookie(ctx: LocaleDetectContext): void {\n if (!ctx.detectBrowserLanguage?.useCookie) return\n const cookieKey = ctx.detectBrowserLanguage.cookieKey ?? 'fluenti_locale'\n try {\n const cookie = useCookie(cookieKey)\n if (cookie.value && ctx.locales.includes(cookie.value)) {\n ctx.setLocale(cookie.value)\n }\n } catch {\n // useCookie may fail outside Nuxt context\n }\n}\n","import { useRequestHeaders } from '#imports'\nimport type { LocaleDetectContext } from '../../types'\n\n/** Detect locale from Accept-Language header (SSR only) */\nexport default function detectHeader(ctx: LocaleDetectContext): void {\n if (!ctx.isServer) return\n try {\n const headers = useRequestHeaders(['accept-language'])\n const acceptLang = headers['accept-language']\n if (acceptLang) {\n const matched = negotiateLocale(acceptLang, ctx.locales)\n if (matched) {\n ctx.setLocale(matched)\n }\n }\n } catch {\n // May fail if not in a request context\n }\n}\n\nfunction negotiateLocale(acceptLanguage: string, locales: string[]): string | null {\n const preferred = acceptLanguage\n .split(',')\n .map((part) => {\n const [lang, q] = part.trim().split(';q=')\n return { lang: lang!.trim().toLowerCase(), q: q ? parseFloat(q) : 1 }\n })\n .sort((a, b) => b.q - a.q)\n\n for (const { lang } of preferred) {\n if (locales.includes(lang)) return lang\n const prefix = lang.split('-')[0]!\n if (locales.includes(prefix)) return prefix\n }\n\n return null\n}\n","import { useRoute } from '#imports'\nimport type { LocaleDetectContext } from '../../types'\n\n/** Detect locale from query parameter (e.g. ?locale=ja) */\nexport default function detectQuery(ctx: LocaleDetectContext): void {\n try {\n const route = useRoute()\n const queryLocale = route.query['locale'] as string | undefined\n if (queryLocale && ctx.locales.includes(queryLocale)) {\n ctx.setLocale(queryLocale)\n }\n } catch {\n // May fail outside Nuxt context\n }\n}\n","import type { LocaleDetectContext, LocaleDetectorFn, FluentNuxtRuntimeConfig } from '../../types'\nimport detectPath from './path'\nimport detectCookie from './cookie'\nimport detectHeader from './header'\nimport detectQuery from './query'\n\n/** Map of built-in detector names to their implementations */\nconst builtinDetectors: Record<string, LocaleDetectorFn> = {\n path: detectPath,\n cookie: detectCookie,\n header: detectHeader,\n query: detectQuery,\n}\n\n/**\n * Run the detection chain: iterate through detectOrder, then fire the hook.\n *\n * Returns the detected locale or the defaultLocale as fallback.\n */\nexport async function runDetectors(\n path: string,\n config: FluentNuxtRuntimeConfig,\n customDetectors?: Map<string, LocaleDetectorFn>,\n hookFn?: (ctx: LocaleDetectContext) => void | Promise<void>,\n): Promise<string> {\n let resolved: string | null = null\n let stopped = false\n\n const ctx: LocaleDetectContext = {\n path,\n locales: config.locales,\n defaultLocale: config.defaultLocale,\n strategy: config.strategy,\n ...(config.detectBrowserLanguage ? { detectBrowserLanguage: config.detectBrowserLanguage } : {}),\n detectedLocale: null,\n setLocale(locale: string) {\n if (config.locales.includes(locale)) {\n resolved = locale\n ctx.detectedLocale = locale\n stopped = true\n }\n },\n isServer: import.meta.server ?? false,\n }\n\n // 1. Run detectors in order\n for (const name of config.detectOrder) {\n if (stopped) break\n\n const detector = builtinDetectors[name] ?? customDetectors?.get(name)\n if (detector) {\n await detector(ctx)\n }\n }\n\n // 2. Fire the hook — allows overriding or supplementing the detection chain\n if (hookFn && !stopped) {\n await hookFn(ctx)\n }\n\n // 3. Fallback\n return resolved ?? config.detectBrowserLanguage?.fallbackLocale ?? config.defaultLocale\n}\n\nexport { builtinDetectors }\nexport type { LocaleDetectContext, LocaleDetectorFn }\n"],"mappings":";;;AAIA,SAAwB,EAAW,GAAgC;AACjE,KAAI,EAAI,aAAa,YAAa;CAClC,IAAM,EAAE,cAAW,EAAsB,EAAI,MAAM,EAAI,QAAQ;AAC/D,CAAI,KACF,EAAI,UAAU,EAAO;;;;ACJzB,SAAwB,EAAa,GAAgC;AACnE,KAAI,CAAC,EAAI,uBAAuB,UAAW;CAC3C,IAAM,IAAY,EAAI,sBAAsB,aAAa;AACzD,KAAI;EACF,IAAM,IAAS,EAAU,EAAU;AACnC,EAAI,EAAO,SAAS,EAAI,QAAQ,SAAS,EAAO,MAAM,IACpD,EAAI,UAAU,EAAO,MAAM;SAEvB;;;;ACRV,SAAwB,EAAa,GAAgC;AAC9D,OAAI,SACT,KAAI;EAEF,IAAM,IADU,EAAkB,CAAC,kBAAkB,CAAC,CAC3B;AAC3B,MAAI,GAAY;GACd,IAAM,IAAU,EAAgB,GAAY,EAAI,QAAQ;AACxD,GAAI,KACF,EAAI,UAAU,EAAQ;;SAGpB;;AAKV,SAAS,EAAgB,GAAwB,GAAkC;CACjF,IAAM,IAAY,EACf,MAAM,IAAI,CACV,KAAK,MAAS;EACb,IAAM,CAAC,GAAM,KAAK,EAAK,MAAM,CAAC,MAAM,MAAM;AAC1C,SAAO;GAAE,MAAM,EAAM,MAAM,CAAC,aAAa;GAAE,GAAG,IAAI,WAAW,EAAE,GAAG;GAAG;GACrE,CACD,MAAM,GAAG,MAAM,EAAE,IAAI,EAAE,EAAE;AAE5B,MAAK,IAAM,EAAE,aAAU,GAAW;AAChC,MAAI,EAAQ,SAAS,EAAK,CAAE,QAAO;EACnC,IAAM,IAAS,EAAK,MAAM,IAAI,CAAC;AAC/B,MAAI,EAAQ,SAAS,EAAO,CAAE,QAAO;;AAGvC,QAAO;;;;AC/BT,SAAwB,EAAY,GAAgC;AAClE,KAAI;EAEF,IAAM,IADQ,GAAU,CACE,MAAM;AAChC,EAAI,KAAe,EAAI,QAAQ,SAAS,EAAY,IAClD,EAAI,UAAU,EAAY;SAEtB;;;;ACJV,IAAM,IAAqD;CACzD,MAAM;CACN,QAAQ;CACR,QAAQ;CACR,OAAO;CACR;AAOD,eAAsB,EACpB,GACA,GACA,GACA,GACiB;CACjB,IAAI,IAA0B,MAC1B,IAAU,IAER,IAA2B;EAC/B;EACA,SAAS,EAAO;EAChB,eAAe,EAAO;EACtB,UAAU,EAAO;EACjB,GAAI,EAAO,wBAAwB,EAAE,uBAAuB,EAAO,uBAAuB,GAAG,EAAE;EAC/F,gBAAgB;EAChB,UAAU,GAAgB;AACxB,GAAI,EAAO,QAAQ,SAAS,EAAO,KACjC,IAAW,GACX,EAAI,iBAAiB,GACrB,IAAU;;EAGd,UAAU,OAAO,KAAK,UAAU;EACjC;AAGD,MAAK,IAAM,KAAQ,EAAO,aAAa;AACrC,MAAI,EAAS;EAEb,IAAM,IAAW,EAAiB,MAAS,GAAiB,IAAI,EAAK;AACrE,EAAI,KACF,MAAM,EAAS,EAAI;;AAUvB,QALI,KAAU,CAAC,KACb,MAAM,EAAO,EAAI,EAIZ,KAAY,EAAO,uBAAuB,kBAAkB,EAAO"}
@@ -0,0 +1,2 @@
1
+ const e=require(`./path-utils-BQIsp_or.cjs`);function t(t,n,r,i){let a={htmlAttrs:{lang:t},link:[],meta:[]};if(i?.addSeoAttributes){let o=i.baseUrl??``;for(let t of r.locales){let i=e.r(n,t,r.locales,r.defaultLocale,r.strategy);a.link.push({rel:`alternate`,hreflang:t,href:`${o}${i}`})}let s=e.r(n,r.defaultLocale,r.locales,r.defaultLocale,r.strategy);a.link.push({rel:`alternate`,hreflang:`x-default`,href:`${o}${s}`}),a.meta.push({property:`og:locale`,content:t});for(let e of r.locales)e!==t&&a.meta.push({property:`og:locale:alternate`,content:e})}return a}Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return t}});
2
+ //# sourceMappingURL=locale-head-BfsESdd7.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-head-BfsESdd7.cjs","names":[],"sources":["../src/runtime/locale-head.ts"],"sourcesContent":["import { switchLocalePath } from './path-utils'\nimport type { FluentNuxtRuntimeConfig } from '../types'\n\n/** Head metadata for locale SEO */\nexport interface LocaleHeadMeta {\n htmlAttrs: { lang: string }\n link: Array<{ rel: string; hreflang: string; href: string }>\n meta: Array<{ property: string; content: string }>\n}\n\nexport interface LocaleHeadOptions {\n /** Add hreflang and og:locale SEO attributes */\n addSeoAttributes?: boolean\n /** Base URL for absolute hreflang links (e.g. 'https://example.com') */\n baseUrl?: string\n}\n\n/**\n * Pure function that builds locale-aware HTML head metadata.\n *\n * This is the framework-agnostic core logic. For the Nuxt composable,\n * use `useLocaleHead()` from `composables.ts` instead.\n */\nexport function buildLocaleHead(\n locale: string,\n currentPath: string,\n config: FluentNuxtRuntimeConfig,\n options?: LocaleHeadOptions,\n): LocaleHeadMeta {\n const head: LocaleHeadMeta = {\n htmlAttrs: { lang: locale },\n link: [],\n meta: [],\n }\n\n if (options?.addSeoAttributes) {\n const baseUrl = options.baseUrl ?? ''\n\n // hreflang alternate links for each locale\n for (const loc of config.locales) {\n const path = switchLocalePath(\n currentPath,\n loc,\n config.locales,\n config.defaultLocale,\n config.strategy,\n )\n head.link.push({\n rel: 'alternate',\n hreflang: loc,\n href: `${baseUrl}${path}`,\n })\n }\n\n // x-default hreflang\n const defaultPath = switchLocalePath(\n currentPath,\n config.defaultLocale,\n config.locales,\n config.defaultLocale,\n config.strategy,\n )\n head.link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: `${baseUrl}${defaultPath}`,\n })\n\n // og:locale\n head.meta.push({ property: 'og:locale', content: locale })\n\n // og:locale:alternate for other locales\n for (const loc of config.locales) {\n if (loc !== locale) {\n head.meta.push({ property: 'og:locale:alternate', content: loc })\n }\n }\n }\n\n return head\n}\n"],"mappings":"6CAuBA,SAAgB,EACd,EACA,EACA,EACA,EACgB,CAChB,IAAM,EAAuB,CAC3B,UAAW,CAAE,KAAM,EAAQ,CAC3B,KAAM,EAAE,CACR,KAAM,EAAE,CACT,CAED,GAAI,GAAS,iBAAkB,CAC7B,IAAM,EAAU,EAAQ,SAAW,GAGnC,IAAK,IAAM,KAAO,EAAO,QAAS,CAChC,IAAM,EAAO,EAAA,EACX,EACA,EACA,EAAO,QACP,EAAO,cACP,EAAO,SACR,CACD,EAAK,KAAK,KAAK,CACb,IAAK,YACL,SAAU,EACV,KAAM,GAAG,IAAU,IACpB,CAAC,CAIJ,IAAM,EAAc,EAAA,EAClB,EACA,EAAO,cACP,EAAO,QACP,EAAO,cACP,EAAO,SACR,CACD,EAAK,KAAK,KAAK,CACb,IAAK,YACL,SAAU,YACV,KAAM,GAAG,IAAU,IACpB,CAAC,CAGF,EAAK,KAAK,KAAK,CAAE,SAAU,YAAa,QAAS,EAAQ,CAAC,CAG1D,IAAK,IAAM,KAAO,EAAO,QACnB,IAAQ,GACV,EAAK,KAAK,KAAK,CAAE,SAAU,sBAAuB,QAAS,EAAK,CAAC,CAKvE,OAAO"}
@@ -0,0 +1,38 @@
1
+ import { r as e } from "./path-utils-BcvXLCGi.js";
2
+ //#region src/runtime/locale-head.ts
3
+ function t(t, n, r, i) {
4
+ let a = {
5
+ htmlAttrs: { lang: t },
6
+ link: [],
7
+ meta: []
8
+ };
9
+ if (i?.addSeoAttributes) {
10
+ let o = i.baseUrl ?? "";
11
+ for (let t of r.locales) {
12
+ let i = e(n, t, r.locales, r.defaultLocale, r.strategy);
13
+ a.link.push({
14
+ rel: "alternate",
15
+ hreflang: t,
16
+ href: `${o}${i}`
17
+ });
18
+ }
19
+ let s = e(n, r.defaultLocale, r.locales, r.defaultLocale, r.strategy);
20
+ a.link.push({
21
+ rel: "alternate",
22
+ hreflang: "x-default",
23
+ href: `${o}${s}`
24
+ }), a.meta.push({
25
+ property: "og:locale",
26
+ content: t
27
+ });
28
+ for (let e of r.locales) e !== t && a.meta.push({
29
+ property: "og:locale:alternate",
30
+ content: e
31
+ });
32
+ }
33
+ return a;
34
+ }
35
+ //#endregion
36
+ export { t };
37
+
38
+ //# sourceMappingURL=locale-head-CqUlvy3O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-head-CqUlvy3O.js","names":[],"sources":["../src/runtime/locale-head.ts"],"sourcesContent":["import { switchLocalePath } from './path-utils'\nimport type { FluentNuxtRuntimeConfig } from '../types'\n\n/** Head metadata for locale SEO */\nexport interface LocaleHeadMeta {\n htmlAttrs: { lang: string }\n link: Array<{ rel: string; hreflang: string; href: string }>\n meta: Array<{ property: string; content: string }>\n}\n\nexport interface LocaleHeadOptions {\n /** Add hreflang and og:locale SEO attributes */\n addSeoAttributes?: boolean\n /** Base URL for absolute hreflang links (e.g. 'https://example.com') */\n baseUrl?: string\n}\n\n/**\n * Pure function that builds locale-aware HTML head metadata.\n *\n * This is the framework-agnostic core logic. For the Nuxt composable,\n * use `useLocaleHead()` from `composables.ts` instead.\n */\nexport function buildLocaleHead(\n locale: string,\n currentPath: string,\n config: FluentNuxtRuntimeConfig,\n options?: LocaleHeadOptions,\n): LocaleHeadMeta {\n const head: LocaleHeadMeta = {\n htmlAttrs: { lang: locale },\n link: [],\n meta: [],\n }\n\n if (options?.addSeoAttributes) {\n const baseUrl = options.baseUrl ?? ''\n\n // hreflang alternate links for each locale\n for (const loc of config.locales) {\n const path = switchLocalePath(\n currentPath,\n loc,\n config.locales,\n config.defaultLocale,\n config.strategy,\n )\n head.link.push({\n rel: 'alternate',\n hreflang: loc,\n href: `${baseUrl}${path}`,\n })\n }\n\n // x-default hreflang\n const defaultPath = switchLocalePath(\n currentPath,\n config.defaultLocale,\n config.locales,\n config.defaultLocale,\n config.strategy,\n )\n head.link.push({\n rel: 'alternate',\n hreflang: 'x-default',\n href: `${baseUrl}${defaultPath}`,\n })\n\n // og:locale\n head.meta.push({ property: 'og:locale', content: locale })\n\n // og:locale:alternate for other locales\n for (const loc of config.locales) {\n if (loc !== locale) {\n head.meta.push({ property: 'og:locale:alternate', content: loc })\n }\n }\n }\n\n return head\n}\n"],"mappings":";;AAuBA,SAAgB,EACd,GACA,GACA,GACA,GACgB;CAChB,IAAM,IAAuB;EAC3B,WAAW,EAAE,MAAM,GAAQ;EAC3B,MAAM,EAAE;EACR,MAAM,EAAE;EACT;AAED,KAAI,GAAS,kBAAkB;EAC7B,IAAM,IAAU,EAAQ,WAAW;AAGnC,OAAK,IAAM,KAAO,EAAO,SAAS;GAChC,IAAM,IAAO,EACX,GACA,GACA,EAAO,SACP,EAAO,eACP,EAAO,SACR;AACD,KAAK,KAAK,KAAK;IACb,KAAK;IACL,UAAU;IACV,MAAM,GAAG,IAAU;IACpB,CAAC;;EAIJ,IAAM,IAAc,EAClB,GACA,EAAO,eACP,EAAO,SACP,EAAO,eACP,EAAO,SACR;AAQD,EAPA,EAAK,KAAK,KAAK;GACb,KAAK;GACL,UAAU;GACV,MAAM,GAAG,IAAU;GACpB,CAAC,EAGF,EAAK,KAAK,KAAK;GAAE,UAAU;GAAa,SAAS;GAAQ,CAAC;AAG1D,OAAK,IAAM,KAAO,EAAO,QACvB,CAAI,MAAQ,KACV,EAAK,KAAK,KAAK;GAAE,UAAU;GAAuB,SAAS;GAAK,CAAC;;AAKvE,QAAO"}
@@ -0,0 +1,2 @@
1
+ Object.defineProperties(exports,{__esModule:{value:!0},[Symbol.toStringTag]:{value:`Module`}});const e=require(`./page-extend-OvV-ZhFB.cjs`),t=require(`./path-utils-BQIsp_or.cjs`),n=require(`./locale-head-BfsESdd7.cjs`);let r=require(`@nuxt/kit`),i=require(`vue`);function a(e,n){return(r,i)=>t.n(r,i??e.value,n.defaultLocale,n.strategy)}function o(e,n){return r=>t.r(e.value,r,n.locales,n.defaultLocale,n.strategy)}function s(e,t,r,a){return(0,i.computed)(()=>n.t(e.value,t.value,r,a))}var c=`@fluenti/nuxt`,l=`fluenti`,u=(0,r.defineNuxtModule)({meta:{name:c,configKey:l,compatibility:{nuxt:`>=3.0.0`}},defaults:{locales:[],defaultLocale:`en`,strategy:`prefix_except_default`},setup(t,n){let{resolve:i}=(0,r.createResolver)({}.url),a=t.detectOrder??[`path`,`cookie`,`header`];if(n.options.runtimeConfig.public.fluenti={locales:t.locales,defaultLocale:t.defaultLocale,strategy:t.strategy??`prefix_except_default`,detectBrowserLanguage:t.detectBrowserLanguage,detectOrder:a},t.autoVitePlugin!==!1)try{let e=require(`@fluenti/vite-plugin`),t=e.default??e;n.options.vite=n.options.vite||{},n.options.vite.plugins=n.options.vite.plugins||[],n.options.vite.plugins.push(t({framework:`vue`}))}catch{}(0,r.addPlugin)({src:i(`./runtime/plugin`),mode:`all`}),t.strategy!==`no_prefix`&&n.hook(`pages:extend`,n=>{e.t(n,{locales:t.locales,defaultLocale:t.defaultLocale,strategy:t.strategy??`prefix_except_default`})}),t.strategy===`prefix`&&(0,r.addRouteMiddleware)({name:`fluenti-locale-redirect`,path:i(`./runtime/middleware/locale-redirect`),global:!0}),(0,r.addImports)([{name:`useLocalePath`,from:i(`./runtime/composables`)},{name:`useSwitchLocalePath`,from:i(`./runtime/composables`)},{name:`useLocaleHead`,from:i(`./runtime/composables`)},{name:`useI18n`,from:`@fluenti/vue`}]),(0,r.addComponent)({name:`${t.componentPrefix??``}NuxtLinkLocale`,filePath:i(`./runtime/components/NuxtLinkLocale`)});let o=t.strategy??`prefix_except_default`;if(o!==`no_prefix`){let e=n.options,r=e.nitro??={},i=r.prerender??={};if(i.crawlLinks=i.crawlLinks??!0,o===`prefix`&&(i.routes=(i.routes??[`/`]).map(e=>e===`/`?`/${t.defaultLocale}`:e)),t.isr?.enabled){let n=e.routeRules??={},r=t.isr.ttl??3600;for(let e of t.locales)e===t.defaultLocale&&o===`prefix_except_default`?n[`/**`]={...n[`/**`],isr:r}:n[`/${e}/**`]={...n[`/${e}/**`],isr:r}}}}});exports.CONFIG_KEY=l,exports.MODULE_NAME=c,exports.buildLocaleHead=n.t,exports.default=u,exports.extendPages=e.t,exports.extractLocaleFromPath=t.t,exports.localePath=t.n,exports.switchLocalePath=t.r,exports.useLocaleHead=s,exports.useLocalePath=a,exports.useSwitchLocalePath=o;
2
+ //# sourceMappingURL=module.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"module.cjs","names":[],"sources":["../src/runtime/standalone-composables.ts","../src/module.ts"],"sourcesContent":["import { computed } from 'vue'\nimport type { ComputedRef, Ref } from 'vue'\nimport { localePath, switchLocalePath } from './path-utils'\nimport type { FluentNuxtRuntimeConfig } from '../types'\nimport type { LocaleHeadMeta, LocaleHeadOptions } from './locale-head'\nimport { buildLocaleHead } from './locale-head'\n\n/**\n * Standalone composable for locale-prefixed paths (no Nuxt dependency).\n * Accepts explicit locale ref and config instead of reading from Nuxt context.\n */\nexport function useLocalePath(\n locale: Ref<string>,\n config: FluentNuxtRuntimeConfig,\n): (path: string, targetLocale?: string) => string {\n return (path: string, targetLocale?: string) => {\n return localePath(\n path,\n targetLocale ?? locale.value,\n config.defaultLocale,\n config.strategy,\n )\n }\n}\n\n/**\n * Standalone composable for switch-locale paths (no Nuxt dependency).\n * Accepts explicit current path ref and config.\n */\nexport function useSwitchLocalePath(\n currentPath: Ref<string>,\n config: FluentNuxtRuntimeConfig,\n): (locale: string) => string {\n return (newLocale: string) => {\n return switchLocalePath(\n currentPath.value,\n newLocale,\n config.locales,\n config.defaultLocale,\n config.strategy,\n )\n }\n}\n\n/**\n * Standalone composable for locale-aware head metadata (no Nuxt dependency).\n * Accepts explicit locale, path, and config refs.\n */\nexport function useLocaleHead(\n locale: Ref<string>,\n currentPath: Ref<string>,\n config: FluentNuxtRuntimeConfig,\n options?: LocaleHeadOptions,\n): ComputedRef<LocaleHeadMeta> {\n return computed(() => {\n return buildLocaleHead(locale.value, currentPath.value, config, options)\n })\n}\n","import { defineNuxtModule, addPlugin, addImports, addComponent, addRouteMiddleware, createResolver } from '@nuxt/kit'\nimport type { FluentNuxtOptions } from './types'\nimport { extendPages } from './runtime/page-extend'\n\nexport type { FluentNuxtOptions, Strategy, FluentNuxtRuntimeConfig, DetectBrowserLanguageOptions, LocaleDetectContext, LocaleDetectorFn, BuiltinDetector, ISROptions } from './types'\nexport { localePath, extractLocaleFromPath, switchLocalePath } from './runtime/path-utils'\nexport { extendPages } from './runtime/page-extend'\nexport type { PageRoute } from './runtime/page-extend'\nexport { buildLocaleHead } from './runtime/locale-head'\nexport type { LocaleHeadMeta, LocaleHeadOptions } from './runtime/locale-head'\nexport { useLocalePath, useSwitchLocalePath, useLocaleHead } from './runtime/standalone-composables'\n\nexport const MODULE_NAME = '@fluenti/nuxt'\nexport const CONFIG_KEY = 'fluenti'\n\nexport default defineNuxtModule<FluentNuxtOptions>({\n meta: {\n name: MODULE_NAME,\n configKey: CONFIG_KEY,\n compatibility: { nuxt: '>=3.0.0' },\n },\n defaults: {\n locales: [],\n defaultLocale: 'en',\n strategy: 'prefix_except_default',\n },\n setup(options, nuxt) {\n const { resolve } = createResolver(import.meta.url)\n\n // --- Inject runtime config ---\n const detectOrder = options.detectOrder ?? ['path', 'cookie', 'header']\n nuxt.options.runtimeConfig.public['fluenti'] = {\n locales: options.locales,\n defaultLocale: options.defaultLocale,\n strategy: options.strategy ?? 'prefix_except_default',\n detectBrowserLanguage: options.detectBrowserLanguage,\n detectOrder,\n }\n\n // --- Auto-register @fluenti/vite-plugin ---\n if (options.autoVitePlugin !== false) {\n try {\n const vitePlugin = require('@fluenti/vite-plugin')\n const plugin = vitePlugin.default ?? vitePlugin\n nuxt.options.vite = nuxt.options.vite || {}\n nuxt.options.vite.plugins = nuxt.options.vite.plugins || []\n ;(nuxt.options.vite.plugins as unknown[]).push(\n plugin({ framework: 'vue' }),\n )\n } catch {\n // @fluenti/vite-plugin is an optional peer dependency\n }\n }\n\n // --- Register runtime plugin ---\n addPlugin({\n src: resolve('./runtime/plugin'),\n mode: 'all',\n })\n\n // --- Extend routes with locale prefixes ---\n if (options.strategy !== 'no_prefix') {\n nuxt.hook('pages:extend', (pages) => {\n extendPages(pages, {\n locales: options.locales,\n defaultLocale: options.defaultLocale,\n strategy: options.strategy ?? 'prefix_except_default',\n })\n })\n }\n\n // --- Register locale redirect middleware ---\n if (options.strategy === 'prefix') {\n addRouteMiddleware({\n name: 'fluenti-locale-redirect',\n path: resolve('./runtime/middleware/locale-redirect'),\n global: true,\n })\n }\n\n // --- Auto-import composables ---\n addImports([\n { name: 'useLocalePath', from: resolve('./runtime/composables') },\n { name: 'useSwitchLocalePath', from: resolve('./runtime/composables') },\n { name: 'useLocaleHead', from: resolve('./runtime/composables') },\n { name: 'useI18n', from: '@fluenti/vue' },\n ])\n\n // --- Register NuxtLinkLocale component ---\n const prefix = options.componentPrefix ?? ''\n addComponent({\n name: `${prefix}NuxtLinkLocale`,\n filePath: resolve('./runtime/components/NuxtLinkLocale'),\n })\n\n // --- SSG / ISR: configure nitro prerender and route rules ---\n const strategy = options.strategy ?? 'prefix_except_default'\n if (strategy !== 'no_prefix') {\n const nuxtOpts = nuxt.options as unknown as Record<string, unknown>\n\n // Enable link crawling so locale-prefixed routes are discovered during prerender\n const nitroOpts = (nuxtOpts['nitro'] ?? (nuxtOpts['nitro'] = {})) as Record<string, unknown>\n const prerender = (nitroOpts['prerender'] ?? (nitroOpts['prerender'] = {})) as Record<string, unknown>\n prerender['crawlLinks'] = prerender['crawlLinks'] ?? true\n\n // For 'prefix' strategy, / has no matching route (all routes are\n // locale-prefixed). Replace the default / initial route with\n // /<defaultLocale> so the prerenderer starts from a valid route.\n if (strategy === 'prefix') {\n const routes = (prerender['routes'] ?? ['/']) as string[]\n prerender['routes'] = routes.map((r) =>\n r === '/' ? `/${options.defaultLocale}` : r,\n )\n }\n\n // ISR: generate routeRules for each locale pattern\n if (options.isr?.enabled) {\n const routeRules = (nuxtOpts['routeRules'] ?? (nuxtOpts['routeRules'] = {})) as Record<string, Record<string, unknown>>\n const ttl = options.isr.ttl ?? 3600\n for (const locale of options.locales) {\n if (locale === options.defaultLocale && strategy === 'prefix_except_default') {\n routeRules['/**'] = { ...routeRules['/**'], isr: ttl }\n } else {\n routeRules[`/${locale}/**`] = { ...routeRules[`/${locale}/**`], isr: ttl }\n }\n }\n }\n }\n },\n})\n"],"mappings":"wQAWA,SAAgB,EACd,EACA,EACiD,CACjD,OAAQ,EAAc,IACb,EAAA,EACL,EACA,GAAgB,EAAO,MACvB,EAAO,cACP,EAAO,SACR,CAQL,SAAgB,EACd,EACA,EAC4B,CAC5B,MAAQ,IACC,EAAA,EACL,EAAY,MACZ,EACA,EAAO,QACP,EAAO,cACP,EAAO,SACR,CAQL,SAAgB,EACd,EACA,EACA,EACA,EAC6B,CAC7B,OAAA,EAAA,EAAA,cACS,EAAA,EAAgB,EAAO,MAAO,EAAY,MAAO,EAAQ,EAAQ,CACxE,CC5CJ,IAAa,EAAc,gBACd,EAAa,UAE1B,GAAA,EAAA,EAAA,kBAAmD,CACjD,KAAM,CACJ,KAAM,EACN,UAAW,EACX,cAAe,CAAE,KAAM,UAAW,CACnC,CACD,SAAU,CACR,QAAS,EAAE,CACX,cAAe,KACf,SAAU,wBACX,CACD,MAAM,EAAS,EAAM,CACnB,GAAM,CAAE,YAAA,EAAA,EAAA,gBAAA,EAAA,CAAuC,IAAI,CAG7C,EAAc,EAAQ,aAAe,CAAC,OAAQ,SAAU,SAAS,CAUvE,GATA,EAAK,QAAQ,cAAc,OAAO,QAAa,CAC7C,QAAS,EAAQ,QACjB,cAAe,EAAQ,cACvB,SAAU,EAAQ,UAAY,wBAC9B,sBAAuB,EAAQ,sBAC/B,cACD,CAGG,EAAQ,iBAAmB,GAC7B,GAAI,CACF,IAAM,EAAa,QAAQ,uBAAuB,CAC5C,EAAS,EAAW,SAAW,EACrC,EAAK,QAAQ,KAAO,EAAK,QAAQ,MAAQ,EAAE,CAC3C,EAAK,QAAQ,KAAK,QAAU,EAAK,QAAQ,KAAK,SAAW,EAAE,CACzD,EAAK,QAAQ,KAAK,QAAsB,KACxC,EAAO,CAAE,UAAW,MAAO,CAAC,CAC7B,MACK,GAMV,EAAA,EAAA,WAAU,CACR,IAAK,EAAQ,mBAAmB,CAChC,KAAM,MACP,CAAC,CAGE,EAAQ,WAAa,aACvB,EAAK,KAAK,eAAiB,GAAU,CACnC,EAAA,EAAY,EAAO,CACjB,QAAS,EAAQ,QACjB,cAAe,EAAQ,cACvB,SAAU,EAAQ,UAAY,wBAC/B,CAAC,EACF,CAIA,EAAQ,WAAa,WACvB,EAAA,EAAA,oBAAmB,CACjB,KAAM,0BACN,KAAM,EAAQ,uCAAuC,CACrD,OAAQ,GACT,CAAC,EAIJ,EAAA,EAAA,YAAW,CACT,CAAE,KAAM,gBAAiB,KAAM,EAAQ,wBAAwB,CAAE,CACjE,CAAE,KAAM,sBAAuB,KAAM,EAAQ,wBAAwB,CAAE,CACvE,CAAE,KAAM,gBAAiB,KAAM,EAAQ,wBAAwB,CAAE,CACjE,CAAE,KAAM,UAAW,KAAM,eAAgB,CAC1C,CAAC,EAIF,EAAA,EAAA,cAAa,CACX,KAAM,GAFO,EAAQ,iBAAmB,GAExB,gBAChB,SAAU,EAAQ,sCAAsC,CACzD,CAAC,CAGF,IAAM,EAAW,EAAQ,UAAY,wBACrC,GAAI,IAAa,YAAa,CAC5B,IAAM,EAAW,EAAK,QAGhB,EAAa,AAAsB,EAAS,QAAW,EAAE,CACzD,EAAa,AAA2B,EAAU,YAAe,EAAE,CAczE,GAbA,EAAU,WAAgB,EAAU,YAAiB,GAKjD,IAAa,WAEf,EAAU,QADM,EAAU,QAAa,CAAC,IAAI,EACf,IAAK,GAChC,IAAM,IAAM,IAAI,EAAQ,gBAAkB,EAC3C,EAIC,EAAQ,KAAK,QAAS,CACxB,IAAM,EAAc,AAA2B,EAAS,aAAgB,EAAE,CACpE,EAAM,EAAQ,IAAI,KAAO,KAC/B,IAAK,IAAM,KAAU,EAAQ,QACvB,IAAW,EAAQ,eAAiB,IAAa,wBACnD,EAAW,OAAS,CAAE,GAAG,EAAW,OAAQ,IAAK,EAAK,CAEtD,EAAW,IAAI,EAAO,MAAQ,CAAE,GAAG,EAAW,IAAI,EAAO,MAAO,IAAK,EAAK,IAMrF,CAAC"}
package/dist/module.js ADDED
@@ -0,0 +1,104 @@
1
+ import { t as e } from "./page-extend-DJ7wpuVj.js";
2
+ import { n as t, r as n, t as r } from "./path-utils-BcvXLCGi.js";
3
+ import { t as i } from "./locale-head-CqUlvy3O.js";
4
+ import { addComponent as a, addImports as o, addPlugin as s, addRouteMiddleware as c, createResolver as l, defineNuxtModule as u } from "@nuxt/kit";
5
+ import { computed as d } from "vue";
6
+ //#region \0rolldown/runtime.js
7
+ var f = /* @__PURE__ */ ((e) => typeof require < "u" ? require : typeof Proxy < "u" ? new Proxy(e, { get: (e, t) => (typeof require < "u" ? require : e)[t] }) : e)(function(e) {
8
+ if (typeof require < "u") return require.apply(this, arguments);
9
+ throw Error("Calling `require` for \"" + e + "\" in an environment that doesn't expose the `require` function. See https://rolldown.rs/in-depth/bundling-cjs#require-external-modules for more details.");
10
+ });
11
+ //#endregion
12
+ //#region src/runtime/standalone-composables.ts
13
+ function p(e, n) {
14
+ return (r, i) => t(r, i ?? e.value, n.defaultLocale, n.strategy);
15
+ }
16
+ function m(e, t) {
17
+ return (r) => n(e.value, r, t.locales, t.defaultLocale, t.strategy);
18
+ }
19
+ function h(e, t, n, r) {
20
+ return d(() => i(e.value, t.value, n, r));
21
+ }
22
+ //#endregion
23
+ //#region src/module.ts
24
+ var g = "@fluenti/nuxt", _ = "fluenti", v = u({
25
+ meta: {
26
+ name: g,
27
+ configKey: _,
28
+ compatibility: { nuxt: ">=3.0.0" }
29
+ },
30
+ defaults: {
31
+ locales: [],
32
+ defaultLocale: "en",
33
+ strategy: "prefix_except_default"
34
+ },
35
+ setup(t, n) {
36
+ let { resolve: r } = l(import.meta.url), i = t.detectOrder ?? [
37
+ "path",
38
+ "cookie",
39
+ "header"
40
+ ];
41
+ if (n.options.runtimeConfig.public.fluenti = {
42
+ locales: t.locales,
43
+ defaultLocale: t.defaultLocale,
44
+ strategy: t.strategy ?? "prefix_except_default",
45
+ detectBrowserLanguage: t.detectBrowserLanguage,
46
+ detectOrder: i
47
+ }, t.autoVitePlugin !== !1) try {
48
+ let e = f("@fluenti/vite-plugin"), t = e.default ?? e;
49
+ n.options.vite = n.options.vite || {}, n.options.vite.plugins = n.options.vite.plugins || [], n.options.vite.plugins.push(t({ framework: "vue" }));
50
+ } catch {}
51
+ s({
52
+ src: r("./runtime/plugin"),
53
+ mode: "all"
54
+ }), t.strategy !== "no_prefix" && n.hook("pages:extend", (n) => {
55
+ e(n, {
56
+ locales: t.locales,
57
+ defaultLocale: t.defaultLocale,
58
+ strategy: t.strategy ?? "prefix_except_default"
59
+ });
60
+ }), t.strategy === "prefix" && c({
61
+ name: "fluenti-locale-redirect",
62
+ path: r("./runtime/middleware/locale-redirect"),
63
+ global: !0
64
+ }), o([
65
+ {
66
+ name: "useLocalePath",
67
+ from: r("./runtime/composables")
68
+ },
69
+ {
70
+ name: "useSwitchLocalePath",
71
+ from: r("./runtime/composables")
72
+ },
73
+ {
74
+ name: "useLocaleHead",
75
+ from: r("./runtime/composables")
76
+ },
77
+ {
78
+ name: "useI18n",
79
+ from: "@fluenti/vue"
80
+ }
81
+ ]), a({
82
+ name: `${t.componentPrefix ?? ""}NuxtLinkLocale`,
83
+ filePath: r("./runtime/components/NuxtLinkLocale")
84
+ });
85
+ let u = t.strategy ?? "prefix_except_default";
86
+ if (u !== "no_prefix") {
87
+ let e = n.options, r = e.nitro ??= {}, i = r.prerender ??= {};
88
+ if (i.crawlLinks = i.crawlLinks ?? !0, u === "prefix" && (i.routes = (i.routes ?? ["/"]).map((e) => e === "/" ? `/${t.defaultLocale}` : e)), t.isr?.enabled) {
89
+ let n = e.routeRules ??= {}, r = t.isr.ttl ?? 3600;
90
+ for (let e of t.locales) e === t.defaultLocale && u === "prefix_except_default" ? n["/**"] = {
91
+ ...n["/**"],
92
+ isr: r
93
+ } : n[`/${e}/**`] = {
94
+ ...n[`/${e}/**`],
95
+ isr: r
96
+ };
97
+ }
98
+ }
99
+ }
100
+ });
101
+ //#endregion
102
+ export { _ as CONFIG_KEY, g as MODULE_NAME, i as buildLocaleHead, v as default, e as extendPages, r as extractLocaleFromPath, t as localePath, n as switchLocalePath, h as useLocaleHead, p as useLocalePath, m as useSwitchLocalePath };
103
+
104
+ //# sourceMappingURL=module.js.map