@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.
- package/README.md +7 -0
- package/dist/__tests__/provider-config.test.d.ts +2 -0
- package/dist/__tests__/provider-config.test.d.ts.map +1 -0
- package/dist/__tests__/provider-config.test.js +79 -0
- package/dist/__tests__/provider-config.test.js.map +1 -0
- package/dist/components/locale-dropdown.d.ts +62 -0
- package/dist/components/locale-dropdown.d.ts.map +1 -0
- package/dist/components/locale-dropdown.js +298 -0
- package/dist/components/locale-dropdown.js.map +1 -0
- package/dist/components.d.ts +44 -0
- package/dist/components.d.ts.map +1 -0
- package/dist/components.js +38 -0
- package/dist/components.js.map +1 -0
- package/dist/context.d.ts +43 -0
- package/dist/context.d.ts.map +1 -0
- package/{src/context.tsx → dist/context.js} +8 -17
- package/dist/context.js.map +1 -0
- package/dist/hooks/useLocaleRouter.d.ts +75 -0
- package/dist/hooks/useLocaleRouter.d.ts.map +1 -0
- package/dist/hooks/useLocaleRouter.js +89 -0
- package/dist/hooks/useLocaleRouter.js.map +1 -0
- package/dist/hooks.d.ts +63 -0
- package/dist/hooks.d.ts.map +1 -0
- package/{src/hooks.ts → dist/hooks.js} +13 -25
- package/dist/hooks.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/index.d.ts +3 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +68 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/provider.d.ts +51 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +138 -0
- package/dist/provider.js.map +1 -0
- package/dist/server.d.ts +79 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +156 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +71 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +22 -9
- package/src/components.tsx +0 -76
- package/src/hooks/useLocaleRouter.ts +0 -147
- package/src/index.ts +0 -46
- package/src/middleware/index.ts +0 -114
- package/src/provider.tsx +0 -183
- package/src/server.ts +0 -108
- package/src/types.ts +0 -83
|
@@ -1,147 +0,0 @@
|
|
|
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/index.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
// Provider
|
|
2
|
-
export { BetterI18nProvider } from "./provider";
|
|
3
|
-
export type { BetterI18nProviderProps } from "./provider";
|
|
4
|
-
|
|
5
|
-
// Context & Hooks
|
|
6
|
-
export { useBetterI18n } from "./context";
|
|
7
|
-
export {
|
|
8
|
-
useLanguages,
|
|
9
|
-
useLocale,
|
|
10
|
-
// Re-exported from use-intl
|
|
11
|
-
useTranslations,
|
|
12
|
-
useFormatter,
|
|
13
|
-
useMessages,
|
|
14
|
-
useNow,
|
|
15
|
-
useTimeZone,
|
|
16
|
-
} from "./hooks";
|
|
17
|
-
|
|
18
|
-
// Router Integration (TanStack Router)
|
|
19
|
-
export { useLocaleRouter } from "./hooks/useLocaleRouter";
|
|
20
|
-
export type { UseLocaleRouterReturn } from "./hooks/useLocaleRouter";
|
|
21
|
-
|
|
22
|
-
// Components
|
|
23
|
-
export { LanguageSwitcher } from "./components";
|
|
24
|
-
export type { LanguageSwitcherProps } from "./components";
|
|
25
|
-
|
|
26
|
-
// Types
|
|
27
|
-
export type {
|
|
28
|
-
Messages,
|
|
29
|
-
BetterI18nProviderConfig,
|
|
30
|
-
BetterI18nContextValue,
|
|
31
|
-
} from "./types";
|
|
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
|
-
|
|
45
|
-
// Re-export commonly used use-intl components
|
|
46
|
-
export { IntlProvider } from "use-intl";
|
package/src/middleware/index.ts
DELETED
|
@@ -1,114 +0,0 @@
|
|
|
1
|
-
// @ts-expect-error - internal workspace dependency
|
|
2
|
-
import { createMiddleware } from "@tanstack/react-router";
|
|
3
|
-
// @ts-expect-error - internal workspace dependency
|
|
4
|
-
import { detectLocale, getLocales } from "@better-i18n/core";
|
|
5
|
-
import type { I18nMiddlewareConfig } from "@better-i18n/core";
|
|
6
|
-
|
|
7
|
-
export function createBetterI18nMiddleware(config: I18nMiddlewareConfig) {
|
|
8
|
-
const { project, defaultLocale, localePrefix = "as-needed", detection = {} } = config;
|
|
9
|
-
|
|
10
|
-
const {
|
|
11
|
-
cookie = true,
|
|
12
|
-
browserLanguage = true,
|
|
13
|
-
cookieName = "locale",
|
|
14
|
-
cookieMaxAge = 31536000,
|
|
15
|
-
} = detection;
|
|
16
|
-
|
|
17
|
-
return createMiddleware().server(
|
|
18
|
-
async ({
|
|
19
|
-
next,
|
|
20
|
-
request,
|
|
21
|
-
}: {
|
|
22
|
-
next: (ctx: { context: { locale: string } }) => Promise<unknown>;
|
|
23
|
-
request: Request;
|
|
24
|
-
}) => {
|
|
25
|
-
// 1. Fetch available locales from CDN (cached)
|
|
26
|
-
const availableLocales = await getLocales({ project });
|
|
27
|
-
|
|
28
|
-
// 2. Extract locale indicators
|
|
29
|
-
const url = new URL(request.url);
|
|
30
|
-
const pathSegment = url.pathname.split("/")[1];
|
|
31
|
-
const hasLocaleInPath =
|
|
32
|
-
!!pathSegment && availableLocales.includes(pathSegment);
|
|
33
|
-
|
|
34
|
-
// Dynamic imports for TanStack Start server functions to avoid bundling them in client
|
|
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");
|
|
49
|
-
|
|
50
|
-
const cookieLocale = cookie ? getCookie(cookieName) : null;
|
|
51
|
-
const headerLocale = browserLanguage
|
|
52
|
-
? getRequestHeader("accept-language")?.split(",")[0]?.split("-")[0]
|
|
53
|
-
: null;
|
|
54
|
-
|
|
55
|
-
// 3. Detect locale using core logic
|
|
56
|
-
const result = detectLocale({
|
|
57
|
-
project,
|
|
58
|
-
defaultLocale,
|
|
59
|
-
pathLocale: pathSegment,
|
|
60
|
-
cookieLocale,
|
|
61
|
-
headerLocale,
|
|
62
|
-
availableLocales,
|
|
63
|
-
});
|
|
64
|
-
|
|
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)
|
|
102
|
-
if (cookie && result.shouldSetCookie) {
|
|
103
|
-
setCookie(cookieName, result.locale, {
|
|
104
|
-
path: "/",
|
|
105
|
-
maxAge: cookieMaxAge,
|
|
106
|
-
sameSite: "lax",
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
// 6. Pass locale to route context
|
|
111
|
-
return next({ context: { locale: result.locale } });
|
|
112
|
-
},
|
|
113
|
-
);
|
|
114
|
-
}
|
package/src/provider.tsx
DELETED
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import { createI18nCore } from "@better-i18n/core";
|
|
4
|
-
import type { LanguageOption } from "@better-i18n/core";
|
|
5
|
-
import { useEffect, useMemo, useState, type ReactNode } from "react";
|
|
6
|
-
import { IntlProvider } from "use-intl";
|
|
7
|
-
import { BetterI18nContext } from "./context.js";
|
|
8
|
-
import type { BetterI18nProviderConfig, Messages } from "./types.js";
|
|
9
|
-
|
|
10
|
-
export interface BetterI18nProviderProps extends BetterI18nProviderConfig {
|
|
11
|
-
children: ReactNode;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/**
|
|
15
|
-
* Provider component that combines Better i18n CDN with use-intl
|
|
16
|
-
*
|
|
17
|
-
* The locale is controlled externally (from URL/router). Use useLocaleRouter()
|
|
18
|
-
* for locale switching with proper router integration.
|
|
19
|
-
*
|
|
20
|
-
* @example
|
|
21
|
-
* ```tsx
|
|
22
|
-
* // Basic usage (CSR - fetches messages on client)
|
|
23
|
-
* function App() {
|
|
24
|
-
* return (
|
|
25
|
-
* <BetterI18nProvider
|
|
26
|
-
* project="acme/dashboard"
|
|
27
|
-
* locale="en"
|
|
28
|
-
* >
|
|
29
|
-
* <MyComponent />
|
|
30
|
-
* </BetterI18nProvider>
|
|
31
|
-
* )
|
|
32
|
-
* }
|
|
33
|
-
*
|
|
34
|
-
* // TanStack Router SSR usage (pre-loaded messages from loader)
|
|
35
|
-
* function RootComponent() {
|
|
36
|
-
* const { messages, locale } = Route.useLoaderData()
|
|
37
|
-
* return (
|
|
38
|
-
* <BetterI18nProvider
|
|
39
|
-
* project="acme/dashboard"
|
|
40
|
-
* locale={locale}
|
|
41
|
-
* messages={messages}
|
|
42
|
-
* >
|
|
43
|
-
* <Outlet />
|
|
44
|
-
* </BetterI18nProvider>
|
|
45
|
-
* )
|
|
46
|
-
* }
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
export function BetterI18nProvider({
|
|
50
|
-
children,
|
|
51
|
-
project,
|
|
52
|
-
locale: propLocale,
|
|
53
|
-
messages: propMessages,
|
|
54
|
-
timeZone,
|
|
55
|
-
now,
|
|
56
|
-
onError,
|
|
57
|
-
cdnBaseUrl,
|
|
58
|
-
debug,
|
|
59
|
-
logLevel,
|
|
60
|
-
fetch: customFetch,
|
|
61
|
-
}: BetterI18nProviderProps) {
|
|
62
|
-
// Locale is controlled by props (from URL/router)
|
|
63
|
-
const locale = propLocale;
|
|
64
|
-
const [messages, setMessages] = useState<Messages | undefined>(propMessages);
|
|
65
|
-
const [languages, setLanguages] = useState<LanguageOption[]>([]);
|
|
66
|
-
const [isLoadingMessages, setIsLoadingMessages] = useState(!propMessages);
|
|
67
|
-
const [isLoadingLanguages, setIsLoadingLanguages] = useState(true);
|
|
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
|
-
|
|
77
|
-
// Create i18n core instance
|
|
78
|
-
const i18nCore = useMemo(
|
|
79
|
-
() =>
|
|
80
|
-
createI18nCore({
|
|
81
|
-
project,
|
|
82
|
-
defaultLocale: locale,
|
|
83
|
-
cdnBaseUrl,
|
|
84
|
-
debug,
|
|
85
|
-
logLevel,
|
|
86
|
-
fetch: customFetch,
|
|
87
|
-
}),
|
|
88
|
-
[project, locale, cdnBaseUrl, debug, logLevel, customFetch]
|
|
89
|
-
);
|
|
90
|
-
|
|
91
|
-
// Load languages on mount
|
|
92
|
-
useEffect(() => {
|
|
93
|
-
let cancelled = false;
|
|
94
|
-
|
|
95
|
-
const loadLanguages = async () => {
|
|
96
|
-
try {
|
|
97
|
-
const langs = await i18nCore.getLanguages();
|
|
98
|
-
if (!cancelled) {
|
|
99
|
-
setLanguages(langs);
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
console.error("[better-i18n] Failed to load languages:", error);
|
|
103
|
-
} finally {
|
|
104
|
-
if (!cancelled) {
|
|
105
|
-
setIsLoadingLanguages(false);
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
loadLanguages();
|
|
111
|
-
|
|
112
|
-
return () => {
|
|
113
|
-
cancelled = true;
|
|
114
|
-
};
|
|
115
|
-
}, [i18nCore]);
|
|
116
|
-
|
|
117
|
-
// Load messages when locale changes and no pre-loaded messages available
|
|
118
|
-
useEffect(() => {
|
|
119
|
-
// Skip if we already have messages for this render
|
|
120
|
-
if (propMessages) {
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
let cancelled = false;
|
|
125
|
-
|
|
126
|
-
const loadMessages = async () => {
|
|
127
|
-
setIsLoadingMessages(true);
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
const msgs = await i18nCore.getMessages(locale);
|
|
131
|
-
if (!cancelled) {
|
|
132
|
-
setMessages(msgs as Messages);
|
|
133
|
-
}
|
|
134
|
-
} catch (error) {
|
|
135
|
-
console.error(
|
|
136
|
-
`[better-i18n] Failed to load messages for locale "${locale}":`,
|
|
137
|
-
error
|
|
138
|
-
);
|
|
139
|
-
} finally {
|
|
140
|
-
if (!cancelled) {
|
|
141
|
-
setIsLoadingMessages(false);
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
};
|
|
145
|
-
|
|
146
|
-
loadMessages();
|
|
147
|
-
|
|
148
|
-
return () => {
|
|
149
|
-
cancelled = true;
|
|
150
|
-
};
|
|
151
|
-
}, [locale, i18nCore, propMessages]);
|
|
152
|
-
|
|
153
|
-
// Context value (read-only locale - use useLocaleRouter for navigation)
|
|
154
|
-
const contextValue = useMemo(
|
|
155
|
-
() => ({
|
|
156
|
-
locale,
|
|
157
|
-
languages,
|
|
158
|
-
isLoadingLanguages,
|
|
159
|
-
isLoadingMessages,
|
|
160
|
-
project,
|
|
161
|
-
}),
|
|
162
|
-
[locale, languages, isLoadingLanguages, isLoadingMessages, project]
|
|
163
|
-
);
|
|
164
|
-
|
|
165
|
-
// Don't render until we have messages
|
|
166
|
-
if (!messages) {
|
|
167
|
-
return null;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return (
|
|
171
|
-
<BetterI18nContext.Provider value={contextValue}>
|
|
172
|
-
<IntlProvider
|
|
173
|
-
locale={locale}
|
|
174
|
-
messages={messages}
|
|
175
|
-
timeZone={timeZone}
|
|
176
|
-
now={now}
|
|
177
|
-
onError={onError}
|
|
178
|
-
>
|
|
179
|
-
{children}
|
|
180
|
-
</IntlProvider>
|
|
181
|
-
</BetterI18nContext.Provider>
|
|
182
|
-
);
|
|
183
|
-
}
|
package/src/server.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { createI18nCore } from "@better-i18n/core";
|
|
2
|
-
import type { I18nCoreConfig, LanguageOption } from "@better-i18n/core";
|
|
3
|
-
import { createFormatter, createTranslator } from "use-intl/core";
|
|
4
|
-
import type { Messages } from "./types.js";
|
|
5
|
-
|
|
6
|
-
export interface GetMessagesConfig extends Omit<
|
|
7
|
-
I18nCoreConfig,
|
|
8
|
-
"defaultLocale"
|
|
9
|
-
> {
|
|
10
|
-
/**
|
|
11
|
-
* Locale to fetch messages for
|
|
12
|
-
*/
|
|
13
|
-
locale: string;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* Fetch messages for a locale (server-side)
|
|
18
|
-
*/
|
|
19
|
-
export async function getMessages(
|
|
20
|
-
config: GetMessagesConfig,
|
|
21
|
-
): Promise<Messages> {
|
|
22
|
-
const i18n = createI18nCore({
|
|
23
|
-
project: config.project,
|
|
24
|
-
defaultLocale: config.locale,
|
|
25
|
-
cdnBaseUrl: config.cdnBaseUrl,
|
|
26
|
-
debug: config.debug,
|
|
27
|
-
logLevel: config.logLevel,
|
|
28
|
-
fetch: config.fetch,
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
const messages = (await i18n.getMessages(config.locale)) as Messages;
|
|
32
|
-
|
|
33
|
-
// better-i18n convention: JSON matches exact namespace structure.
|
|
34
|
-
// if CDN returns { "hero": { "title": "..." } }, use-intl expects exactly that
|
|
35
|
-
// if it's deeply nested, use-intl also handles nested objects.
|
|
36
|
-
|
|
37
|
-
return messages as Messages;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Fetch available locales (server-side)
|
|
42
|
-
*/
|
|
43
|
-
export async function getLocales(
|
|
44
|
-
config: Omit<I18nCoreConfig, "defaultLocale"> & { defaultLocale?: string },
|
|
45
|
-
): Promise<string[]> {
|
|
46
|
-
const i18n = createI18nCore({
|
|
47
|
-
project: config.project,
|
|
48
|
-
defaultLocale: config.defaultLocale || "en",
|
|
49
|
-
cdnBaseUrl: config.cdnBaseUrl,
|
|
50
|
-
debug: config.debug,
|
|
51
|
-
logLevel: config.logLevel,
|
|
52
|
-
fetch: config.fetch,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
return i18n.getLocales();
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Fetch available languages with metadata (server-side)
|
|
60
|
-
*/
|
|
61
|
-
export async function getLanguages(
|
|
62
|
-
config: Omit<I18nCoreConfig, "defaultLocale"> & { defaultLocale?: string },
|
|
63
|
-
): Promise<LanguageOption[]> {
|
|
64
|
-
const i18n = createI18nCore({
|
|
65
|
-
project: config.project,
|
|
66
|
-
defaultLocale: config.defaultLocale || "en",
|
|
67
|
-
cdnBaseUrl: config.cdnBaseUrl,
|
|
68
|
-
debug: config.debug,
|
|
69
|
-
logLevel: config.logLevel,
|
|
70
|
-
fetch: config.fetch,
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
return i18n.getLanguages();
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Create a translator function for use outside React (server-side)
|
|
78
|
-
*/
|
|
79
|
-
export function createServerTranslator(config: {
|
|
80
|
-
locale: string;
|
|
81
|
-
messages: Messages;
|
|
82
|
-
namespace?: string;
|
|
83
|
-
}) {
|
|
84
|
-
return createTranslator({
|
|
85
|
-
locale: config.locale,
|
|
86
|
-
messages: config.messages as Parameters<
|
|
87
|
-
typeof createTranslator
|
|
88
|
-
>[0]["messages"],
|
|
89
|
-
namespace: config.namespace,
|
|
90
|
-
});
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Create a formatter for use outside React (server-side)
|
|
95
|
-
*/
|
|
96
|
-
export function createServerFormatter(config: {
|
|
97
|
-
locale: string;
|
|
98
|
-
timeZone?: string;
|
|
99
|
-
now?: Date;
|
|
100
|
-
}) {
|
|
101
|
-
return createFormatter({
|
|
102
|
-
locale: config.locale,
|
|
103
|
-
timeZone: config.timeZone,
|
|
104
|
-
now: config.now,
|
|
105
|
-
});
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export { createTranslator, createFormatter } from "use-intl/core";
|
package/src/types.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
import type { I18nCoreConfig, LanguageOption } from "@better-i18n/core";
|
|
2
|
-
import type { ComponentProps } from "react";
|
|
3
|
-
import type { IntlProvider } from "use-intl";
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Messages type (compatible with use-intl)
|
|
7
|
-
*/
|
|
8
|
-
export type Messages = ComponentProps<typeof IntlProvider>["messages"];
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* Configuration for BetterI18nProvider
|
|
12
|
-
*/
|
|
13
|
-
export interface BetterI18nProviderConfig
|
|
14
|
-
extends Omit<I18nCoreConfig, "defaultLocale"> {
|
|
15
|
-
/**
|
|
16
|
-
* Current locale
|
|
17
|
-
*/
|
|
18
|
-
locale: string;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Pre-loaded messages (for SSR hydration)
|
|
22
|
-
*/
|
|
23
|
-
messages?: Messages;
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Timezone for date/time formatting
|
|
27
|
-
* @default undefined (uses browser timezone)
|
|
28
|
-
*/
|
|
29
|
-
timeZone?: string;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Current date/time (useful for SSR to prevent hydration mismatches)
|
|
33
|
-
*/
|
|
34
|
-
now?: Date;
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* Error handler for missing translations
|
|
38
|
-
*/
|
|
39
|
-
onError?: ComponentProps<typeof IntlProvider>["onError"];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Better i18n context value
|
|
44
|
-
*
|
|
45
|
-
* Note: Locale is read-only. Use useLocaleRouter().navigate() for locale changes
|
|
46
|
-
* to ensure proper router integration.
|
|
47
|
-
*/
|
|
48
|
-
export interface BetterI18nContextValue {
|
|
49
|
-
/**
|
|
50
|
-
* Current locale (read-only - use useLocaleRouter().navigate() to change)
|
|
51
|
-
*/
|
|
52
|
-
locale: string;
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Available languages with metadata from CDN manifest
|
|
56
|
-
*/
|
|
57
|
-
languages: LanguageOption[];
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Whether languages are still loading from CDN
|
|
61
|
-
*/
|
|
62
|
-
isLoadingLanguages: boolean;
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Whether messages are still loading
|
|
66
|
-
*/
|
|
67
|
-
isLoadingMessages: boolean;
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* Project identifier
|
|
71
|
-
*/
|
|
72
|
-
project: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Server-side configuration for getMessages
|
|
77
|
-
*/
|
|
78
|
-
export interface GetMessagesConfig extends I18nCoreConfig {
|
|
79
|
-
/**
|
|
80
|
-
* Locale to fetch messages for
|
|
81
|
-
*/
|
|
82
|
-
locale: string;
|
|
83
|
-
}
|