@fluenti/solid 0.6.0 → 0.6.2

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 (62) hide show
  1. package/dist/components-entry-Bdm3s2x9.cjs +1 -0
  2. package/dist/{components-entry-C-Df62O-.js → components-entry-Dfzg6Idb.js} +189 -111
  3. package/dist/components-entry.cjs +1 -1
  4. package/dist/components-entry.js +1 -1
  5. package/dist/index.cjs +1 -2
  6. package/dist/index.js +1 -3
  7. package/dist/server.cjs +1 -2
  8. package/dist/server.js +0 -2
  9. package/dist/solid/src/compile-time-t.d.ts +1 -2
  10. package/dist/solid/src/compiled.d.ts +0 -1
  11. package/dist/solid/src/components/DateTime.d.ts +0 -1
  12. package/dist/solid/src/components/NumberFormat.d.ts +0 -1
  13. package/dist/solid/src/components-entry.d.ts +0 -1
  14. package/dist/solid/src/context.d.ts +8 -3
  15. package/dist/solid/src/hooks/__useI18n.d.ts +0 -1
  16. package/dist/solid/src/index.d.ts +0 -1
  17. package/dist/solid/src/msg.d.ts +1 -2
  18. package/dist/solid/src/plain-runtime.d.ts +1 -2
  19. package/dist/solid/src/plural.d.ts +0 -1
  20. package/dist/solid/src/provider.d.ts +0 -1
  21. package/dist/solid/src/rich-dom.d.ts +0 -1
  22. package/dist/solid/src/select.d.ts +0 -1
  23. package/dist/solid/src/server.d.ts +3 -4
  24. package/dist/solid/src/solid-runtime.d.ts +1 -2
  25. package/dist/solid/src/trans.d.ts +0 -1
  26. package/dist/solid/src/types.d.ts +1 -2
  27. package/dist/solid/src/use-i18n.d.ts +2 -2
  28. package/dist/solid/src/vite-plugin.d.ts +2 -3
  29. package/dist/vite-plugin.cjs +1 -2
  30. package/dist/vite-plugin.js +0 -2
  31. package/package.json +3 -3
  32. package/src/context.ts +158 -1
  33. package/src/use-i18n.ts +8 -1
  34. package/dist/components-entry-5CGlUlc5.cjs +0 -2
  35. package/dist/components-entry-5CGlUlc5.cjs.map +0 -1
  36. package/dist/components-entry-C-Df62O-.js.map +0 -1
  37. package/dist/index.cjs.map +0 -1
  38. package/dist/index.js.map +0 -1
  39. package/dist/server.cjs.map +0 -1
  40. package/dist/server.js.map +0 -1
  41. package/dist/solid/src/compile-time-t.d.ts.map +0 -1
  42. package/dist/solid/src/compiled.d.ts.map +0 -1
  43. package/dist/solid/src/components/DateTime.d.ts.map +0 -1
  44. package/dist/solid/src/components/NumberFormat.d.ts.map +0 -1
  45. package/dist/solid/src/components-entry.d.ts.map +0 -1
  46. package/dist/solid/src/context.d.ts.map +0 -1
  47. package/dist/solid/src/hooks/__useI18n.d.ts.map +0 -1
  48. package/dist/solid/src/index.d.ts.map +0 -1
  49. package/dist/solid/src/msg.d.ts.map +0 -1
  50. package/dist/solid/src/plain-runtime.d.ts.map +0 -1
  51. package/dist/solid/src/plural.d.ts.map +0 -1
  52. package/dist/solid/src/provider.d.ts.map +0 -1
  53. package/dist/solid/src/rich-dom.d.ts.map +0 -1
  54. package/dist/solid/src/select.d.ts.map +0 -1
  55. package/dist/solid/src/server.d.ts.map +0 -1
  56. package/dist/solid/src/solid-runtime.d.ts.map +0 -1
  57. package/dist/solid/src/trans.d.ts.map +0 -1
  58. package/dist/solid/src/types.d.ts.map +0 -1
  59. package/dist/solid/src/use-i18n.d.ts.map +0 -1
  60. package/dist/solid/src/vite-plugin.d.ts.map +0 -1
  61. package/dist/vite-plugin.cjs.map +0 -1
  62. package/dist/vite-plugin.js.map +0 -1
package/dist/server.cjs CHANGED
@@ -1,2 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@fluenti/core`),t=require(`node:async_hooks`);function n(n){let r=new t.AsyncLocalStorage,i=new Map,a={locale:null,instance:null};function o(){let e=r.getStore();return!e&&typeof process<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[fluenti] Server i18n used without withLocale() — locale state is shared across requests. Use withLocale() for concurrent SSR safety.`),e??a}function s(e){let t=o();t.locale=e,t.instance=null}async function c(e){let t=i.get(e);if(t)return t;let r=await n.loadMessages(e),a=typeof r==`object`&&r&&`default`in r?r.default:r;return i.set(e,a),a}async function l(t){let r={};r[t]=await c(t),n.fallbackLocale&&n.fallbackLocale!==t&&(r[n.fallbackLocale]=await c(n.fallbackLocale));let i={locale:t,messages:r};return n.fallbackLocale!==void 0&&(i.fallbackLocale=n.fallbackLocale),n.fallbackChain!==void 0&&(i.fallbackChain=n.fallbackChain),n.dateFormats!==void 0&&(i.dateFormats=n.dateFormats),n.numberFormats!==void 0&&(i.numberFormats=n.numberFormats),n.missing!==void 0&&(i.missing=n.missing),(0,e.createFluentiCore)(i)}async function u(){let e=o();!e.locale&&n.resolveLocale&&(e.locale=await n.resolveLocale());let t=e.locale;if(!t)throw Error(`[fluenti] No locale set. Call setLocale(locale) in your entry-server or layout before using getI18n(), or provide a resolveLocale function in createServerI18n config to auto-detect locale in server functions and other contexts where the layout does not run.`);return e.instance&&e.instance.locale===t||(e.instance=await l(t)),e.instance}async function d(e,t){let n={locale:e,instance:null};return r.run(n,async()=>t())}return{setLocale:s,getI18n:u,withLocale:d}}exports.createServerI18n=n,Object.defineProperty(exports,`detectLocale`,{enumerable:!0,get:function(){return e.detectLocale}}),Object.defineProperty(exports,`getDirection`,{enumerable:!0,get:function(){return e.getDirection}}),Object.defineProperty(exports,`getHydratedLocale`,{enumerable:!0,get:function(){return e.getHydratedLocale}}),Object.defineProperty(exports,`getSSRLocaleScript`,{enumerable:!0,get:function(){return e.getSSRLocaleScript}}),Object.defineProperty(exports,`isRTL`,{enumerable:!0,get:function(){return e.isRTL}});
2
- //# sourceMappingURL=server.cjs.map
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require(`@fluenti/core`),t=require(`node:async_hooks`);function n(n){let r=new t.AsyncLocalStorage,i=new Map,a={locale:null,instance:null};function o(){let e=r.getStore();return!e&&typeof process<`u`&&process.env.NODE_ENV===`development`&&console.warn(`[fluenti] Server i18n used without withLocale() — locale state is shared across requests. Use withLocale() for concurrent SSR safety.`),e??a}function s(e){let t=o();t.locale=e,t.instance=null}async function c(e){let t=i.get(e);if(t)return t;let r=await n.loadMessages(e),a=typeof r==`object`&&r&&`default`in r?r.default:r;return i.set(e,a),a}async function l(t){let r={};r[t]=await c(t),n.fallbackLocale&&n.fallbackLocale!==t&&(r[n.fallbackLocale]=await c(n.fallbackLocale));let i={locale:t,messages:r};return n.fallbackLocale!==void 0&&(i.fallbackLocale=n.fallbackLocale),n.fallbackChain!==void 0&&(i.fallbackChain=n.fallbackChain),n.dateFormats!==void 0&&(i.dateFormats=n.dateFormats),n.numberFormats!==void 0&&(i.numberFormats=n.numberFormats),n.missing!==void 0&&(i.missing=n.missing),(0,e.createFluentiCore)(i)}async function u(){let e=o();!e.locale&&n.resolveLocale&&(e.locale=await n.resolveLocale());let t=e.locale;if(!t)throw Error(`[fluenti] No locale set. Call setLocale(locale) in your entry-server or layout before using getI18n(), or provide a resolveLocale function in createServerI18n config to auto-detect locale in server functions and other contexts where the layout does not run.`);return e.instance&&e.instance.locale===t||(e.instance=await l(t)),e.instance}async function d(e,t){let n={locale:e,instance:null};return r.run(n,async()=>t())}return{setLocale:s,getI18n:u,withLocale:d}}exports.createServerI18n=n,Object.defineProperty(exports,`detectLocale`,{enumerable:!0,get:function(){return e.detectLocale}}),Object.defineProperty(exports,`getDirection`,{enumerable:!0,get:function(){return e.getDirection}}),Object.defineProperty(exports,`getHydratedLocale`,{enumerable:!0,get:function(){return e.getHydratedLocale}}),Object.defineProperty(exports,`getSSRLocaleScript`,{enumerable:!0,get:function(){return e.getSSRLocaleScript}}),Object.defineProperty(exports,`isRTL`,{enumerable:!0,get:function(){return e.isRTL}});
package/dist/server.js CHANGED
@@ -51,5 +51,3 @@ function s(t) {
51
51
  }
52
52
  //#endregion
53
53
  export { s as createServerI18n, t as detectLocale, n as getDirection, r as getHydratedLocale, i as getSSRLocaleScript, a as isRTL };
54
-
55
- //# sourceMappingURL=server.js.map
@@ -1,3 +1,2 @@
1
- import { CompileTimeT } from '../../core/src/index.ts';
1
+ import { CompileTimeT } from '@fluenti/core';
2
2
  export declare const t: CompileTimeT;
3
- //# sourceMappingURL=compile-time-t.d.ts.map
@@ -40,4 +40,3 @@ export declare const __FluentiCompiledSelect: Component<FluentiCompiledSelectPro
40
40
  export declare const __FluentiCompiledRichPlural: Component<FluentiCompiledRichPluralProps>;
41
41
  /** @internal Build-plugin fast path for rich-text `<Select>` usage. */
42
42
  export declare const __FluentiCompiledRichSelect: Component<FluentiCompiledRichSelectProps>;
43
- //# sourceMappingURL=compiled.d.ts.map
@@ -15,4 +15,3 @@ export type FluentiDateTimeProps = DateTimeProps;
15
15
  * ```
16
16
  */
17
17
  export declare function DateTime(props: DateTimeProps): import("solid-js").JSX.Element;
18
- //# sourceMappingURL=DateTime.d.ts.map
@@ -15,4 +15,3 @@ export type FluentiNumberFormatProps = NumberProps;
15
15
  * ```
16
16
  */
17
17
  export declare function NumberFormat(props: NumberProps): import("solid-js").JSX.Element;
18
- //# sourceMappingURL=NumberFormat.d.ts.map
@@ -9,4 +9,3 @@ export type { DateTimeProps, FluentiDateTimeProps } from './components/DateTime'
9
9
  export { NumberFormat } from './components/NumberFormat';
10
10
  export type { NumberProps, FluentiNumberFormatProps } from './components/NumberFormat';
11
11
  export { __FluentiCompiledTrans, __FluentiCompiledPlural, __FluentiCompiledSelect, __FluentiCompiledRichPlural, __FluentiCompiledRichSelect, } from './compiled';
12
- //# sourceMappingURL=components-entry.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { Accessor } from 'solid-js';
2
- import { FluentiCoreConfig, Locale, LocalizedString, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions, DiagnosticsConfig, CustomFormatter } from '../../core/src/index.ts';
2
+ import { FluentiCoreConfig, Locale, LocalizedString, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions, DiagnosticsConfig, CustomFormatter } from '@fluenti/core';
3
3
  /** Chunk loader for lazy locale loading */
4
4
  export type ChunkLoader = (locale: string) => Promise<Record<string, CompiledMessage> | {
5
5
  default: Record<string, CompiledMessage>;
@@ -58,6 +58,11 @@ export interface FluentiContext {
58
58
  /** Get the raw compiled message for a key without interpolation */
59
59
  tm(key: string, loc?: string): CompiledMessage | undefined;
60
60
  }
61
+ export type FluentiContextFallbackSource = 'singleton' | 'development';
62
+ export declare function resolveFluentiFallbackContext(): {
63
+ context: FluentiContext;
64
+ source: FluentiContextFallbackSource;
65
+ } | undefined;
61
66
  /**
62
67
  * Create a reactive i18n context backed by Solid signals.
63
68
  *
@@ -79,5 +84,5 @@ export interface FluentiContext {
79
84
  * ```
80
85
  */
81
86
  export declare function createFluentiContext(config: FluentiCoreConfig | FluentiConfig): FluentiContext;
82
- export declare const createFluenti: typeof createFluentiContext;
83
- //# sourceMappingURL=context.d.ts.map
87
+ export declare function createFluenti(config: FluentiCoreConfig | FluentiConfig): FluentiContext;
88
+ export declare function __resetFluentiGlobalStateForTests(): void;
@@ -9,4 +9,3 @@ import { FluentiContext } from '../types';
9
9
  * @internal
10
10
  */
11
11
  export declare function __useI18n(): FluentiContext;
12
- //# sourceMappingURL=__useI18n.d.ts.map
@@ -6,4 +6,3 @@ export { t } from './compile-time-t';
6
6
  export { Trans, Plural, Select, DateTime, NumberFormat } from './components-entry';
7
7
  export type { FluentiTransProps, FluentiPluralProps, FluentiSelectProps, DateTimeProps, FluentiDateTimeProps, NumberProps, FluentiNumberFormatProps, } from './components-entry';
8
8
  export { msg } from './msg';
9
- //# sourceMappingURL=index.d.ts.map
@@ -1,2 +1 @@
1
- export { msg } from '../../core/src/index.ts';
2
- //# sourceMappingURL=msg.d.ts.map
1
+ export { msg } from '@fluenti/core';
@@ -1,4 +1,4 @@
1
- import { PluralCategory } from '../../core/src/runtime.ts';
1
+ import { PluralCategory } from '@fluenti/core/runtime';
2
2
  export declare function resolvePropValue(value: unknown): unknown;
3
3
  export declare function resolveCompiledMessageId(id: string | undefined, message: string, context: string | undefined): string;
4
4
  export declare function buildPlainPluralMessage(forms: Partial<Record<PluralCategory, unknown>>, offset?: number): string | undefined;
@@ -6,4 +6,3 @@ export declare function buildPlainSelectMessage(forms: Record<string, unknown>):
6
6
  message: string;
7
7
  valueMap: Record<string, string>;
8
8
  } | undefined;
9
- //# sourceMappingURL=plain-runtime.d.ts.map
@@ -61,4 +61,3 @@ export interface FluentiPluralProps {
61
61
  * ```
62
62
  */
63
63
  export declare const Plural: Component<FluentiPluralProps>;
64
- //# sourceMappingURL=plural.d.ts.map
@@ -25,4 +25,3 @@ export declare const I18nCtx: import('solid-js').Context<FluentiContext | undefi
25
25
  * ```
26
26
  */
27
27
  export declare const I18nProvider: ParentComponent<FluentiConfig>;
28
- //# sourceMappingURL=provider.d.ts.map
@@ -8,4 +8,3 @@ export declare function serializeRichForms<T extends string>(keys: readonly T[],
8
8
  messages: Record<string, string>;
9
9
  components: Node[];
10
10
  };
11
- //# sourceMappingURL=rich-dom.d.ts.map
@@ -61,4 +61,3 @@ export interface FluentiSelectProps {
61
61
  * ```
62
62
  */
63
63
  export declare const SelectComp: Component<FluentiSelectProps>;
64
- //# sourceMappingURL=select.d.ts.map
@@ -1,6 +1,6 @@
1
- import { FluentiCoreInstanceFull, Locale, Messages, DateFormatOptions, NumberFormatOptions } from '../../core/src/index.ts';
2
- export { detectLocale, getSSRLocaleScript, getHydratedLocale, isRTL, getDirection } from '../../core/src/index.ts';
3
- export type { DetectLocaleOptions } from '../../core/src/index.ts';
1
+ import { FluentiCoreInstanceFull, Locale, Messages, DateFormatOptions, NumberFormatOptions } from '@fluenti/core';
2
+ export { detectLocale, getSSRLocaleScript, getHydratedLocale, isRTL, getDirection } from '@fluenti/core';
3
+ export type { DetectLocaleOptions } from '@fluenti/core';
4
4
  /**
5
5
  * Configuration for `createServerI18n`.
6
6
  */
@@ -97,4 +97,3 @@ export interface ServerI18n {
97
97
  * ```
98
98
  */
99
99
  export declare function createServerI18n(config: ServerI18nConfig): ServerI18n;
100
- //# sourceMappingURL=server.d.ts.map
@@ -1,3 +1,2 @@
1
- import { RuntimeGenerator } from '../../vite-plugin/src/index.ts';
1
+ import { RuntimeGenerator } from '@fluenti/vite-plugin';
2
2
  export declare const solidRuntimeGenerator: RuntimeGenerator;
3
- //# sourceMappingURL=solid-runtime.d.ts.map
@@ -43,4 +43,3 @@ export interface FluentiTransProps {
43
43
  * ```
44
44
  */
45
45
  export declare const Trans: Component<FluentiTransProps>;
46
- //# sourceMappingURL=trans.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { Accessor } from 'solid-js';
2
- import { FluentiCoreConfig, Locale, LocalizedString, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions } from '../../core/src/index.ts';
2
+ import { FluentiCoreConfig, Locale, LocalizedString, Messages, CompiledMessage, MessageDescriptor, DateFormatOptions, NumberFormatOptions } from '@fluenti/core';
3
3
  /** Chunk loader for lazy locale loading */
4
4
  export type ChunkLoader = (locale: string) => Promise<Record<string, CompiledMessage> | {
5
5
  default: Record<string, CompiledMessage>;
@@ -42,4 +42,3 @@ export interface FluentiContext {
42
42
  /** Preload a locale in the background without switching to it */
43
43
  preloadLocale(locale: string): void;
44
44
  }
45
- //# sourceMappingURL=types.d.ts.map
@@ -2,9 +2,9 @@ import { FluentiContext } from './context';
2
2
  /**
3
3
  * Access the i18n context from the nearest `<I18nProvider>`.
4
4
  *
5
- * Throws if no provider is found in the component tree.
5
+ * Falls back to a global `createFluenti()` singleton when present.
6
+ * In development, returns a no-op fallback context instead of throwing.
6
7
  */
7
8
  export declare function useI18n(): FluentiContext;
8
9
  /** Shorthand hook that returns only the current locale accessor. */
9
10
  export declare function useLocale(): () => string;
10
- //# sourceMappingURL=use-i18n.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import { Plugin } from 'vite';
2
- import { FluentiPluginOptions } from '../../vite-plugin/src/index.ts';
3
- export type { FluentiPluginOptions as FluentiSolidOptions } from '../../vite-plugin/src/index.ts';
2
+ import { FluentiPluginOptions } from '@fluenti/vite-plugin';
3
+ export type { FluentiPluginOptions as FluentiSolidOptions } from '@fluenti/vite-plugin';
4
4
  /**
5
5
  * Fluenti SolidJS Vite plugin.
6
6
  *
@@ -22,4 +22,3 @@ export type { FluentiPluginOptions as FluentiSolidOptions } from '../../vite-plu
22
22
  * ```
23
23
  */
24
24
  export default function fluentiSolid(options?: FluentiPluginOptions): Plugin[];
25
- //# sourceMappingURL=vite-plugin.d.ts.map
@@ -1,3 +1,2 @@
1
1
  let e=require(`@fluenti/vite-plugin`);var t=(0,e.createRuntimeGenerator)({imports:`import { createSignal } from 'solid-js'
2
- import { createStore, reconcile } from 'solid-js/store'`,catalogInit:`const [__catalog, __setCatalog] = createStore({ ...__defaultMsgs })`,localeInit:e=>`const [__currentLocale, __setCurrentLocale] = createSignal(${JSON.stringify(e)})`,loadingInit:`const [__loading, __setLoading] = createSignal(false)`,catalogUpdate:e=>`__setCatalog(reconcile(${e}))`,localeUpdate:e=>`__setCurrentLocale(${e})`,loadingUpdate:e=>`__setLoading(${e})`,localeRead:`__currentLocale()`,runtimeKey:`fluenti.runtime.solid.v1`});function n(n){return(0,e.createFluentiPlugins)({...n?.config===void 0?{}:{config:n.config},framework:`solid`},[],t)}module.exports=n;
3
- //# sourceMappingURL=vite-plugin.cjs.map
2
+ import { createStore, reconcile } from 'solid-js/store'`,catalogInit:`const [__catalog, __setCatalog] = createStore({ ...__defaultMsgs })`,localeInit:e=>`const [__currentLocale, __setCurrentLocale] = createSignal(${JSON.stringify(e)})`,loadingInit:`const [__loading, __setLoading] = createSignal(false)`,catalogUpdate:e=>`__setCatalog(reconcile(${e}))`,localeUpdate:e=>`__setCurrentLocale(${e})`,loadingUpdate:e=>`__setLoading(${e})`,localeRead:`__currentLocale()`,runtimeKey:`fluenti.runtime.solid.v1`});function n(n){return(0,e.createFluentiPlugins)({...n?.config===void 0?{}:{config:n.config},framework:`solid`},[],t)}module.exports=n;
@@ -21,5 +21,3 @@ function r(t) {
21
21
  }
22
22
  //#endregion
23
23
  export { r as default };
24
-
25
- //# sourceMappingURL=vite-plugin.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fluenti/solid",
3
- "version": "0.6.0",
3
+ "version": "0.6.2",
4
4
  "type": "module",
5
5
  "description": "SolidJS compile-time i18n — Trans/Plural/Select components, I18nProvider, useI18n",
6
6
  "homepage": "https://fluenti.dev",
@@ -95,8 +95,8 @@
95
95
  }
96
96
  },
97
97
  "dependencies": {
98
- "@fluenti/vite-plugin": "0.6.0",
99
- "@fluenti/core": "0.6.0"
98
+ "@fluenti/core": "0.6.2",
99
+ "@fluenti/vite-plugin": "0.6.2"
100
100
  },
101
101
  "devDependencies": {
102
102
  "@solidjs/testing-library": "^0.8",
package/src/context.ts CHANGED
@@ -13,6 +13,8 @@ interface SplitRuntimeModule {
13
13
  }
14
14
 
15
15
  const SPLIT_RUNTIME_KEY = Symbol.for('fluenti.runtime.solid.v1')
16
+ const GLOBAL_FLUENTI_CONTEXT_KEY = Symbol.for('fluenti.solid.context.v1')
17
+ const DEVELOPMENT_FALLBACK_CONTEXT_KEY = Symbol.for('fluenti.solid.fallback-context.v1')
16
18
 
17
19
  function getSplitRuntimeModule(): SplitRuntimeModule | null {
18
20
  const runtime = (globalThis as Record<PropertyKey, unknown>)[SPLIT_RUNTIME_KEY]
@@ -29,6 +31,41 @@ function resolveChunkMessages(
29
31
  : loaded
30
32
  }
31
33
 
34
+ function isDevelopmentMode(): boolean {
35
+ try {
36
+ if (typeof process !== 'undefined' && process.env != null && process.env['NODE_ENV'] === 'production') {
37
+ return false
38
+ }
39
+ } catch {
40
+ // process not available
41
+ }
42
+ try {
43
+ if (typeof import.meta !== 'undefined' && (import.meta as { env?: { DEV?: boolean } }).env != null) {
44
+ return !!(import.meta as { env?: { DEV?: boolean } }).env?.DEV
45
+ }
46
+ } catch {
47
+ // import.meta not available
48
+ }
49
+ try {
50
+ if (typeof process !== 'undefined' && process.env != null) {
51
+ return process.env['NODE_ENV'] !== 'production'
52
+ }
53
+ } catch {
54
+ // process not available
55
+ }
56
+ return false
57
+ }
58
+
59
+ function getStoredContext(key: symbol): FluentiContext | undefined {
60
+ const value = (globalThis as Record<PropertyKey, unknown>)[key]
61
+ return value && typeof value === 'object' ? value as FluentiContext : undefined
62
+ }
63
+
64
+ function setStoredContext(key: symbol, context: FluentiContext): FluentiContext {
65
+ ;(globalThis as Record<PropertyKey, unknown>)[key] = context
66
+ return context
67
+ }
68
+
32
69
  /** @internal Map locale → default currency code */
33
70
  const LOCALE_CURRENCY_MAP: Record<string, string> = {
34
71
  'en': 'USD', 'en-US': 'USD', 'en-GB': 'GBP', 'en-AU': 'AUD', 'en-CA': 'CAD',
@@ -121,6 +158,119 @@ export interface FluentiContext {
121
158
  tm(key: string, loc?: string): CompiledMessage | undefined
122
159
  }
123
160
 
161
+ function createDevelopmentFallbackContext(): FluentiContext {
162
+ let currentLocale: Locale = 'en'
163
+ const loadedMessages: Record<string, Messages> = { en: {} }
164
+ const core = createFluentiCore({
165
+ locale: currentLocale,
166
+ messages: loadedMessages,
167
+ devWarnings: false,
168
+ })
169
+
170
+ const locale = (): Locale => currentLocale
171
+ const isLoading = (): boolean => false
172
+ const loadedLocales = (): Set<string> => new Set(Object.keys(loadedMessages))
173
+
174
+ function syncLocale(): void {
175
+ if (core.locale !== currentLocale) {
176
+ core.locale = currentLocale
177
+ }
178
+ }
179
+
180
+ function t(strings: TemplateStringsArray, ...exprs: unknown[]): LocalizedString
181
+ function t(id: string | MessageDescriptor, values?: Record<string, unknown>): LocalizedString
182
+ function t(idOrStrings: string | MessageDescriptor | TemplateStringsArray, ...rest: unknown[]): LocalizedString {
183
+ syncLocale()
184
+ if (Array.isArray(idOrStrings) && 'raw' in idOrStrings) {
185
+ return core.t(idOrStrings as TemplateStringsArray, ...rest) as LocalizedString
186
+ }
187
+ return core.t(
188
+ idOrStrings as string | MessageDescriptor,
189
+ rest[0] as Record<string, unknown> | undefined,
190
+ ) as LocalizedString
191
+ }
192
+
193
+ const setLocale = async (newLocale: Locale): Promise<void> => {
194
+ currentLocale = newLocale
195
+ core.locale = newLocale
196
+ loadedMessages[newLocale] ??= {}
197
+ }
198
+
199
+ const loadMessages = (loc: Locale, msgs: Messages): void => {
200
+ core.loadMessages(loc, msgs)
201
+ loadedMessages[loc] = { ...loadedMessages[loc], ...msgs }
202
+ }
203
+
204
+ const getLocales = (): Locale[] => Array.from(new Set([...core.getLocales(), ...Object.keys(loadedMessages)]))
205
+
206
+ const d = (value: Date | number, style?: string): LocalizedString => {
207
+ syncLocale()
208
+ return core.d(value, style) as LocalizedString
209
+ }
210
+
211
+ const n = (value: number, style?: string): LocalizedString => {
212
+ syncLocale()
213
+ return core.n(value, style) as LocalizedString
214
+ }
215
+
216
+ const format = (message: string, values?: Record<string, unknown>): LocalizedString => {
217
+ syncLocale()
218
+ return core.format(message, values) as LocalizedString
219
+ }
220
+
221
+ const preloadLocale = (_locale: string): void => {}
222
+
223
+ const te = (key: string, loc?: string): boolean => {
224
+ const messages = loadedMessages[loc ?? currentLocale]
225
+ return messages !== undefined && key in messages
226
+ }
227
+
228
+ const tm = (key: string, loc?: string): CompiledMessage | undefined => {
229
+ const messages = loadedMessages[loc ?? currentLocale]
230
+ return messages ? messages[key] : undefined
231
+ }
232
+
233
+ return {
234
+ locale,
235
+ setLocale,
236
+ t,
237
+ loadMessages,
238
+ getLocales,
239
+ d,
240
+ n,
241
+ format,
242
+ isLoading,
243
+ loadedLocales,
244
+ preloadLocale,
245
+ te,
246
+ tm,
247
+ }
248
+ }
249
+
250
+ export type FluentiContextFallbackSource = 'singleton' | 'development'
251
+
252
+ export function resolveFluentiFallbackContext():
253
+ | { context: FluentiContext; source: FluentiContextFallbackSource }
254
+ | undefined {
255
+ const globalContext = getStoredContext(GLOBAL_FLUENTI_CONTEXT_KEY)
256
+ if (globalContext) {
257
+ return { context: globalContext, source: 'singleton' }
258
+ }
259
+
260
+ if (!isDevelopmentMode()) {
261
+ return undefined
262
+ }
263
+
264
+ let fallbackContext = getStoredContext(DEVELOPMENT_FALLBACK_CONTEXT_KEY)
265
+ if (!fallbackContext) {
266
+ fallbackContext = setStoredContext(
267
+ DEVELOPMENT_FALLBACK_CONTEXT_KEY,
268
+ createDevelopmentFallbackContext(),
269
+ )
270
+ }
271
+ return { context: fallbackContext, source: 'development' }
272
+ }
273
+
124
274
  /**
125
275
  * Create a reactive i18n context backed by Solid signals.
126
276
  *
@@ -299,4 +449,11 @@ export function createFluentiContext(config: FluentiCoreConfig | FluentiConfig):
299
449
  return { locale, setLocale, t, loadMessages, getLocales, d, n, format, isLoading, loadedLocales, preloadLocale, te, tm }
300
450
  }
301
451
 
302
- export const createFluenti = createFluentiContext
452
+ export function createFluenti(config: FluentiCoreConfig | FluentiConfig): FluentiContext {
453
+ return setStoredContext(GLOBAL_FLUENTI_CONTEXT_KEY, createFluentiContext(config))
454
+ }
455
+
456
+ export function __resetFluentiGlobalStateForTests(): void {
457
+ delete (globalThis as Record<PropertyKey, unknown>)[GLOBAL_FLUENTI_CONTEXT_KEY]
458
+ delete (globalThis as Record<PropertyKey, unknown>)[DEVELOPMENT_FALLBACK_CONTEXT_KEY]
459
+ }
package/src/use-i18n.ts CHANGED
@@ -1,11 +1,13 @@
1
1
  import { useContext } from 'solid-js'
2
2
  import { I18nCtx } from './provider'
3
+ import { resolveFluentiFallbackContext } from './context'
3
4
  import type { FluentiContext } from './context'
4
5
 
5
6
  /**
6
7
  * Access the i18n context from the nearest `<I18nProvider>`.
7
8
  *
8
- * Throws if no provider is found in the component tree.
9
+ * Falls back to a global `createFluenti()` singleton when present.
10
+ * In development, returns a no-op fallback context instead of throwing.
9
11
  */
10
12
  export function useI18n(): FluentiContext {
11
13
  const ctx = useContext(I18nCtx)
@@ -13,6 +15,11 @@ export function useI18n(): FluentiContext {
13
15
  return ctx
14
16
  }
15
17
 
18
+ const fallback = resolveFluentiFallbackContext()
19
+ if (fallback) {
20
+ return fallback.context
21
+ }
22
+
16
23
  throw new Error(
17
24
  'useI18n() must be used inside an <I18nProvider>.',
18
25
  )
@@ -1,2 +0,0 @@
1
- let e=require(`solid-js`),t=require(`@fluenti/core`),n=require(`solid-js/web`),r=require(`@fluenti/core/runtime`);var i=Symbol.for(`fluenti.runtime.solid.v1`);function a(){let e=globalThis[i];return typeof e==`object`&&e?e:null}function o(e){return typeof e==`object`&&e&&`default`in e?e.default:e}var s={en:`USD`,"en-US":`USD`,"en-GB":`GBP`,"en-AU":`AUD`,"en-CA":`CAD`,"zh-CN":`CNY`,"zh-TW":`TWD`,"zh-HK":`HKD`,ja:`JPY`,"ja-JP":`JPY`,ko:`KRW`,"ko-KR":`KRW`,de:`EUR`,"de-DE":`EUR`,"de-AT":`EUR`,fr:`EUR`,"fr-FR":`EUR`,"fr-CA":`CAD`,es:`EUR`,"es-ES":`EUR`,"es-MX":`MXN`,pt:`EUR`,"pt-BR":`BRL`,"pt-PT":`EUR`,it:`EUR`,ru:`RUB`,ar:`SAR`,hi:`INR`},c={short:{year:`numeric`,month:`numeric`,day:`numeric`},long:{year:`numeric`,month:`long`,day:`numeric`,weekday:`long`},time:{hour:`numeric`,minute:`numeric`},datetime:{year:`numeric`,month:`short`,day:`numeric`,hour:`numeric`,minute:`numeric`}},l={currency:e=>({style:`currency`,currency:s[e]??s[e.split(`-`)[0]]??`USD`}),percent:{style:`percent`},decimal:{minimumFractionDigits:2,maximumFractionDigits:2}};function u(n){let[r,i]=(0,e.createSignal)(n.locale),[s,u]=(0,e.createSignal)(!1),d=new Set([n.locale]),[f,p]=(0,e.createSignal)(new Set(d)),m={...n.messages},h=n,g=h.lazyLocaleLoading??n.splitting??!1,_={locale:n.locale,messages:n.messages??{},dateFormats:{...c,...h.dateFormats},numberFormats:{...l,...h.numberFormats}};n.fallbackLocale!==void 0&&(_.fallbackLocale=n.fallbackLocale),h.fallbackChain!==void 0&&(_.fallbackChain=h.fallbackChain),n.missing!==void 0&&(_.missing=n.missing),h.diagnostics!==void 0&&(_.diagnostics=h.diagnostics),h.interpolate!==void 0&&(_.interpolate=h.interpolate),_.devWarnings=_.devWarnings??(typeof process<`u`&&process.env.NODE_ENV===`development`);let v=(0,t.createFluentiCore)(_);function y(e,...t){let n=r();return v.locale!==n&&(v.locale=n),Array.isArray(e)&&`raw`in e?v.t(e,...t):v.t(e,t[0])}let b=(e,t)=>{v.loadMessages(e,t),m[e]={...m[e],...t},d.add(e),p(new Set(d))},x=0,S=async e=>{if(!g||!h.chunkLoader){i(e);return}let t=a();if(d.has(e)){try{t?.__switchLocale&&await t.__switchLocale(e)}catch(t){console.warn(`[fluenti] split runtime switch failed for locale "${e}"`,t)}i(e);return}let n=++x;u(!0);try{let r=o(await h.chunkLoader(e));if(v.loadMessages(e,r),m[e]={...m[e],...r},d.add(e),p(new Set(d)),n!==x)return;try{t?.__switchLocale&&await t.__switchLocale(e)}catch(t){console.warn(`[fluenti] split runtime switch failed for locale "${e}"`,t)}if(n!==x)return;i(e)}finally{n===x&&u(!1)}},C=new Set;return{locale:r,setLocale:S,t:y,loadMessages:b,getLocales:()=>v.getLocales(),d:(e,t)=>{let n=r();return v.locale!==n&&(v.locale=n),v.d(e,t)},n:(e,t)=>{let n=r();return v.locale!==n&&(v.locale=n),v.n(e,t)},format:(e,t)=>{let n=r();return v.locale!==n&&(v.locale=n),v.format(e,t)},isLoading:s,loadedLocales:f,preloadLocale:e=>{if(!g||d.has(e)||!h.chunkLoader||C.has(e))return;C.add(e);let t=a();h.chunkLoader(e).then(async n=>{let r=o(n);v.loadMessages(e,r),m[e]={...m[e],...r},d.add(e),p(new Set(d)),t?.__preloadLocale&&await t.__preloadLocale(e)}).catch(t=>{console.warn(`[fluenti] preload failed:`,e,t)}).finally(()=>{C.delete(e)})},te:(e,t)=>{let n=m[t??r()];return n!==void 0&&e in n},tm:(e,t)=>{let n=m[t??r()];return n?n[e]:void 0}}}var d=u,f=(0,e.createContext)(),p=e=>{let t=u(e);return(0,n.createComponent)(f.Provider,{value:t,get children(){return e.children}})};function m(){let t=(0,e.useContext)(f);if(t)return t;throw Error(`useI18n() must be used inside an <I18nProvider>.`)}function h(){return m().locale}function g(e){return typeof Node<`u`&&e instanceof Node}function _(e){return typeof e==`function`&&!e.length?e():e}function v(e){let t=[],n=``;function i(e){let a=_(e);if(a==null||typeof a==`boolean`)return;if(Array.isArray(a)){for(let e of a)i(e);return}if(typeof a==`string`||typeof a==`number`){n+=String(a);return}if(!g(a))return;if(a.nodeType===Node.TEXT_NODE){n+=a.textContent??``;return}if(a.nodeType===Node.DOCUMENT_FRAGMENT_NODE){i(Array.from(a.childNodes));return}let o=t.length,s=v(Array.from(a.childNodes));t.push(a.cloneNode(!1)),t.push(...s.components),s.message===``&&s.components.length===0?n+=`<${o}/>`:n+=`<${o}>${(0,r.offsetIndices)(s.message,o+1)}</${o}>`}return i(e),{message:n,components:t}}function y(e,t){let n=_(t);if(!(n==null||typeof n==`boolean`)){if(Array.isArray(n)){for(let t of n)y(e,t);return}if(typeof n==`string`||typeof n==`number`){e.appendChild(document.createTextNode(String(n)));return}g(n)&&e.appendChild(n)}}function b(e,t){let n=/<(\d+)(?:\/>|(>)([\s\S]*?)<\/\1>)/g,r=[],i=0,a;for(n.lastIndex=0,a=n.exec(e);a!==null;){a.index>i&&r.push(e.slice(i,a.index));let o=Number(a[1]),s=a[2]===void 0;if(!Number.isInteger(o)||o<0||o>=t.length){s||r.push(a[3]??``),i=n.lastIndex,a=n.exec(e);continue}let c=t[o];if(s)c&&r.push(c.cloneNode(!1));else{let e=b(a[2]===void 0?``:a[3],t);if(c){let t=c.cloneNode(!1);y(t,e),r.push(t)}else r.push(a[3]??``)}i=n.lastIndex,a=n.exec(e)}return i<e.length&&r.push(e.slice(i)),r.length<=1?r[0]??``:r}function x(e,t){let n={},i=[];for(let a of e){let e=t[a];if(e===void 0)continue;let o=v(e);n[a]=(0,r.offsetIndices)(o.message,i.length),i.push(...o.components)}for(let[a,o]of Object.entries(t)){if(e.includes(a)||o===void 0)continue;let t=v(o);n[a]=(0,r.offsetIndices)(t.message,i.length),i.push(...t.components)}return{messages:n,components:i}}var S=100;function C(e,t=0){if(t>S)return[{type:`text`,value:e}];let n=[],r=0;for(;r<e.length;){let i=e.indexOf(`<`,r);if(i===-1){n.push({type:`text`,value:e.slice(r)});break}i>r&&n.push({type:`text`,value:e.slice(r,i)});let a=e.slice(i).match(/^<(\w+)\s*\/>/);if(a){n.push({type:`tag`,name:a[1],children:[]}),r=i+a[0].length;continue}let o=e.slice(i).match(/^<(\w+)>/);if(!o){n.push({type:`text`,value:`<`}),r=i+1;continue}let s=o[1],c=i+o[0].length,l=w(e,s,c);if(l===-1){n.push({type:`text`,value:e.slice(i,c)}),r=c;continue}let u=e.slice(c,l),d=`</${s}>`;n.push({type:`tag`,name:s,children:C(u,t+1)}),r=l+d.length}return n}function w(e,t,n){let r=`<${t}>`,i=`</${t}>`,a=1,o=n;for(;o<e.length&&a>0;){let t=e.indexOf(r,o),n=e.indexOf(i,o);if(n===-1)return-1;if(t!==-1&&t<n)a++,o=t+r.length;else{if(a--,a===0)return n;o=n+i.length}}return-1}function T(e,t){let r=e.map(e=>{if(e.type===`text`)return e.value;let r=t[e.name];return r?(0,n.createComponent)(n.Dynamic,{component:r,children:e.children.length>0?T(e.children,t):void 0}):T(e.children,t)});return r.length===1?r[0]:r}var E=t=>{let{t:r}=m(),i=(0,e.children)(()=>t.children),a=(0,e.createMemo)(()=>{let e=t.__message??t.message;return typeof e==`function`?e():e}),o=(0,e.createMemo)(()=>t.__components??t.components);return(()=>{let e=a(),s=o();if(e!==void 0&&s)return T(C(r({...t.id===void 0?{}:{id:t.id},message:e,...t.context===void 0?{}:{context:t.context},...t.comment===void 0?{}:{comment:t.comment}})),s);let c=i.toArray();if(c.length===0)return null;let l=v(c),u=r({...t.id===void 0?{}:{id:t.id},message:l.message,...t.context===void 0?{}:{context:t.context},...t.comment===void 0?{}:{comment:t.comment}}),d=l.components.length>0?b(u,l.components):u;return Array.isArray(d)&&d.length>1&&t.tag?(0,n.createComponent)(n.Dynamic,{get component(){return t.tag},children:d}):d})};function D(e){return typeof e==`function`&&!e.length?e():e}function O(e,t,n){return e??(n===void 0?t:(0,r.hashMessage)(t,n))}function k(e){return typeof e==`string`||typeof e==`number`}function A(e,t){let n={};for(let t of r.PLURAL_CATEGORIES){let r=D(e[t]);if(r!==void 0){if(!k(r))return;n[t]=String(r)}}if(n.other!==void 0)return(0,r.buildICUPluralMessage)(n,t)}function j(e){let t={};for(let[n,r]of Object.entries(e)){let e=D(r);if(e!==void 0){if(!k(e))return;t[n]=String(e)}}if(t.other===void 0)return;let n=(0,r.normalizeSelectForms)(t);return{message:(0,r.buildICUSelectMessage)(n.forms),valueMap:n.valueMap}}var M=e=>{let{t}=m();return(()=>{let i={};for(let t of r.PLURAL_CATEGORIES){let n=D(e[t]);n!==void 0&&(i[t]=n)}let a=A(i,e.offset);if(a!==void 0){let r=t({id:O(e.id,a,e.context),message:a,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{count:e.value});return e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},children:r}):r}let{messages:o,components:s}=x(r.PLURAL_CATEGORIES,i),c=(0,r.buildICUPluralMessage)({...o.zero!==void 0&&{zero:o.zero},...o.one!==void 0&&{one:o.one},...o.two!==void 0&&{two:o.two},...o.few!==void 0&&{few:o.few},...o.many!==void 0&&{many:o.many},other:o.other??``},e.offset),l=t({id:O(e.id,c,e.context),message:c,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{count:e.value}),u=s.length>0?b(l,s):l;return e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},children:u}):u})},N=e=>{let{t}=m(),i=()=>{let n=new Set([`value`,`id`,`context`,`comment`,`options`,`other`,`tag`,`children`,`ref`,`class`,`className`,`style`,`classList`,`on`,`oncapture`,`use`,`prop`]),i=e.options===void 0?{...Object.fromEntries(Object.entries(e).filter(([e])=>!n.has(e)&&!e.startsWith(`data-`)&&!e.startsWith(`aria-`)&&!e.startsWith(`on`))),other:e.other}:{...e.options,other:e.other},a=j(i);if(a!==void 0)return t({id:O(e.id,a.message,e.context),message:a.message,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{value:a.valueMap[e.value]??`other`});let o=[...Object.keys(i).filter(e=>e!==`other`),`other`],{messages:s,components:c}=x(o,i),l=(0,r.normalizeSelectForms)(Object.fromEntries([...o].map(e=>[e,s[e]??``]))),u=t({id:O(e.id,(0,r.buildICUSelectMessage)(l.forms),e.context),message:(0,r.buildICUSelectMessage)(l.forms),...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{value:l.valueMap[e.value]??`other`});return c.length>0?b(u,c):u};return(()=>e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},get children(){return i()}}):(0,n.memo)(i))};function P(e){let{d:t}=m();return(0,n.memo)(()=>t(e.value,e.format))}function F(e){let{n:t}=m();return(0,n.memo)(()=>t(e.value,e.format))}var I=e=>{let{t}=m();return(()=>{let i=t({id:e.id??(0,r.hashMessage)(e.message,e.context),message:e.message,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}}),a=e.components&&e.components.length>0?b(i,e.components):i;return e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},children:a}):a})},L=e=>{let{t}=m();return(()=>{let r=t({id:O(e.id,e.message,e.context),message:e.message,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{count:e.value});return e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},children:r}):r})},R=e=>{let{t}=m();return(()=>{let r=t({id:O(e.id,e.message,e.context),message:e.message,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{value:e.valueMap?.[e.value]??`other`});return e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},children:r}):r})},z=e=>{let{t}=m();return(()=>{let r=t({id:O(e.id,e.message,e.context),message:e.message,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{count:e.value}),i=e.components.length>0?b(r,e.components):r;return e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},children:i}):i})},B=e=>{let{t}=m();return(()=>{let r=t({id:O(e.id,e.message,e.context),message:e.message,...e.context===void 0?{}:{context:e.context},...e.comment===void 0?{}:{comment:e.comment}},{value:e.valueMap?.[e.value]??`other`}),i=e.components.length>0?b(r,e.components):r;return e.tag?(0,n.createComponent)(n.Dynamic,{get component(){return e.tag},children:i}):i})};Object.defineProperty(exports,`a`,{enumerable:!0,get:function(){return I}}),Object.defineProperty(exports,`c`,{enumerable:!0,get:function(){return N}}),Object.defineProperty(exports,`d`,{enumerable:!0,get:function(){return m}}),Object.defineProperty(exports,`f`,{enumerable:!0,get:function(){return h}}),Object.defineProperty(exports,`i`,{enumerable:!0,get:function(){return R}}),Object.defineProperty(exports,`l`,{enumerable:!0,get:function(){return M}}),Object.defineProperty(exports,`m`,{enumerable:!0,get:function(){return d}}),Object.defineProperty(exports,`n`,{enumerable:!0,get:function(){return z}}),Object.defineProperty(exports,`o`,{enumerable:!0,get:function(){return F}}),Object.defineProperty(exports,`p`,{enumerable:!0,get:function(){return p}}),Object.defineProperty(exports,`r`,{enumerable:!0,get:function(){return B}}),Object.defineProperty(exports,`s`,{enumerable:!0,get:function(){return P}}),Object.defineProperty(exports,`t`,{enumerable:!0,get:function(){return L}}),Object.defineProperty(exports,`u`,{enumerable:!0,get:function(){return E}});
2
- //# sourceMappingURL=components-entry-5CGlUlc5.cjs.map