@better-i18n/use-intl 0.1.8 → 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.
Files changed (53) hide show
  1. package/README.md +7 -0
  2. package/dist/__tests__/provider-config.test.d.ts +2 -0
  3. package/dist/__tests__/provider-config.test.d.ts.map +1 -0
  4. package/dist/__tests__/provider-config.test.js +79 -0
  5. package/dist/__tests__/provider-config.test.js.map +1 -0
  6. package/dist/components/locale-dropdown.d.ts +62 -0
  7. package/dist/components/locale-dropdown.d.ts.map +1 -0
  8. package/dist/components/locale-dropdown.js +298 -0
  9. package/dist/components/locale-dropdown.js.map +1 -0
  10. package/dist/components.d.ts +44 -0
  11. package/dist/components.d.ts.map +1 -0
  12. package/dist/components.js +38 -0
  13. package/dist/components.js.map +1 -0
  14. package/dist/context.d.ts +43 -0
  15. package/dist/context.d.ts.map +1 -0
  16. package/{src/context.tsx → dist/context.js} +8 -17
  17. package/dist/context.js.map +1 -0
  18. package/dist/hooks/useLocaleRouter.d.ts +75 -0
  19. package/dist/hooks/useLocaleRouter.d.ts.map +1 -0
  20. package/dist/hooks/useLocaleRouter.js +89 -0
  21. package/dist/hooks/useLocaleRouter.js.map +1 -0
  22. package/dist/hooks.d.ts +63 -0
  23. package/dist/hooks.d.ts.map +1 -0
  24. package/{src/hooks.ts → dist/hooks.js} +13 -25
  25. package/dist/hooks.js.map +1 -0
  26. package/dist/index.d.ts +14 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +17 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/middleware/index.d.ts +3 -0
  31. package/dist/middleware/index.d.ts.map +1 -0
  32. package/dist/middleware/index.js +68 -0
  33. package/dist/middleware/index.js.map +1 -0
  34. package/dist/provider.d.ts +51 -0
  35. package/dist/provider.d.ts.map +1 -0
  36. package/dist/provider.js +138 -0
  37. package/dist/provider.js.map +1 -0
  38. package/dist/server.d.ts +79 -0
  39. package/dist/server.d.ts.map +1 -0
  40. package/dist/server.js +156 -0
  41. package/dist/server.js.map +1 -0
  42. package/dist/types.d.ts +71 -0
  43. package/dist/types.d.ts.map +1 -0
  44. package/dist/types.js +2 -0
  45. package/dist/types.js.map +1 -0
  46. package/package.json +22 -9
  47. package/src/components.tsx +0 -76
  48. package/src/hooks/useLocaleRouter.ts +0 -147
  49. package/src/index.ts +0 -46
  50. package/src/middleware/index.ts +0 -114
  51. package/src/provider.tsx +0 -183
  52. package/src/server.ts +0 -108
  53. package/src/types.ts +0 -83
package/README.md CHANGED
@@ -93,11 +93,18 @@ Main provider that combines Better i18n CDN with use-intl.
93
93
  timeZone="Europe/Berlin" // Optional: Timezone for formatting
94
94
  now={new Date()} // Optional: Current time (SSR)
95
95
  onLocaleChange={(l) => {}} // Optional: Locale change callback
96
+ // Fallback & Resilience
97
+ storage={storageAdapter} // Optional: Persistent cache (localStorage, etc.)
98
+ staticData={bundledData} // Optional: Bundled translations as last-resort
99
+ fetchTimeout={10000} // Optional: CDN timeout in ms (default: 10s)
100
+ retryCount={1} // Optional: CDN retry attempts (default: 1)
96
101
  >
97
102
  {children}
98
103
  </BetterI18nProvider>
99
104
  ```
100
105
 
106
+ See [`@better-i18n/core`](https://www.npmjs.com/package/@better-i18n/core) for storage adapters and fallback chain details.
107
+
101
108
  ### Hooks
102
109
 
103
110
  #### `useTranslations(namespace?)`
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=provider-config.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-config.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/provider-config.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,79 @@
1
+ import { describe, it, expect } from "vitest";
2
+ // ─── Test fixtures ──────────────────────────────────────────────────
3
+ const mockStorage = {
4
+ get: async () => null,
5
+ set: async () => { },
6
+ };
7
+ const mockStaticData = {
8
+ en: { common: { hello: "Hello" } },
9
+ tr: { common: { hello: "Merhaba" } },
10
+ };
11
+ // ─── Tests ──────────────────────────────────────────────────────────
12
+ describe("BetterI18nProviderConfig — fallback fields", () => {
13
+ it("accepts storage field", () => {
14
+ const config = {
15
+ project: "acme/dashboard",
16
+ locale: "en",
17
+ storage: mockStorage,
18
+ };
19
+ expect(config.storage).toBe(mockStorage);
20
+ });
21
+ it("accepts staticData field", () => {
22
+ const config = {
23
+ project: "acme/dashboard",
24
+ locale: "en",
25
+ staticData: mockStaticData,
26
+ };
27
+ expect(config.staticData).toBe(mockStaticData);
28
+ });
29
+ it("accepts fetchTimeout field", () => {
30
+ const config = {
31
+ project: "acme/dashboard",
32
+ locale: "en",
33
+ fetchTimeout: 5000,
34
+ };
35
+ expect(config.fetchTimeout).toBe(5000);
36
+ });
37
+ it("accepts retryCount field", () => {
38
+ const config = {
39
+ project: "acme/dashboard",
40
+ locale: "en",
41
+ retryCount: 3,
42
+ };
43
+ expect(config.retryCount).toBe(3);
44
+ });
45
+ it("accepts all fallback fields together", () => {
46
+ const config = {
47
+ project: "acme/dashboard",
48
+ locale: "en",
49
+ storage: mockStorage,
50
+ staticData: mockStaticData,
51
+ fetchTimeout: 7000,
52
+ retryCount: 2,
53
+ };
54
+ expect(config.storage).toBe(mockStorage);
55
+ expect(config.staticData).toBe(mockStaticData);
56
+ expect(config.fetchTimeout).toBe(7000);
57
+ expect(config.retryCount).toBe(2);
58
+ });
59
+ it("works without any fallback fields (backward compat)", () => {
60
+ const config = {
61
+ project: "acme/dashboard",
62
+ locale: "en",
63
+ };
64
+ expect(config.storage).toBeUndefined();
65
+ expect(config.staticData).toBeUndefined();
66
+ expect(config.fetchTimeout).toBeUndefined();
67
+ expect(config.retryCount).toBeUndefined();
68
+ });
69
+ it("supports lazy staticData function", () => {
70
+ const lazyStaticData = async () => mockStaticData;
71
+ const config = {
72
+ project: "acme/dashboard",
73
+ locale: "en",
74
+ staticData: lazyStaticData,
75
+ };
76
+ expect(config.staticData).toBe(lazyStaticData);
77
+ });
78
+ });
79
+ //# sourceMappingURL=provider-config.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider-config.test.js","sourceRoot":"","sources":["../../src/__tests__/provider-config.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,62 @@
1
+ import { type ReactNode } from "react";
2
+ import type { LanguageOption } from "@better-i18n/core";
3
+ import { type ResolvedFlag } from "@better-i18n/core";
4
+ export interface LocaleDropdownRenderContext {
5
+ language: LanguageOption;
6
+ isActive: boolean;
7
+ flag: ResolvedFlag;
8
+ label: string;
9
+ }
10
+ export interface LocaleDropdownProps {
11
+ /** Display variant. "styled" = inline styles + CSS custom properties, "unstyled" = no styles (data attributes only). @default "styled" */
12
+ variant?: "styled" | "unstyled";
13
+ /** Additional className on the root wrapper. */
14
+ className?: string;
15
+ /** Additional className on the trigger button. */
16
+ triggerClassName?: string;
17
+ /** Additional className on the dropdown menu. */
18
+ menuClassName?: string;
19
+ /** Show flag icon/emoji. @default true */
20
+ showFlag?: boolean;
21
+ /** Show native language name. @default true */
22
+ showNativeName?: boolean;
23
+ /** Show locale code (e.g., "EN"). @default true */
24
+ showLocaleCode?: boolean;
25
+ /** Custom render for the trigger button. */
26
+ renderTrigger?: (ctx: {
27
+ language: LanguageOption | undefined;
28
+ isOpen: boolean;
29
+ isLoading: boolean;
30
+ flag: ResolvedFlag | null;
31
+ label: string;
32
+ }) => ReactNode;
33
+ /** Custom render for each menu item. */
34
+ renderItem?: (ctx: LocaleDropdownRenderContext) => ReactNode;
35
+ }
36
+ /**
37
+ * Styled locale dropdown with flag icons, keyboard navigation, and
38
+ * full CSS customization via custom properties or data attributes.
39
+ *
40
+ * Uses `useLocaleRouter()` and `useLanguages()` internally — works
41
+ * out of the box inside `<BetterI18nProvider>` with TanStack Router.
42
+ *
43
+ * @example
44
+ * ```tsx
45
+ * // Zero config — styled mode
46
+ * <LocaleDropdown />
47
+ *
48
+ * // Headless mode for full custom styling
49
+ * <LocaleDropdown variant="unstyled" className="my-dropdown" />
50
+ *
51
+ * // Custom render
52
+ * <LocaleDropdown
53
+ * renderItem={({ language, isActive, flag, label }) => (
54
+ * <div className={isActive ? "active" : ""}>
55
+ * {flag.type === "emoji" ? flag.emoji : null} {label}
56
+ * </div>
57
+ * )}
58
+ * />
59
+ * ```
60
+ */
61
+ export declare function LocaleDropdown({ variant, className, triggerClassName, menuClassName, showFlag, showNativeName, showLocaleCode, renderTrigger, renderItem, }: LocaleDropdownProps): import("react/jsx-runtime").JSX.Element;
62
+ //# sourceMappingURL=locale-dropdown.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-dropdown.d.ts","sourceRoot":"","sources":["../../src/components/locale-dropdown.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,mBAAmB,CAAC;AA+N3B,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,EAAE,cAAc,CAAC;IACzB,QAAQ,EAAE,OAAO,CAAC;IAClB,IAAI,EAAE,YAAY,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,mBAAmB;IAClC,0IAA0I;IAC1I,OAAO,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;IAChC,gDAAgD;IAChD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kDAAkD;IAClD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iDAAiD;IACjD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,0CAA0C;IAC1C,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,+CAA+C;IAC/C,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,CAAC,GAAG,EAAE;QACpB,QAAQ,EAAE,cAAc,GAAG,SAAS,CAAC;QACrC,MAAM,EAAE,OAAO,CAAC;QAChB,SAAS,EAAE,OAAO,CAAC;QACnB,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;QAC1B,KAAK,EAAE,MAAM,CAAC;KACf,KAAK,SAAS,CAAC;IAChB,wCAAwC;IACxC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,2BAA2B,KAAK,SAAS,CAAC;CAC9D;AAID;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,cAAc,CAAC,EAC7B,OAAkB,EAClB,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,QAAe,EACf,cAAqB,EACrB,cAAqB,EACrB,aAAa,EACb,UAAU,GACX,EAAE,mBAAmB,2CAuRrB"}
@@ -0,0 +1,298 @@
1
+ "use client";
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useCallback, useEffect, useMemo, useRef, useState, } from "react";
4
+ import { getLanguageLabel, resolveFlag, } from "@better-i18n/core";
5
+ import { useLocaleRouter } from "../hooks/useLocaleRouter.js";
6
+ import { useLanguages } from "../hooks.js";
7
+ // ─── Inline SVG Icons ────────────────────────────────────────────────
8
+ function GlobeIcon() {
9
+ return (_jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "square", "aria-hidden": "true", children: [_jsx("path", { d: "M21 12C21 16.9706 16.9706 21 12 21C7.02944 21 3 16.9706 3 12C3 7.02944 7.02944 3 12 3C16.9706 3 21 7.02944 21 12Z" }), _jsx("path", { d: "M21 12H3" }), _jsx("path", { d: "M12 21C10.067 21 8.5 16.9706 8.5 12C8.5 7.02944 10.067 3 12 3C13.933 3 15.5 7.02944 15.5 12C15.5 16.9706 13.933 21 12 21Z" })] }));
10
+ }
11
+ function ChevronDownIcon({ style }) {
12
+ return (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", style: style, children: _jsx("path", { d: "M20 9L13.4142 15.5858C12.6332 16.3668 11.3669 16.3668 10.5858 15.5858L4 9" }) }));
13
+ }
14
+ function CheckIcon() {
15
+ return (_jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: _jsx("path", { d: "M6 12l4.243 4.243L18.485 8" }) }));
16
+ }
17
+ // ─── Flag Component ──────────────────────────────────────────────────
18
+ function FlagDisplay({ flag, label, }) {
19
+ const [hasError, setHasError] = useState(false);
20
+ useEffect(() => {
21
+ setHasError(false);
22
+ }, [flag.type === "url" ? flag.url : null]);
23
+ if (flag.type === "url" && !hasError) {
24
+ return (_jsx("img", { src: flag.url, alt: label, style: { width: 20, height: 16, borderRadius: 2, objectFit: "cover" }, loading: "lazy", onError: () => setHasError(true) }));
25
+ }
26
+ if (flag.type === "emoji" || (flag.type === "url" && hasError)) {
27
+ const emoji = flag.type === "emoji" ? flag.emoji : null;
28
+ if (emoji) {
29
+ return (_jsx("span", { style: {
30
+ display: "inline-flex",
31
+ alignItems: "center",
32
+ justifyContent: "center",
33
+ width: 20,
34
+ height: 16,
35
+ fontSize: 14,
36
+ lineHeight: 1,
37
+ }, "aria-hidden": "true", children: emoji }));
38
+ }
39
+ }
40
+ return (_jsx("span", { style: { color: "var(--better-locale-code-text, #9ca3af)" }, children: _jsx(GlobeIcon, {}) }));
41
+ }
42
+ // ─── Styles ──────────────────────────────────────────────────────────
43
+ const styles = {
44
+ trigger: {
45
+ display: "inline-flex",
46
+ alignItems: "center",
47
+ gap: 8,
48
+ padding: "var(--better-locale-trigger-padding, 6px 10px)",
49
+ borderRadius: "var(--better-locale-trigger-radius, 8px)",
50
+ border: "var(--better-locale-trigger-border, 1px solid transparent)",
51
+ background: "var(--better-locale-trigger-bg, transparent)",
52
+ color: "var(--better-locale-text, #374151)",
53
+ fontSize: 14,
54
+ fontWeight: 500,
55
+ cursor: "pointer",
56
+ transition: "background 0.15s, border-color 0.15s",
57
+ lineHeight: 1,
58
+ fontFamily: "inherit",
59
+ },
60
+ triggerDisabled: {
61
+ opacity: 0.7,
62
+ cursor: "default",
63
+ },
64
+ menu: {
65
+ position: "absolute",
66
+ right: 0,
67
+ top: "calc(100% + 4px)",
68
+ minWidth: 200,
69
+ maxHeight: "70vh",
70
+ overflowY: "auto",
71
+ borderRadius: 12,
72
+ border: "1px solid var(--better-locale-border, #e5e7eb)",
73
+ background: "var(--better-locale-menu-bg, #ffffff)",
74
+ boxShadow: "0 4px 24px rgba(0, 0, 0, 0.12)",
75
+ padding: 0,
76
+ zIndex: 50,
77
+ listStyle: "none",
78
+ margin: 0,
79
+ },
80
+ item: {
81
+ display: "flex",
82
+ alignItems: "center",
83
+ gap: 12,
84
+ width: "100%",
85
+ padding: "8px 14px",
86
+ border: "none",
87
+ background: "transparent",
88
+ color: "var(--better-locale-text, #374151)",
89
+ fontSize: 14,
90
+ cursor: "pointer",
91
+ transition: "background 0.1s",
92
+ textAlign: "left",
93
+ fontFamily: "inherit",
94
+ lineHeight: 1.2,
95
+ },
96
+ itemActive: {
97
+ background: "var(--better-locale-active-bg, #f9fafb)",
98
+ },
99
+ itemHovered: {
100
+ background: "var(--better-locale-hover-bg, #f3f4f6)",
101
+ },
102
+ label: {
103
+ flex: 1,
104
+ textAlign: "left",
105
+ },
106
+ code: {
107
+ fontSize: 10,
108
+ fontFamily: "monospace",
109
+ textTransform: "uppercase",
110
+ color: "var(--better-locale-code-text, #9ca3af)",
111
+ letterSpacing: "0.05em",
112
+ },
113
+ check: {
114
+ color: "var(--better-locale-accent, currentColor)",
115
+ flexShrink: 0,
116
+ },
117
+ skeleton: {
118
+ display: "inline-flex",
119
+ alignItems: "center",
120
+ gap: 8,
121
+ padding: "6px 10px",
122
+ borderRadius: 8,
123
+ border: "1px solid transparent",
124
+ background: "transparent",
125
+ },
126
+ skeletonFlag: {
127
+ width: 20,
128
+ height: 18,
129
+ borderRadius: 3,
130
+ background: "var(--better-locale-border, #e5e7eb)",
131
+ animation: "better-locale-pulse 1.5s ease-in-out infinite",
132
+ flexShrink: 0,
133
+ },
134
+ skeletonText: {
135
+ width: 56,
136
+ height: 18,
137
+ borderRadius: 4,
138
+ background: "var(--better-locale-border, #e5e7eb)",
139
+ animation: "better-locale-pulse 1.5s ease-in-out infinite",
140
+ },
141
+ };
142
+ // ─── Component ───────────────────────────────────────────────────────
143
+ /**
144
+ * Styled locale dropdown with flag icons, keyboard navigation, and
145
+ * full CSS customization via custom properties or data attributes.
146
+ *
147
+ * Uses `useLocaleRouter()` and `useLanguages()` internally — works
148
+ * out of the box inside `<BetterI18nProvider>` with TanStack Router.
149
+ *
150
+ * @example
151
+ * ```tsx
152
+ * // Zero config — styled mode
153
+ * <LocaleDropdown />
154
+ *
155
+ * // Headless mode for full custom styling
156
+ * <LocaleDropdown variant="unstyled" className="my-dropdown" />
157
+ *
158
+ * // Custom render
159
+ * <LocaleDropdown
160
+ * renderItem={({ language, isActive, flag, label }) => (
161
+ * <div className={isActive ? "active" : ""}>
162
+ * {flag.type === "emoji" ? flag.emoji : null} {label}
163
+ * </div>
164
+ * )}
165
+ * />
166
+ * ```
167
+ */
168
+ export function LocaleDropdown({ variant = "styled", className, triggerClassName, menuClassName, showFlag = true, showNativeName = true, showLocaleCode = true, renderTrigger, renderItem, }) {
169
+ const { locale, navigate, isReady } = useLocaleRouter();
170
+ const { languages, isLoading } = useLanguages();
171
+ const [isOpen, setIsOpen] = useState(false);
172
+ const [focusIndex, setFocusIndex] = useState(-1);
173
+ const containerRef = useRef(null);
174
+ const menuRef = useRef(null);
175
+ const isStyled = variant === "styled";
176
+ const currentLanguage = useMemo(() => languages.find((l) => l.code === locale), [languages, locale]);
177
+ const currentLabel = currentLanguage
178
+ ? getLanguageLabel(currentLanguage)
179
+ : locale.toUpperCase();
180
+ const currentFlag = currentLanguage ? resolveFlag(currentLanguage) : null;
181
+ const canToggle = languages.length > 1;
182
+ // Close on outside click
183
+ useEffect(() => {
184
+ if (!isOpen)
185
+ return;
186
+ function handleClick(e) {
187
+ if (containerRef.current &&
188
+ !containerRef.current.contains(e.target)) {
189
+ setIsOpen(false);
190
+ }
191
+ }
192
+ document.addEventListener("mousedown", handleClick);
193
+ return () => document.removeEventListener("mousedown", handleClick);
194
+ }, [isOpen]);
195
+ // Keyboard navigation
196
+ const handleKeyDown = useCallback((e) => {
197
+ if (!isOpen) {
198
+ if (e.key === "ArrowDown" || e.key === "Enter" || e.key === " ") {
199
+ e.preventDefault();
200
+ setIsOpen(true);
201
+ setFocusIndex(0);
202
+ }
203
+ return;
204
+ }
205
+ switch (e.key) {
206
+ case "Escape":
207
+ e.preventDefault();
208
+ setIsOpen(false);
209
+ setFocusIndex(-1);
210
+ break;
211
+ case "ArrowDown":
212
+ e.preventDefault();
213
+ setFocusIndex((prev) => prev < languages.length - 1 ? prev + 1 : 0);
214
+ break;
215
+ case "ArrowUp":
216
+ e.preventDefault();
217
+ setFocusIndex((prev) => prev > 0 ? prev - 1 : languages.length - 1);
218
+ break;
219
+ case "Enter":
220
+ case " ": {
221
+ e.preventDefault();
222
+ const lang = languages[focusIndex];
223
+ if (lang) {
224
+ navigate(lang.code);
225
+ setIsOpen(false);
226
+ setFocusIndex(-1);
227
+ }
228
+ break;
229
+ }
230
+ case "Home":
231
+ e.preventDefault();
232
+ setFocusIndex(0);
233
+ break;
234
+ case "End":
235
+ e.preventDefault();
236
+ setFocusIndex(languages.length - 1);
237
+ break;
238
+ }
239
+ }, [isOpen, focusIndex, languages, navigate]);
240
+ // Scroll focused item into view
241
+ useEffect(() => {
242
+ if (focusIndex >= 0 && menuRef.current) {
243
+ const items = menuRef.current.querySelectorAll("[data-better-locale-item]");
244
+ items[focusIndex]?.scrollIntoView({ block: "nearest" });
245
+ }
246
+ }, [focusIndex]);
247
+ // Loading skeleton
248
+ if (!isReady || isLoading) {
249
+ if (renderTrigger) {
250
+ return (_jsx("div", { "data-better-locale-dropdown": true, className: className, children: renderTrigger({
251
+ language: undefined,
252
+ isOpen: false,
253
+ isLoading: true,
254
+ flag: null,
255
+ label: "",
256
+ }) }));
257
+ }
258
+ return (_jsxs("div", { "data-better-locale-dropdown": true, className: className, children: [_jsxs("div", { "aria-busy": "true", "data-better-locale-trigger": true, className: triggerClassName, style: isStyled ? styles.skeleton : undefined, children: [_jsx("span", { style: isStyled ? styles.skeletonFlag : undefined }), _jsx("span", { style: isStyled ? styles.skeletonText : undefined })] }), isStyled && (_jsx("style", { children: `@keyframes better-locale-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }` }))] }));
259
+ }
260
+ return (_jsxs("div", { ref: containerRef, "data-better-locale-dropdown": true, className: className, style: { position: "relative", display: "inline-block" }, children: [renderTrigger ? (_jsx("div", { onClick: () => canToggle && setIsOpen(!isOpen), onKeyDown: handleKeyDown, role: "button", tabIndex: 0, "aria-haspopup": "listbox", "aria-expanded": isOpen, "data-better-locale-trigger": true, className: triggerClassName, children: renderTrigger({
261
+ language: currentLanguage,
262
+ isOpen,
263
+ isLoading: false,
264
+ flag: currentFlag,
265
+ label: currentLabel,
266
+ }) })) : (_jsxs("button", { type: "button", onClick: () => canToggle && setIsOpen(!isOpen), onKeyDown: handleKeyDown, "aria-haspopup": "listbox", "aria-expanded": isOpen, "aria-label": "Select language", disabled: !canToggle, "data-better-locale-trigger": true, className: triggerClassName, style: isStyled
267
+ ? {
268
+ ...styles.trigger,
269
+ ...(!canToggle ? styles.triggerDisabled : {}),
270
+ }
271
+ : undefined, children: [showFlag && currentFlag && (_jsx(FlagDisplay, { flag: currentFlag, label: currentLabel })), showNativeName && _jsx("span", { children: currentLabel }), canToggle && (_jsx(ChevronDownIcon, { style: {
272
+ transition: "transform 0.2s",
273
+ transform: isOpen ? "rotate(180deg)" : "rotate(0deg)",
274
+ color: "var(--better-locale-code-text, #9ca3af)",
275
+ } }))] })), isOpen && canToggle && (_jsx("ul", { ref: menuRef, role: "listbox", "aria-label": "Available languages", "data-better-locale-menu": true, className: menuClassName, style: isStyled ? styles.menu : undefined, children: languages.map((language, index) => {
276
+ const label = getLanguageLabel(language);
277
+ const isActive = language.code === locale;
278
+ const isFocused = index === focusIndex;
279
+ const flag = resolveFlag(language);
280
+ if (renderItem) {
281
+ return (_jsx("li", { role: "option", "aria-selected": isActive, "data-better-locale-item": true, "data-active": isActive || undefined, "data-focused": isFocused || undefined, onClick: () => {
282
+ navigate(language.code);
283
+ setIsOpen(false);
284
+ }, onMouseEnter: () => setFocusIndex(index), style: { cursor: "pointer" }, children: renderItem({ language, isActive, flag, label }) }, language.code));
285
+ }
286
+ return (_jsxs("li", { role: "option", "aria-selected": isActive, "data-better-locale-item": true, "data-active": isActive || undefined, "data-focused": isFocused || undefined, onClick: () => {
287
+ navigate(language.code);
288
+ setIsOpen(false);
289
+ }, onMouseEnter: () => setFocusIndex(index), style: isStyled
290
+ ? {
291
+ ...styles.item,
292
+ ...(isActive ? styles.itemActive : {}),
293
+ ...(isFocused ? styles.itemHovered : {}),
294
+ }
295
+ : undefined, children: [showFlag && _jsx(FlagDisplay, { flag: flag, label: label }), _jsx("span", { style: isStyled ? styles.label : undefined, children: showNativeName ? label : language.code.toUpperCase() }), showLocaleCode && (_jsx("span", { style: isStyled ? styles.code : undefined, children: language.code })), isActive && (_jsx("span", { style: isStyled ? styles.check : undefined, children: _jsx(CheckIcon, {}) }))] }, language.code));
296
+ }) })), isStyled && isOpen && (_jsx("style", { children: `@keyframes better-locale-pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.5; } }` }))] }));
297
+ }
298
+ //# sourceMappingURL=locale-dropdown.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"locale-dropdown.js","sourceRoot":"","sources":["../../src/components/locale-dropdown.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,44 @@
1
+ import type { ComponentProps, ReactNode } from "react";
2
+ export interface LanguageSwitcherProps extends Omit<ComponentProps<"select">, "value" | "onChange" | "children"> {
3
+ /**
4
+ * Render function for custom option display
5
+ */
6
+ renderOption?: (language: {
7
+ code: string;
8
+ name?: string;
9
+ nativeName?: string;
10
+ flagUrl?: string | null;
11
+ isDefault?: boolean;
12
+ }) => ReactNode;
13
+ /**
14
+ * Label for loading state
15
+ */
16
+ loadingLabel?: string;
17
+ }
18
+ /**
19
+ * Pre-built language switcher component with router integration
20
+ *
21
+ * Uses useLocaleRouter() internally for proper SPA navigation.
22
+ * Locale changes trigger router navigation, which re-executes loaders.
23
+ *
24
+ * @example
25
+ * ```tsx
26
+ * // Basic usage - just works!
27
+ * <LanguageSwitcher />
28
+ *
29
+ * // With custom styling
30
+ * <LanguageSwitcher className="my-select" />
31
+ *
32
+ * // Custom option rendering
33
+ * <LanguageSwitcher
34
+ * renderOption={(lang) => (
35
+ * <>
36
+ * {lang.flagUrl && <img src={lang.flagUrl} alt="" />}
37
+ * {lang.nativeName}
38
+ * </>
39
+ * )}
40
+ * />
41
+ * ```
42
+ */
43
+ export declare function LanguageSwitcher({ renderOption, loadingLabel, ...props }: LanguageSwitcherProps): import("react/jsx-runtime").JSX.Element;
44
+ //# sourceMappingURL=components.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.d.ts","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIvD,MAAM,WAAW,qBACf,SAAQ,IAAI,CAAC,cAAc,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,UAAU,GAAG,UAAU,CAAC;IACzE;;OAEG;IACH,YAAY,CAAC,EAAE,CAAC,QAAQ,EAAE;QACxB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,UAAU,CAAC,EAAE,MAAM,CAAC;QACpB,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;QACxB,SAAS,CAAC,EAAE,OAAO,CAAC;KACrB,KAAK,SAAS,CAAC;IAEhB;;OAEG;IACH,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,YAAY,EACZ,YAA2B,EAC3B,GAAG,KAAK,EACT,EAAE,qBAAqB,2CAqBvB"}
@@ -0,0 +1,38 @@
1
+ "use client";
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { useLocaleRouter } from "./hooks/useLocaleRouter.js";
4
+ import { useLanguages } from "./hooks.js";
5
+ /**
6
+ * Pre-built language switcher component with router integration
7
+ *
8
+ * Uses useLocaleRouter() internally for proper SPA navigation.
9
+ * Locale changes trigger router navigation, which re-executes loaders.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * // Basic usage - just works!
14
+ * <LanguageSwitcher />
15
+ *
16
+ * // With custom styling
17
+ * <LanguageSwitcher className="my-select" />
18
+ *
19
+ * // Custom option rendering
20
+ * <LanguageSwitcher
21
+ * renderOption={(lang) => (
22
+ * <>
23
+ * {lang.flagUrl && <img src={lang.flagUrl} alt="" />}
24
+ * {lang.nativeName}
25
+ * </>
26
+ * )}
27
+ * />
28
+ * ```
29
+ */
30
+ export function LanguageSwitcher({ renderOption, loadingLabel = "Loading...", ...props }) {
31
+ const { locale, navigate, isReady } = useLocaleRouter();
32
+ const { languages } = useLanguages();
33
+ if (!isReady) {
34
+ return (_jsx("select", { disabled: true, ...props, children: _jsx("option", { children: loadingLabel }) }));
35
+ }
36
+ return (_jsx("select", { value: locale, onChange: (e) => navigate(e.target.value), ...props, children: languages.map((lang) => (_jsx("option", { value: lang.code, children: renderOption ? renderOption(lang) : lang.nativeName || lang.code }, lang.code))) }));
37
+ }
38
+ //# sourceMappingURL=components.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"components.js","sourceRoot":"","sources":["../src/components.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAGb,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAqB1C;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,MAAM,UAAU,gBAAgB,CAAC,EAC/B,YAAY,EACZ,YAAY,GAAG,YAAY,EAC3B,GAAG,KAAK,EACc;IACtB,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,eAAe,EAAE,CAAC;IACxD,MAAM,EAAE,SAAS,EAAE,GAAG,YAAY,EAAE,CAAC;IAErC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO,CACL,iBAAQ,QAAQ,WAAK,KAAK,YACxB,2BAAS,YAAY,GAAU,GACxB,CACV,CAAC;IACJ,CAAC;IAED,OAAO,CACL,iBAAQ,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,KAAM,KAAK,YACxE,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CACvB,iBAAwB,KAAK,EAAE,IAAI,CAAC,IAAI,YACrC,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,UAAU,IAAI,IAAI,CAAC,IAAI,IADtD,IAAI,CAAC,IAAI,CAEb,CACV,CAAC,GACK,CACV,CAAC;AACJ,CAAC"}
@@ -0,0 +1,43 @@
1
+ import type { BetterI18nContextValue } from "./types.js";
2
+ /**
3
+ * Context for Better i18n specific state
4
+ */
5
+ export declare const BetterI18nContext: import("react").Context<BetterI18nContextValue | null>;
6
+ /**
7
+ * Hook to access Better i18n context
8
+ *
9
+ * Note: For locale switching, use useLocaleRouter() which integrates with TanStack Router.
10
+ *
11
+ * @example
12
+ * ```tsx
13
+ * function LanguageInfo() {
14
+ * const { locale, languages, isLoadingLanguages } = useBetterI18n()
15
+ *
16
+ * if (isLoadingLanguages) return <div>Loading...</div>
17
+ *
18
+ * return (
19
+ * <div>
20
+ * Current: {locale}
21
+ * Available: {languages.map(l => l.code).join(', ')}
22
+ * </div>
23
+ * )
24
+ * }
25
+ * ```
26
+ *
27
+ * @example
28
+ * ```tsx
29
+ * // For language switching with proper router navigation:
30
+ * import { useLocaleRouter } from '@better-i18n/use-intl'
31
+ *
32
+ * function LanguageSwitcher() {
33
+ * const { locale, locales, navigate } = useLocaleRouter()
34
+ * return (
35
+ * <select value={locale} onChange={(e) => navigate(e.target.value)}>
36
+ * {locales.map((loc) => <option key={loc} value={loc}>{loc}</option>)}
37
+ * </select>
38
+ * )
39
+ * }
40
+ * ```
41
+ */
42
+ export declare function useBetterI18n(): BetterI18nContextValue;
43
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,YAAY,CAAC;AAEzD;;GAEG;AACH,eAAO,MAAM,iBAAiB,wDAE7B,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,wBAAgB,aAAa,IAAI,sBAAsB,CAUtD"}