@better-i18n/use-intl 0.1.6 → 0.1.8

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@better-i18n/use-intl",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "description": "Better i18n integration for use-intl (React, TanStack Start)",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -44,14 +44,21 @@
44
44
  "clean": "rm -rf dist"
45
45
  },
46
46
  "dependencies": {
47
- "@better-i18n/core": "0.1.6"
47
+ "@better-i18n/core": "0.1.8"
48
48
  },
49
49
  "peerDependencies": {
50
50
  "react": ">=18.0.0",
51
- "use-intl": ">=4.0.0"
51
+ "use-intl": ">=4.0.0",
52
+ "@tanstack/react-router": ">=1.0.0"
53
+ },
54
+ "peerDependenciesMeta": {
55
+ "@tanstack/react-router": {
56
+ "optional": true
57
+ }
52
58
  },
53
59
  "devDependencies": {
54
60
  "@types/react": "^19.0.0",
61
+ "@tanstack/react-router": "^1.120.3",
55
62
  "react": "^19.0.0",
56
63
  "use-intl": "^4.7.0",
57
64
  "typescript": "~5.9.2"
@@ -1,12 +1,11 @@
1
1
  "use client";
2
2
 
3
3
  import type { ComponentProps, ReactNode } from "react";
4
- import { useBetterI18n } from "./context";
4
+ import { useLocaleRouter } from "./hooks/useLocaleRouter";
5
+ import { useLanguages } from "./hooks";
5
6
 
6
- export interface LanguageSwitcherProps extends Omit<
7
- ComponentProps<"select">,
8
- "value" | "onChange" | "children"
9
- > {
7
+ export interface LanguageSwitcherProps
8
+ extends Omit<ComponentProps<"select">, "value" | "onChange" | "children"> {
10
9
  /**
11
10
  * Render function for custom option display
12
11
  */
@@ -15,6 +14,7 @@ export interface LanguageSwitcherProps extends Omit<
15
14
  name?: string;
16
15
  nativeName?: string;
17
16
  flagUrl?: string | null;
17
+ isDefault?: boolean;
18
18
  }) => ReactNode;
19
19
 
20
20
  /**
@@ -24,11 +24,14 @@ export interface LanguageSwitcherProps extends Omit<
24
24
  }
25
25
 
26
26
  /**
27
- * Pre-built language switcher component
27
+ * Pre-built language switcher component with router integration
28
+ *
29
+ * Uses useLocaleRouter() internally for proper SPA navigation.
30
+ * Locale changes trigger router navigation, which re-executes loaders.
28
31
  *
29
32
  * @example
30
33
  * ```tsx
31
- * // Basic usage
34
+ * // Basic usage - just works!
32
35
  * <LanguageSwitcher />
33
36
  *
34
37
  * // With custom styling
@@ -50,9 +53,10 @@ export function LanguageSwitcher({
50
53
  loadingLabel = "Loading...",
51
54
  ...props
52
55
  }: LanguageSwitcherProps) {
53
- const { locale, setLocale, languages, isLoadingLanguages } = useBetterI18n();
56
+ const { locale, navigate, isReady } = useLocaleRouter();
57
+ const { languages } = useLanguages();
54
58
 
55
- if (isLoadingLanguages) {
59
+ if (!isReady) {
56
60
  return (
57
61
  <select disabled {...props}>
58
62
  <option>{loadingLabel}</option>
@@ -61,11 +65,7 @@ export function LanguageSwitcher({
61
65
  }
62
66
 
63
67
  return (
64
- <select
65
- value={locale}
66
- onChange={(e) => setLocale(e.target.value)}
67
- {...props}
68
- >
68
+ <select value={locale} onChange={(e) => navigate(e.target.value)} {...props}>
69
69
  {languages.map((lang) => (
70
70
  <option key={lang.code} value={lang.code}>
71
71
  {renderOption ? renderOption(lang) : lang.nativeName || lang.code}
package/src/context.tsx CHANGED
@@ -13,18 +13,34 @@ export const BetterI18nContext = createContext<BetterI18nContextValue | null>(
13
13
  /**
14
14
  * Hook to access Better i18n context
15
15
  *
16
+ * Note: For locale switching, use useLocaleRouter() which integrates with TanStack Router.
17
+ *
16
18
  * @example
17
19
  * ```tsx
18
- * function LanguageSwitcher() {
19
- * const { locale, setLocale, languages } = useBetterI18n()
20
+ * function LanguageInfo() {
21
+ * const { locale, languages, isLoadingLanguages } = useBetterI18n()
22
+ *
23
+ * if (isLoadingLanguages) return <div>Loading...</div>
24
+ *
25
+ * return (
26
+ * <div>
27
+ * Current: {locale}
28
+ * Available: {languages.map(l => l.code).join(', ')}
29
+ * </div>
30
+ * )
31
+ * }
32
+ * ```
33
+ *
34
+ * @example
35
+ * ```tsx
36
+ * // For language switching with proper router navigation:
37
+ * import { useLocaleRouter } from '@better-i18n/use-intl'
20
38
  *
39
+ * function LanguageSwitcher() {
40
+ * const { locale, locales, navigate } = useLocaleRouter()
21
41
  * return (
22
- * <select value={locale} onChange={(e) => setLocale(e.target.value)}>
23
- * {languages.map((lang) => (
24
- * <option key={lang.code} value={lang.code}>
25
- * {lang.nativeName}
26
- * </option>
27
- * ))}
42
+ * <select value={locale} onChange={(e) => navigate(e.target.value)}>
43
+ * {locales.map((loc) => <option key={loc} value={loc}>{loc}</option>)}
28
44
  * </select>
29
45
  * )
30
46
  * }
@@ -0,0 +1,147 @@
1
+ "use client";
2
+
3
+ import { useCallback, useMemo } from "react";
4
+ import { useRouter, useLocation } from "@tanstack/react-router";
5
+ import {
6
+ getLocaleFromPath,
7
+ replaceLocaleInPath,
8
+ addLocalePrefix,
9
+ type LocaleConfig,
10
+ } from "@better-i18n/core";
11
+ import { useBetterI18n } from "../context";
12
+
13
+ /**
14
+ * Return type for useLocaleRouter hook
15
+ */
16
+ export interface UseLocaleRouterReturn {
17
+ /**
18
+ * Current locale (extracted from URL or default)
19
+ */
20
+ locale: string;
21
+
22
+ /**
23
+ * Available locales from CDN manifest
24
+ */
25
+ locales: string[];
26
+
27
+ /**
28
+ * Default locale (no URL prefix)
29
+ */
30
+ defaultLocale: string;
31
+
32
+ /**
33
+ * Navigate to the same page with a new locale
34
+ * Uses TanStack Router's navigate() for proper SPA navigation
35
+ */
36
+ navigate: (locale: string) => void;
37
+
38
+ /**
39
+ * Get a localized path for link building
40
+ * @param path - The path to localize
41
+ * @param locale - Target locale (optional, uses current if not specified)
42
+ */
43
+ localePath: (path: string, locale?: string) => string;
44
+
45
+ /**
46
+ * Whether languages have been loaded from CDN
47
+ */
48
+ isReady: boolean;
49
+ }
50
+
51
+ /**
52
+ * Hook for router-integrated locale navigation
53
+ *
54
+ * This hook provides a navigation-first approach to locale switching:
55
+ * - Locale changes trigger proper router navigation
56
+ * - Loaders re-execute with the new locale
57
+ * - No state synchronization issues
58
+ * - Works with TanStack Router's file-based routing
59
+ *
60
+ * @example
61
+ * ```tsx
62
+ * function LanguageSwitcher() {
63
+ * const { locale, locales, navigate, isReady } = useLocaleRouter();
64
+ *
65
+ * if (!isReady) return <Skeleton />;
66
+ *
67
+ * return (
68
+ * <select value={locale} onChange={(e) => navigate(e.target.value)}>
69
+ * {locales.map((loc) => (
70
+ * <option key={loc} value={loc}>{loc}</option>
71
+ * ))}
72
+ * </select>
73
+ * );
74
+ * }
75
+ * ```
76
+ *
77
+ * @example
78
+ * ```tsx
79
+ * // Building localized links
80
+ * function Navigation() {
81
+ * const { localePath } = useLocaleRouter();
82
+ *
83
+ * return (
84
+ * <nav>
85
+ * <Link to={localePath('/about')}>About</Link>
86
+ * <Link to={localePath('/contact', 'tr')}>İletişim (TR)</Link>
87
+ * </nav>
88
+ * );
89
+ * }
90
+ * ```
91
+ */
92
+ export function useLocaleRouter(): UseLocaleRouterReturn {
93
+ const router = useRouter();
94
+ const location = useLocation();
95
+ const { languages, isLoadingLanguages } = useBetterI18n();
96
+
97
+ // Build config from CDN manifest
98
+ const config: LocaleConfig = useMemo(
99
+ () => ({
100
+ locales: languages.map((lang) => lang.code),
101
+ defaultLocale: languages.find((l) => l.isDefault)?.code || "en",
102
+ }),
103
+ [languages]
104
+ );
105
+
106
+ // Get effective locale from URL (handles default without prefix)
107
+ const locale = useMemo(() => {
108
+ // If no languages loaded yet, extract from path manually
109
+ if (languages.length === 0) {
110
+ const segments = location.pathname.split("/").filter(Boolean);
111
+ const firstSegment = segments[0];
112
+ // Check if it looks like a locale code (2 letters)
113
+ if (firstSegment && /^[a-z]{2}$/i.test(firstSegment)) {
114
+ return firstSegment;
115
+ }
116
+ return "en"; // fallback
117
+ }
118
+ return getLocaleFromPath(location.pathname, config);
119
+ }, [location.pathname, config, languages]);
120
+
121
+ // Navigate to same page with new locale (SPA navigation!)
122
+ const navigate = useCallback(
123
+ (newLocale: string) => {
124
+ const newPath = replaceLocaleInPath(location.pathname, newLocale, config);
125
+ router.navigate({ to: newPath });
126
+ },
127
+ [location.pathname, config, router]
128
+ );
129
+
130
+ // Get localized path for links
131
+ const localePath = useCallback(
132
+ (path: string, targetLocale?: string) => {
133
+ const loc = targetLocale || locale;
134
+ return addLocalePrefix(path, loc, config);
135
+ },
136
+ [locale, config]
137
+ );
138
+
139
+ return {
140
+ locale,
141
+ locales: config.locales,
142
+ defaultLocale: config.defaultLocale,
143
+ navigate,
144
+ localePath,
145
+ isReady: !isLoadingLanguages && config.locales.length > 0,
146
+ };
147
+ }
package/src/hooks.ts CHANGED
@@ -3,7 +3,7 @@
3
3
  import { useBetterI18n } from "./context";
4
4
 
5
5
  /**
6
- * Hook to get available languages
6
+ * Hook to get available languages from CDN manifest
7
7
  *
8
8
  * @example
9
9
  * ```tsx
@@ -34,27 +34,41 @@ export function useLanguages() {
34
34
  }
35
35
 
36
36
  /**
37
- * Hook to get and set current locale
37
+ * Hook to get current locale (read-only)
38
+ *
39
+ * For locale switching with proper router navigation, use useLocaleRouter() instead.
38
40
  *
39
41
  * @example
40
42
  * ```tsx
41
- * function LocaleSwitcher() {
42
- * const { locale, setLocale } = useLocale()
43
+ * function LocaleDisplay() {
44
+ * const { locale, isLoading } = useLocale()
45
+ *
46
+ * if (isLoading) return <div>Loading...</div>
43
47
  *
48
+ * return <span>Current locale: {locale}</span>
49
+ * }
50
+ * ```
51
+ *
52
+ * @example
53
+ * ```tsx
54
+ * // For locale switching, use useLocaleRouter:
55
+ * import { useLocaleRouter } from '@better-i18n/use-intl'
56
+ *
57
+ * function LocaleSwitcher() {
58
+ * const { locale, navigate } = useLocaleRouter()
44
59
  * return (
45
- * <button onClick={() => setLocale(locale === 'en' ? 'tr' : 'en')}>
46
- * Current: {locale}
60
+ * <button onClick={() => navigate(locale === 'en' ? 'tr' : 'en')}>
61
+ * Toggle: {locale}
47
62
  * </button>
48
63
  * )
49
64
  * }
50
65
  * ```
51
66
  */
52
67
  export function useLocale() {
53
- const { locale, setLocale, isLoadingMessages } = useBetterI18n();
68
+ const { locale, isLoadingMessages } = useBetterI18n();
54
69
 
55
70
  return {
56
71
  locale,
57
- setLocale,
58
72
  isLoading: isLoadingMessages,
59
73
  };
60
74
  }
package/src/index.ts CHANGED
@@ -15,6 +15,10 @@ export {
15
15
  useTimeZone,
16
16
  } from "./hooks";
17
17
 
18
+ // Router Integration (TanStack Router)
19
+ export { useLocaleRouter } from "./hooks/useLocaleRouter";
20
+ export type { UseLocaleRouterReturn } from "./hooks/useLocaleRouter";
21
+
18
22
  // Components
19
23
  export { LanguageSwitcher } from "./components";
20
24
  export type { LanguageSwitcherProps } from "./components";
@@ -26,5 +30,17 @@ export type {
26
30
  BetterI18nContextValue,
27
31
  } from "./types";
28
32
 
33
+ // Re-export locale utilities from core (convenience)
34
+ export {
35
+ extractLocale,
36
+ getLocaleFromPath,
37
+ hasLocalePrefix,
38
+ removeLocalePrefix,
39
+ addLocalePrefix,
40
+ replaceLocaleInPath,
41
+ createLocalePath,
42
+ type LocaleConfig,
43
+ } from "@better-i18n/core";
44
+
29
45
  // Re-export commonly used use-intl components
30
46
  export { IntlProvider } from "use-intl";
@@ -1,12 +1,11 @@
1
- // @ts-ignore - internal workspace dependency
1
+ // @ts-expect-error - internal workspace dependency
2
2
  import { createMiddleware } from "@tanstack/react-router";
3
- // @ts-ignore - internal workspace dependency
3
+ // @ts-expect-error - internal workspace dependency
4
4
  import { detectLocale, getLocales } from "@better-i18n/core";
5
- // @ts-ignore - internal workspace dependency
6
5
  import type { I18nMiddlewareConfig } from "@better-i18n/core";
7
6
 
8
7
  export function createBetterI18nMiddleware(config: I18nMiddlewareConfig) {
9
- const { project, defaultLocale, detection = {} } = config;
8
+ const { project, defaultLocale, localePrefix = "as-needed", detection = {} } = config;
10
9
 
11
10
  const {
12
11
  cookie = true,
@@ -16,19 +15,37 @@ export function createBetterI18nMiddleware(config: I18nMiddlewareConfig) {
16
15
  } = detection;
17
16
 
18
17
  return createMiddleware().server(
19
- async ({ next, request }: { next: any; request: any }) => {
18
+ async ({
19
+ next,
20
+ request,
21
+ }: {
22
+ next: (ctx: { context: { locale: string } }) => Promise<unknown>;
23
+ request: Request;
24
+ }) => {
20
25
  // 1. Fetch available locales from CDN (cached)
21
26
  const availableLocales = await getLocales({ project });
22
27
 
23
28
  // 2. Extract locale indicators
24
29
  const url = new URL(request.url);
25
- const pathLocale = url.pathname.split("/")[1];
30
+ const pathSegment = url.pathname.split("/")[1];
31
+ const hasLocaleInPath =
32
+ !!pathSegment && availableLocales.includes(pathSegment);
26
33
 
27
34
  // Dynamic imports for TanStack Start server functions to avoid bundling them in client
28
- // @ts-ignore - internal workspace dependency
29
- const { getCookie, setCookie, getRequestHeader } =
30
- // @ts-ignore - dynamic import type safety
31
- await import("@tanstack/react-start/server");
35
+ const {
36
+ getCookie,
37
+ setCookie,
38
+ getRequestHeader,
39
+ }: {
40
+ getCookie: (name: string) => string | null;
41
+ setCookie: (
42
+ name: string,
43
+ value: string,
44
+ options: { path: string; maxAge: number; sameSite: string },
45
+ ) => void;
46
+ getRequestHeader: (name: string) => string | undefined;
47
+ // @ts-expect-error - optional runtime dependency
48
+ } = await import("@tanstack/react-start/server");
32
49
 
33
50
  const cookieLocale = cookie ? getCookie(cookieName) : null;
34
51
  const headerLocale = browserLanguage
@@ -39,13 +56,49 @@ export function createBetterI18nMiddleware(config: I18nMiddlewareConfig) {
39
56
  const result = detectLocale({
40
57
  project,
41
58
  defaultLocale,
42
- pathLocale,
59
+ pathLocale: pathSegment,
43
60
  cookieLocale,
44
61
  headerLocale,
45
62
  availableLocales,
46
63
  });
47
64
 
48
- // 4. Set cookie if needed (if enabled and changed)
65
+ // 4. Redirect if locale prefix is needed but missing
66
+ // Skip API routes and paths that already have a locale prefix
67
+ const isApiRoute = url.pathname.startsWith("/api/");
68
+ if (
69
+ localePrefix !== "never" &&
70
+ !hasLocaleInPath &&
71
+ !isApiRoute &&
72
+ result.detectedFrom !== "path"
73
+ ) {
74
+ const shouldRedirect =
75
+ localePrefix === "always" ||
76
+ (localePrefix === "as-needed" && result.locale !== defaultLocale);
77
+
78
+ if (shouldRedirect) {
79
+ const redirectUrl = new URL(
80
+ `/${result.locale}${url.pathname}`,
81
+ url.origin,
82
+ );
83
+ redirectUrl.search = url.search;
84
+
85
+ // Build redirect response with locale cookie
86
+ const headers = new Headers({
87
+ Location: redirectUrl.toString(),
88
+ });
89
+
90
+ if (cookie && result.shouldSetCookie) {
91
+ headers.set(
92
+ "Set-Cookie",
93
+ `${cookieName}=${result.locale}; Path=/; Max-Age=${cookieMaxAge}; SameSite=Lax`,
94
+ );
95
+ }
96
+
97
+ return new Response(null, { status: 302, headers });
98
+ }
99
+ }
100
+
101
+ // 5. Set cookie if needed (non-redirect path)
49
102
  if (cookie && result.shouldSetCookie) {
50
103
  setCookie(cookieName, result.locale, {
51
104
  path: "/",
@@ -54,7 +107,7 @@ export function createBetterI18nMiddleware(config: I18nMiddlewareConfig) {
54
107
  });
55
108
  }
56
109
 
57
- // 5. Pass locale to route context
110
+ // 6. Pass locale to route context
58
111
  return next({ context: { locale: result.locale } });
59
112
  },
60
113
  );
package/src/provider.tsx CHANGED
@@ -2,13 +2,7 @@
2
2
 
3
3
  import { createI18nCore } from "@better-i18n/core";
4
4
  import type { LanguageOption } from "@better-i18n/core";
5
- import {
6
- useCallback,
7
- useEffect,
8
- useMemo,
9
- useState,
10
- type ReactNode,
11
- } from "react";
5
+ import { useEffect, useMemo, useState, type ReactNode } from "react";
12
6
  import { IntlProvider } from "use-intl";
13
7
  import { BetterI18nContext } from "./context.js";
14
8
  import type { BetterI18nProviderConfig, Messages } from "./types.js";
@@ -20,6 +14,9 @@ export interface BetterI18nProviderProps extends BetterI18nProviderConfig {
20
14
  /**
21
15
  * Provider component that combines Better i18n CDN with use-intl
22
16
  *
17
+ * The locale is controlled externally (from URL/router). Use useLocaleRouter()
18
+ * for locale switching with proper router integration.
19
+ *
23
20
  * @example
24
21
  * ```tsx
25
22
  * // Basic usage (CSR - fetches messages on client)
@@ -34,15 +31,16 @@ export interface BetterI18nProviderProps extends BetterI18nProviderConfig {
34
31
  * )
35
32
  * }
36
33
  *
37
- * // SSR usage (pre-loaded messages)
38
- * function App({ locale, messages }) {
34
+ * // TanStack Router SSR usage (pre-loaded messages from loader)
35
+ * function RootComponent() {
36
+ * const { messages, locale } = Route.useLoaderData()
39
37
  * return (
40
38
  * <BetterI18nProvider
41
39
  * project="acme/dashboard"
42
40
  * locale={locale}
43
41
  * messages={messages}
44
42
  * >
45
- * <MyComponent />
43
+ * <Outlet />
46
44
  * </BetterI18nProvider>
47
45
  * )
48
46
  * }
@@ -51,37 +49,43 @@ export interface BetterI18nProviderProps extends BetterI18nProviderConfig {
51
49
  export function BetterI18nProvider({
52
50
  children,
53
51
  project,
54
- locale: initialLocale,
55
- messages: initialMessages,
52
+ locale: propLocale,
53
+ messages: propMessages,
56
54
  timeZone,
57
55
  now,
58
- onLocaleChange,
59
56
  onError,
60
57
  cdnBaseUrl,
61
58
  debug,
62
59
  logLevel,
63
60
  fetch: customFetch,
64
61
  }: BetterI18nProviderProps) {
65
- const [locale, setLocaleState] = useState(initialLocale);
66
- const [messages, setMessages] = useState<Messages | undefined>(
67
- initialMessages,
68
- );
62
+ // Locale is controlled by props (from URL/router)
63
+ const locale = propLocale;
64
+ const [messages, setMessages] = useState<Messages | undefined>(propMessages);
69
65
  const [languages, setLanguages] = useState<LanguageOption[]>([]);
70
- const [isLoadingMessages, setIsLoadingMessages] = useState(!initialMessages);
66
+ const [isLoadingMessages, setIsLoadingMessages] = useState(!propMessages);
71
67
  const [isLoadingLanguages, setIsLoadingLanguages] = useState(true);
72
68
 
69
+ // Sync messages when props change (e.g., from router navigation with new loader data)
70
+ useEffect(() => {
71
+ if (propMessages) {
72
+ setMessages(propMessages);
73
+ setIsLoadingMessages(false);
74
+ }
75
+ }, [propMessages]);
76
+
73
77
  // Create i18n core instance
74
78
  const i18nCore = useMemo(
75
79
  () =>
76
80
  createI18nCore({
77
81
  project,
78
- defaultLocale: initialLocale,
82
+ defaultLocale: locale,
79
83
  cdnBaseUrl,
80
84
  debug,
81
85
  logLevel,
82
86
  fetch: customFetch,
83
87
  }),
84
- [project, initialLocale, cdnBaseUrl, debug, logLevel, customFetch],
88
+ [project, locale, cdnBaseUrl, debug, logLevel, customFetch]
85
89
  );
86
90
 
87
91
  // Load languages on mount
@@ -110,10 +114,10 @@ export function BetterI18nProvider({
110
114
  };
111
115
  }, [i18nCore]);
112
116
 
113
- // Load messages when locale changes (if not pre-loaded)
117
+ // Load messages when locale changes and no pre-loaded messages available
114
118
  useEffect(() => {
115
- // Skip if we have initial messages for the current locale
116
- if (initialMessages && locale === initialLocale) {
119
+ // Skip if we already have messages for this render
120
+ if (propMessages) {
117
121
  return;
118
122
  }
119
123
 
@@ -130,7 +134,7 @@ export function BetterI18nProvider({
130
134
  } catch (error) {
131
135
  console.error(
132
136
  `[better-i18n] Failed to load messages for locale "${locale}":`,
133
- error,
137
+ error
134
138
  );
135
139
  } finally {
136
140
  if (!cancelled) {
@@ -144,35 +148,18 @@ export function BetterI18nProvider({
144
148
  return () => {
145
149
  cancelled = true;
146
150
  };
147
- }, [locale, i18nCore, initialMessages, initialLocale]);
148
-
149
- // Locale change handler
150
- const setLocale = useCallback(
151
- (newLocale: string) => {
152
- setLocaleState(newLocale);
153
- onLocaleChange?.(newLocale);
154
- },
155
- [onLocaleChange],
156
- );
151
+ }, [locale, i18nCore, propMessages]);
157
152
 
158
- // Context value
153
+ // Context value (read-only locale - use useLocaleRouter for navigation)
159
154
  const contextValue = useMemo(
160
155
  () => ({
161
156
  locale,
162
- setLocale,
163
157
  languages,
164
158
  isLoadingLanguages,
165
159
  isLoadingMessages,
166
160
  project,
167
161
  }),
168
- [
169
- locale,
170
- setLocale,
171
- languages,
172
- isLoadingLanguages,
173
- isLoadingMessages,
174
- project,
175
- ],
162
+ [locale, languages, isLoadingLanguages, isLoadingMessages, project]
176
163
  );
177
164
 
178
165
  // Don't render until we have messages
package/src/server.ts CHANGED
@@ -28,7 +28,7 @@ export async function getMessages(
28
28
  fetch: config.fetch,
29
29
  });
30
30
 
31
- const messages = (await i18n.getMessages(config.locale)) as any;
31
+ const messages = (await i18n.getMessages(config.locale)) as Messages;
32
32
 
33
33
  // better-i18n convention: JSON matches exact namespace structure.
34
34
  // if CDN returns { "hero": { "title": "..." } }, use-intl expects exactly that
package/src/types.ts CHANGED
@@ -33,11 +33,6 @@ export interface BetterI18nProviderConfig
33
33
  */
34
34
  now?: Date;
35
35
 
36
- /**
37
- * Callback when locale changes
38
- */
39
- onLocaleChange?: (locale: string) => void;
40
-
41
36
  /**
42
37
  * Error handler for missing translations
43
38
  */
@@ -46,25 +41,23 @@ export interface BetterI18nProviderConfig
46
41
 
47
42
  /**
48
43
  * Better i18n context value
44
+ *
45
+ * Note: Locale is read-only. Use useLocaleRouter().navigate() for locale changes
46
+ * to ensure proper router integration.
49
47
  */
50
48
  export interface BetterI18nContextValue {
51
49
  /**
52
- * Current locale
50
+ * Current locale (read-only - use useLocaleRouter().navigate() to change)
53
51
  */
54
52
  locale: string;
55
53
 
56
54
  /**
57
- * Change the current locale
58
- */
59
- setLocale: (locale: string) => void;
60
-
61
- /**
62
- * Available languages with metadata
55
+ * Available languages with metadata from CDN manifest
63
56
  */
64
57
  languages: LanguageOption[];
65
58
 
66
59
  /**
67
- * Whether languages are still loading
60
+ * Whether languages are still loading from CDN
68
61
  */
69
62
  isLoadingLanguages: boolean;
70
63