@fluenti/solid 0.1.2 → 0.2.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/README.md +3 -3
- package/dist/context.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +7 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/solid-runtime.d.ts +3 -0
- package/dist/solid-runtime.d.ts.map +1 -0
- package/dist/vite-plugin.cjs +114 -0
- package/dist/vite-plugin.cjs.map +1 -0
- package/dist/vite-plugin.d.ts +5 -0
- package/dist/vite-plugin.d.ts.map +1 -0
- package/dist/vite-plugin.js +135 -0
- package/dist/vite-plugin.js.map +1 -0
- package/package.json +20 -3
- package/src/context.ts +5 -4
- package/src/server.ts +8 -0
- package/src/solid-runtime.ts +134 -0
- package/src/vite-plugin.ts +14 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fluenti/solid",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "SolidJS compile-time i18n — Trans/Plural/Select components, I18nProvider, useI18n",
|
|
6
6
|
"homepage": "https://fluenti.dev",
|
|
@@ -43,6 +43,16 @@
|
|
|
43
43
|
"types": "./dist/index.d.ts",
|
|
44
44
|
"default": "./dist/index.cjs"
|
|
45
45
|
}
|
|
46
|
+
},
|
|
47
|
+
"./vite-plugin": {
|
|
48
|
+
"import": {
|
|
49
|
+
"types": "./dist/vite-plugin.d.ts",
|
|
50
|
+
"default": "./dist/vite-plugin.js"
|
|
51
|
+
},
|
|
52
|
+
"require": {
|
|
53
|
+
"types": "./dist/vite-plugin.d.ts",
|
|
54
|
+
"default": "./dist/vite-plugin.cjs"
|
|
55
|
+
}
|
|
46
56
|
}
|
|
47
57
|
},
|
|
48
58
|
"files": [
|
|
@@ -50,10 +60,17 @@
|
|
|
50
60
|
"src"
|
|
51
61
|
],
|
|
52
62
|
"peerDependencies": {
|
|
53
|
-
"solid-js": "^1.8"
|
|
63
|
+
"solid-js": "^1.8",
|
|
64
|
+
"vite": "^5 || ^6 || ^8"
|
|
65
|
+
},
|
|
66
|
+
"peerDependenciesMeta": {
|
|
67
|
+
"vite": {
|
|
68
|
+
"optional": true
|
|
69
|
+
}
|
|
54
70
|
},
|
|
55
71
|
"dependencies": {
|
|
56
|
-
"@fluenti/core": "0.
|
|
72
|
+
"@fluenti/core": "0.2.0",
|
|
73
|
+
"@fluenti/vite-plugin": "0.2.0"
|
|
57
74
|
},
|
|
58
75
|
"devDependencies": {
|
|
59
76
|
"@solidjs/testing-library": "^0.8",
|
package/src/context.ts
CHANGED
|
@@ -223,6 +223,7 @@ export function createI18nContext(config: FluentConfig | I18nConfig): I18nContex
|
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
const loadMessages = (loc: Locale, msgs: Messages): void => {
|
|
226
|
+
// Intentional mutation: messages record is locally scoped to this context closure
|
|
226
227
|
messages[loc] = { ...messages[loc], ...msgs }
|
|
227
228
|
loadedLocalesSet.add(loc)
|
|
228
229
|
setLoadedLocales(new Set(loadedLocalesSet))
|
|
@@ -247,6 +248,7 @@ export function createI18nContext(config: FluentConfig | I18nConfig): I18nContex
|
|
|
247
248
|
setIsLoading(true)
|
|
248
249
|
try {
|
|
249
250
|
const loaded = resolveChunkMessages(await i18nConfig.chunkLoader(newLocale))
|
|
251
|
+
// Intentional mutation: messages record is locally scoped to this context closure
|
|
250
252
|
messages[newLocale] = { ...messages[newLocale], ...loaded }
|
|
251
253
|
loadedLocalesSet.add(newLocale)
|
|
252
254
|
setLoadedLocales(new Set(loadedLocalesSet))
|
|
@@ -264,14 +266,15 @@ export function createI18nContext(config: FluentConfig | I18nConfig): I18nContex
|
|
|
264
266
|
const splitRuntime = getSplitRuntimeModule()
|
|
265
267
|
i18nConfig.chunkLoader(loc).then(async (loaded) => {
|
|
266
268
|
const resolved = resolveChunkMessages(loaded)
|
|
269
|
+
// Intentional mutation: messages record is locally scoped to this context closure
|
|
267
270
|
messages[loc] = { ...messages[loc], ...resolved }
|
|
268
271
|
loadedLocalesSet.add(loc)
|
|
269
272
|
setLoadedLocales(new Set(loadedLocalesSet))
|
|
270
273
|
if (splitRuntime?.__preloadLocale) {
|
|
271
274
|
await splitRuntime.__preloadLocale(loc)
|
|
272
275
|
}
|
|
273
|
-
}).catch(() => {
|
|
274
|
-
|
|
276
|
+
}).catch((e: unknown) => {
|
|
277
|
+
console.warn('[fluenti] preload failed:', loc, e)
|
|
275
278
|
})
|
|
276
279
|
}
|
|
277
280
|
|
|
@@ -314,8 +317,6 @@ export function createI18n(config: FluentConfig | I18nConfig): I18nContext {
|
|
|
314
317
|
'[fluenti] createI18n() detected SSR environment. ' +
|
|
315
318
|
'Use <I18nProvider> for per-request isolation in SSR.',
|
|
316
319
|
)
|
|
317
|
-
// Still set globalCtx as fallback, but document the risk
|
|
318
|
-
globalCtx = ctx
|
|
319
320
|
}
|
|
320
321
|
|
|
321
322
|
return ctx
|
package/src/server.ts
CHANGED
|
@@ -72,6 +72,13 @@ export interface ServerI18n {
|
|
|
72
72
|
* your `resolveLocale` callback, or call `setLocale()` in your
|
|
73
73
|
* entry-server middleware.
|
|
74
74
|
*
|
|
75
|
+
* **⚠️ SSR Concurrency Warning**: This function uses module-level state for locale
|
|
76
|
+
* and cached instance. In concurrent SSR environments (e.g. multiple simultaneous
|
|
77
|
+
* requests), this can cause cross-request locale leakage. For per-request isolation:
|
|
78
|
+
* - Use `getRequestEvent()` in SolidStart to scope locale per request
|
|
79
|
+
* - Or create a separate `createServerI18n()` per request context
|
|
80
|
+
* - Consider using AsyncLocalStorage for true per-request isolation (future)
|
|
81
|
+
*
|
|
75
82
|
* @example
|
|
76
83
|
* ```ts
|
|
77
84
|
* // lib/i18n.server.ts
|
|
@@ -90,6 +97,7 @@ export function createServerI18n(config: ServerI18nConfig): ServerI18n {
|
|
|
90
97
|
|
|
91
98
|
function setLocale(locale: string): void {
|
|
92
99
|
currentLocale = locale
|
|
100
|
+
cachedInstance = null
|
|
93
101
|
}
|
|
94
102
|
|
|
95
103
|
async function loadLocaleMessages(locale: string): Promise<Messages> {
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { resolve } from 'node:path'
|
|
2
|
+
import type { RuntimeGenerator, RuntimeGeneratorOptions } from '@fluenti/vite-plugin'
|
|
3
|
+
|
|
4
|
+
export const solidRuntimeGenerator: RuntimeGenerator = {
|
|
5
|
+
generateRuntime(options: RuntimeGeneratorOptions): string {
|
|
6
|
+
const { catalogDir, locales, sourceLocale, defaultBuildLocale } = options
|
|
7
|
+
const defaultLocale = defaultBuildLocale || sourceLocale
|
|
8
|
+
const absoluteCatalogDir = resolve(process.cwd(), catalogDir)
|
|
9
|
+
const runtimeKey = 'fluenti.runtime.solid'
|
|
10
|
+
const lazyLocales = locales.filter((locale) => locale !== defaultLocale)
|
|
11
|
+
|
|
12
|
+
return `
|
|
13
|
+
import { createSignal } from 'solid-js'
|
|
14
|
+
import { createStore, reconcile } from 'solid-js/store'
|
|
15
|
+
import __defaultMsgs from '${absoluteCatalogDir}/${defaultLocale}.js'
|
|
16
|
+
|
|
17
|
+
const [__catalog, __setCatalog] = createStore({ ...__defaultMsgs })
|
|
18
|
+
const [__currentLocale, __setCurrentLocale] = createSignal('${defaultLocale}')
|
|
19
|
+
const __loadedLocales = new Set(['${defaultLocale}'])
|
|
20
|
+
const [__loading, __setLoading] = createSignal(false)
|
|
21
|
+
const __cache = new Map()
|
|
22
|
+
const __normalizeMessages = (mod) => mod.default ?? mod
|
|
23
|
+
|
|
24
|
+
const __loaders = {
|
|
25
|
+
${lazyLocales.map((l) => ` '${l}': () => import('${absoluteCatalogDir}/${l}.js'),`).join('\n')}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function __switchLocale(locale) {
|
|
29
|
+
if (__loadedLocales.has(locale)) {
|
|
30
|
+
__setCatalog(reconcile(__cache.get(locale) || __defaultMsgs))
|
|
31
|
+
__setCurrentLocale(locale)
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
__setLoading(true)
|
|
35
|
+
try {
|
|
36
|
+
const mod = __normalizeMessages(await __loaders[locale]())
|
|
37
|
+
__cache.set(locale, mod)
|
|
38
|
+
__loadedLocales.add(locale)
|
|
39
|
+
__setCatalog(reconcile(mod))
|
|
40
|
+
__setCurrentLocale(locale)
|
|
41
|
+
} finally {
|
|
42
|
+
__setLoading(false)
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function __preloadLocale(locale) {
|
|
47
|
+
if (__loadedLocales.has(locale) || !__loaders[locale]) return
|
|
48
|
+
try {
|
|
49
|
+
const mod = __normalizeMessages(await __loaders[locale]())
|
|
50
|
+
__cache.set(locale, mod)
|
|
51
|
+
__loadedLocales.add(locale)
|
|
52
|
+
} catch (e) { console.warn('[fluenti] preload failed:', locale, e) }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
globalThis[Symbol.for('${runtimeKey}')] = { __switchLocale, __preloadLocale }
|
|
56
|
+
|
|
57
|
+
export { __catalog, __switchLocale, __preloadLocale, __currentLocale, __loading, __loadedLocales }
|
|
58
|
+
`
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
generateRouteRuntime(options: RuntimeGeneratorOptions): string {
|
|
62
|
+
const { catalogDir, locales, sourceLocale, defaultBuildLocale } = options
|
|
63
|
+
const defaultLocale = defaultBuildLocale || sourceLocale
|
|
64
|
+
const absoluteCatalogDir = resolve(process.cwd(), catalogDir)
|
|
65
|
+
const runtimeKey = 'fluenti.runtime.solid'
|
|
66
|
+
const lazyLocales = locales.filter((locale) => locale !== defaultLocale)
|
|
67
|
+
|
|
68
|
+
return `
|
|
69
|
+
import { createSignal } from 'solid-js'
|
|
70
|
+
import { createStore, reconcile } from 'solid-js/store'
|
|
71
|
+
import __defaultMsgs from '${absoluteCatalogDir}/${defaultLocale}.js'
|
|
72
|
+
|
|
73
|
+
const [__catalog, __setCatalog] = createStore({ ...__defaultMsgs })
|
|
74
|
+
const [__currentLocale, __setCurrentLocale] = createSignal('${defaultLocale}')
|
|
75
|
+
const __loadedLocales = new Set(['${defaultLocale}'])
|
|
76
|
+
const [__loading, __setLoading] = createSignal(false)
|
|
77
|
+
const __cache = new Map()
|
|
78
|
+
const __loadedRoutes = new Set()
|
|
79
|
+
const __normalizeMessages = (mod) => mod.default ?? mod
|
|
80
|
+
|
|
81
|
+
const __loaders = {
|
|
82
|
+
${lazyLocales.map((l) => ` '${l}': () => import('${absoluteCatalogDir}/${l}.js'),`).join('\n')}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const __routeLoaders = {}
|
|
86
|
+
|
|
87
|
+
function __registerRouteLoader(routeId, locale, loader) {
|
|
88
|
+
const key = routeId + ':' + locale
|
|
89
|
+
__routeLoaders[key] = loader
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function __loadRoute(routeId, locale) {
|
|
93
|
+
const key = routeId + ':' + (locale || __currentLocale())
|
|
94
|
+
if (__loadedRoutes.has(key)) return
|
|
95
|
+
const loader = __routeLoaders[key]
|
|
96
|
+
if (!loader) return
|
|
97
|
+
const mod = __normalizeMessages(await loader())
|
|
98
|
+
__setCatalog(reconcile({ ...__catalog, ...mod }))
|
|
99
|
+
__loadedRoutes.add(key)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function __switchLocale(locale) {
|
|
103
|
+
if (locale === __currentLocale()) return
|
|
104
|
+
__setLoading(true)
|
|
105
|
+
try {
|
|
106
|
+
if (__cache.has(locale)) {
|
|
107
|
+
__setCatalog(reconcile(__cache.get(locale)))
|
|
108
|
+
} else {
|
|
109
|
+
const mod = __normalizeMessages(await __loaders[locale]())
|
|
110
|
+
__cache.set(locale, mod)
|
|
111
|
+
__setCatalog(reconcile(mod))
|
|
112
|
+
}
|
|
113
|
+
__loadedLocales.add(locale)
|
|
114
|
+
__setCurrentLocale(locale)
|
|
115
|
+
} finally {
|
|
116
|
+
__setLoading(false)
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function __preloadLocale(locale) {
|
|
121
|
+
if (__cache.has(locale) || !__loaders[locale]) return
|
|
122
|
+
try {
|
|
123
|
+
const mod = __normalizeMessages(await __loaders[locale]())
|
|
124
|
+
__cache.set(locale, mod)
|
|
125
|
+
__loadedLocales.add(locale)
|
|
126
|
+
} catch (e) { console.warn('[fluenti] preload failed:', locale, e) }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
globalThis[Symbol.for('${runtimeKey}')] = { __switchLocale, __preloadLocale }
|
|
130
|
+
|
|
131
|
+
export { __catalog, __switchLocale, __preloadLocale, __loadRoute, __registerRouteLoader, __currentLocale, __loading, __loadedLocales }
|
|
132
|
+
`
|
|
133
|
+
},
|
|
134
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Plugin } from 'vite'
|
|
2
|
+
import type { FluentiPluginOptions } from '@fluenti/vite-plugin'
|
|
3
|
+
import { createFluentiPlugins } from '@fluenti/vite-plugin'
|
|
4
|
+
import { solidRuntimeGenerator } from './solid-runtime'
|
|
5
|
+
|
|
6
|
+
export type { FluentiPluginOptions as FluentiSolidOptions } from '@fluenti/vite-plugin'
|
|
7
|
+
|
|
8
|
+
export default function fluentiSolid(options?: FluentiPluginOptions): Plugin[] {
|
|
9
|
+
return createFluentiPlugins(
|
|
10
|
+
{ ...options, framework: 'solid' },
|
|
11
|
+
[],
|
|
12
|
+
solidRuntimeGenerator,
|
|
13
|
+
)
|
|
14
|
+
}
|