@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,15 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
3
2
|
import { createContext, useContext } from "react";
|
|
4
|
-
import type { BetterI18nContextValue } from "./types";
|
|
5
|
-
|
|
6
3
|
/**
|
|
7
4
|
* Context for Better i18n specific state
|
|
8
5
|
*/
|
|
9
|
-
export const BetterI18nContext = createContext
|
|
10
|
-
null
|
|
11
|
-
);
|
|
12
|
-
|
|
6
|
+
export const BetterI18nContext = createContext(null);
|
|
13
7
|
/**
|
|
14
8
|
* Hook to access Better i18n context
|
|
15
9
|
*
|
|
@@ -46,14 +40,11 @@ export const BetterI18nContext = createContext<BetterI18nContextValue | null>(
|
|
|
46
40
|
* }
|
|
47
41
|
* ```
|
|
48
42
|
*/
|
|
49
|
-
export function useBetterI18n()
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
return context;
|
|
43
|
+
export function useBetterI18n() {
|
|
44
|
+
const context = useContext(BetterI18nContext);
|
|
45
|
+
if (!context) {
|
|
46
|
+
throw new Error("[better-i18n] useBetterI18n must be used within a BetterI18nProvider");
|
|
47
|
+
}
|
|
48
|
+
return context;
|
|
59
49
|
}
|
|
50
|
+
//# sourceMappingURL=context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,MAAM,OAAO,CAAC;AAGlD;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,aAAa,CAC5C,IAAI,CACL,CAAC;AAEF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,OAAO,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAE9C,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,sEAAsE,CACvE,CAAC;IACJ,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return type for useLocaleRouter hook
|
|
3
|
+
*/
|
|
4
|
+
export interface UseLocaleRouterReturn {
|
|
5
|
+
/**
|
|
6
|
+
* Current locale (extracted from URL or default)
|
|
7
|
+
*/
|
|
8
|
+
locale: string;
|
|
9
|
+
/**
|
|
10
|
+
* Available locales from CDN manifest
|
|
11
|
+
*/
|
|
12
|
+
locales: string[];
|
|
13
|
+
/**
|
|
14
|
+
* Default locale (no URL prefix)
|
|
15
|
+
*/
|
|
16
|
+
defaultLocale: string;
|
|
17
|
+
/**
|
|
18
|
+
* Navigate to the same page with a new locale
|
|
19
|
+
* Uses TanStack Router's navigate() for proper SPA navigation
|
|
20
|
+
*/
|
|
21
|
+
navigate: (locale: string) => void;
|
|
22
|
+
/**
|
|
23
|
+
* Get a localized path for link building
|
|
24
|
+
* @param path - The path to localize
|
|
25
|
+
* @param locale - Target locale (optional, uses current if not specified)
|
|
26
|
+
*/
|
|
27
|
+
localePath: (path: string, locale?: string) => string;
|
|
28
|
+
/**
|
|
29
|
+
* Whether languages have been loaded from CDN
|
|
30
|
+
*/
|
|
31
|
+
isReady: boolean;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Hook for router-integrated locale navigation
|
|
35
|
+
*
|
|
36
|
+
* This hook provides a navigation-first approach to locale switching:
|
|
37
|
+
* - Locale changes trigger proper router navigation
|
|
38
|
+
* - Loaders re-execute with the new locale
|
|
39
|
+
* - No state synchronization issues
|
|
40
|
+
* - Works with TanStack Router's file-based routing
|
|
41
|
+
*
|
|
42
|
+
* @example
|
|
43
|
+
* ```tsx
|
|
44
|
+
* function LanguageSwitcher() {
|
|
45
|
+
* const { locale, locales, navigate, isReady } = useLocaleRouter();
|
|
46
|
+
*
|
|
47
|
+
* if (!isReady) return <Skeleton />;
|
|
48
|
+
*
|
|
49
|
+
* return (
|
|
50
|
+
* <select value={locale} onChange={(e) => navigate(e.target.value)}>
|
|
51
|
+
* {locales.map((loc) => (
|
|
52
|
+
* <option key={loc} value={loc}>{loc}</option>
|
|
53
|
+
* ))}
|
|
54
|
+
* </select>
|
|
55
|
+
* );
|
|
56
|
+
* }
|
|
57
|
+
* ```
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* // Building localized links
|
|
62
|
+
* function Navigation() {
|
|
63
|
+
* const { localePath } = useLocaleRouter();
|
|
64
|
+
*
|
|
65
|
+
* return (
|
|
66
|
+
* <nav>
|
|
67
|
+
* <Link to={localePath('/about')}>About</Link>
|
|
68
|
+
* <Link to={localePath('/contact', 'tr')}>İletişim (TR)</Link>
|
|
69
|
+
* </nav>
|
|
70
|
+
* );
|
|
71
|
+
* }
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export declare function useLocaleRouter(): UseLocaleRouterReturn;
|
|
75
|
+
//# sourceMappingURL=useLocaleRouter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLocaleRouter.d.ts","sourceRoot":"","sources":["../../src/hooks/useLocaleRouter.ts"],"names":[],"mappings":"AAYA;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,OAAO,EAAE,MAAM,EAAE,CAAC;IAElB;;OAEG;IACH,aAAa,EAAE,MAAM,CAAC;IAEtB;;;OAGG;IACH,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAEnC;;;;OAIG;IACH,UAAU,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;IAEtD;;OAEG;IACH,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,wBAAgB,eAAe,IAAI,qBAAqB,CAuDvD"}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { useCallback, useMemo } from "react";
|
|
3
|
+
import { useRouter, useLocation } from "@tanstack/react-router";
|
|
4
|
+
import { getLocaleFromPath, replaceLocaleInPath, addLocalePrefix, } from "@better-i18n/core";
|
|
5
|
+
import { useBetterI18n } from "../context.js";
|
|
6
|
+
/**
|
|
7
|
+
* Hook for router-integrated locale navigation
|
|
8
|
+
*
|
|
9
|
+
* This hook provides a navigation-first approach to locale switching:
|
|
10
|
+
* - Locale changes trigger proper router navigation
|
|
11
|
+
* - Loaders re-execute with the new locale
|
|
12
|
+
* - No state synchronization issues
|
|
13
|
+
* - Works with TanStack Router's file-based routing
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```tsx
|
|
17
|
+
* function LanguageSwitcher() {
|
|
18
|
+
* const { locale, locales, navigate, isReady } = useLocaleRouter();
|
|
19
|
+
*
|
|
20
|
+
* if (!isReady) return <Skeleton />;
|
|
21
|
+
*
|
|
22
|
+
* return (
|
|
23
|
+
* <select value={locale} onChange={(e) => navigate(e.target.value)}>
|
|
24
|
+
* {locales.map((loc) => (
|
|
25
|
+
* <option key={loc} value={loc}>{loc}</option>
|
|
26
|
+
* ))}
|
|
27
|
+
* </select>
|
|
28
|
+
* );
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* // Building localized links
|
|
35
|
+
* function Navigation() {
|
|
36
|
+
* const { localePath } = useLocaleRouter();
|
|
37
|
+
*
|
|
38
|
+
* return (
|
|
39
|
+
* <nav>
|
|
40
|
+
* <Link to={localePath('/about')}>About</Link>
|
|
41
|
+
* <Link to={localePath('/contact', 'tr')}>İletişim (TR)</Link>
|
|
42
|
+
* </nav>
|
|
43
|
+
* );
|
|
44
|
+
* }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function useLocaleRouter() {
|
|
48
|
+
const router = useRouter();
|
|
49
|
+
const location = useLocation();
|
|
50
|
+
const { languages, isLoadingLanguages } = useBetterI18n();
|
|
51
|
+
// Build config from CDN manifest
|
|
52
|
+
const config = useMemo(() => ({
|
|
53
|
+
locales: languages.map((lang) => lang.code),
|
|
54
|
+
defaultLocale: languages.find((l) => l.isDefault)?.code || "en",
|
|
55
|
+
}), [languages]);
|
|
56
|
+
// Get effective locale from URL (handles default without prefix)
|
|
57
|
+
const locale = useMemo(() => {
|
|
58
|
+
// If no languages loaded yet, extract from path manually
|
|
59
|
+
if (languages.length === 0) {
|
|
60
|
+
const segments = location.pathname.split("/").filter(Boolean);
|
|
61
|
+
const firstSegment = segments[0];
|
|
62
|
+
// Check if it looks like a locale code (2 letters)
|
|
63
|
+
if (firstSegment && /^[a-z]{2}$/i.test(firstSegment)) {
|
|
64
|
+
return firstSegment;
|
|
65
|
+
}
|
|
66
|
+
return "en"; // fallback
|
|
67
|
+
}
|
|
68
|
+
return getLocaleFromPath(location.pathname, config);
|
|
69
|
+
}, [location.pathname, config, languages]);
|
|
70
|
+
// Navigate to same page with new locale (SPA navigation!)
|
|
71
|
+
const navigate = useCallback((newLocale) => {
|
|
72
|
+
const newPath = replaceLocaleInPath(location.pathname, newLocale, config);
|
|
73
|
+
router.navigate({ to: newPath });
|
|
74
|
+
}, [location.pathname, config, router]);
|
|
75
|
+
// Get localized path for links
|
|
76
|
+
const localePath = useCallback((path, targetLocale) => {
|
|
77
|
+
const loc = targetLocale || locale;
|
|
78
|
+
return addLocalePrefix(path, loc, config);
|
|
79
|
+
}, [locale, config]);
|
|
80
|
+
return {
|
|
81
|
+
locale,
|
|
82
|
+
locales: config.locales,
|
|
83
|
+
defaultLocale: config.defaultLocale,
|
|
84
|
+
navigate,
|
|
85
|
+
localePath,
|
|
86
|
+
isReady: !isLoadingLanguages && config.locales.length > 0,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=useLocaleRouter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useLocaleRouter.js","sourceRoot":"","sources":["../../src/hooks/useLocaleRouter.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,WAAW,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAC7C,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,eAAe,GAEhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAwC9C;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,UAAU,eAAe;IAC7B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE,GAAG,aAAa,EAAE,CAAC;IAE1D,iCAAiC;IACjC,MAAM,MAAM,GAAiB,OAAO,CAClC,GAAG,EAAE,CAAC,CAAC;QACL,OAAO,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QAC3C,aAAa,EAAE,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,EAAE,IAAI,IAAI,IAAI;KAChE,CAAC,EACF,CAAC,SAAS,CAAC,CACZ,CAAC;IAEF,iEAAiE;IACjE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE;QAC1B,yDAAyD;QACzD,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC3B,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;YAC9D,MAAM,YAAY,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC;YACjC,mDAAmD;YACnD,IAAI,YAAY,IAAI,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,EAAE,CAAC;gBACrD,OAAO,YAAY,CAAC;YACtB,CAAC;YACD,OAAO,IAAI,CAAC,CAAC,WAAW;QAC1B,CAAC;QACD,OAAO,iBAAiB,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACtD,CAAC,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,CAAC,CAAC,CAAC;IAE3C,0DAA0D;IAC1D,MAAM,QAAQ,GAAG,WAAW,CAC1B,CAAC,SAAiB,EAAE,EAAE;QACpB,MAAM,OAAO,GAAG,mBAAmB,CAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC1E,MAAM,CAAC,QAAQ,CAAC,EAAE,EAAE,EAAE,OAAO,EAAE,CAAC,CAAC;IACnC,CAAC,EACD,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC,CACpC,CAAC;IAEF,+BAA+B;IAC/B,MAAM,UAAU,GAAG,WAAW,CAC5B,CAAC,IAAY,EAAE,YAAqB,EAAE,EAAE;QACtC,MAAM,GAAG,GAAG,YAAY,IAAI,MAAM,CAAC;QACnC,OAAO,eAAe,CAAC,IAAI,EAAE,GAAG,EAAE,MAAM,CAAC,CAAC;IAC5C,CAAC,EACD,CAAC,MAAM,EAAE,MAAM,CAAC,CACjB,CAAC;IAEF,OAAO;QACL,MAAM;QACN,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,aAAa,EAAE,MAAM,CAAC,aAAa;QACnC,QAAQ;QACR,UAAU;QACV,OAAO,EAAE,CAAC,kBAAkB,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC;KAC1D,CAAC;AACJ,CAAC"}
|
package/dist/hooks.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hook to get available languages from CDN manifest
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```tsx
|
|
6
|
+
* function LanguageList() {
|
|
7
|
+
* const { languages, isLoading } = useLanguages()
|
|
8
|
+
*
|
|
9
|
+
* if (isLoading) return <div>Loading...</div>
|
|
10
|
+
*
|
|
11
|
+
* return (
|
|
12
|
+
* <ul>
|
|
13
|
+
* {languages.map((lang) => (
|
|
14
|
+
* <li key={lang.code}>
|
|
15
|
+
* {lang.nativeName} ({lang.code})
|
|
16
|
+
* </li>
|
|
17
|
+
* ))}
|
|
18
|
+
* </ul>
|
|
19
|
+
* )
|
|
20
|
+
* }
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export declare function useLanguages(): {
|
|
24
|
+
languages: import("@better-i18n/core").LanguageOption[];
|
|
25
|
+
isLoading: boolean;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Hook to get current locale (read-only)
|
|
29
|
+
*
|
|
30
|
+
* For locale switching with proper router navigation, use useLocaleRouter() instead.
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* function LocaleDisplay() {
|
|
35
|
+
* const { locale, isLoading } = useLocale()
|
|
36
|
+
*
|
|
37
|
+
* if (isLoading) return <div>Loading...</div>
|
|
38
|
+
*
|
|
39
|
+
* return <span>Current locale: {locale}</span>
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*
|
|
43
|
+
* @example
|
|
44
|
+
* ```tsx
|
|
45
|
+
* // For locale switching, use useLocaleRouter:
|
|
46
|
+
* import { useLocaleRouter } from '@better-i18n/use-intl'
|
|
47
|
+
*
|
|
48
|
+
* function LocaleSwitcher() {
|
|
49
|
+
* const { locale, navigate } = useLocaleRouter()
|
|
50
|
+
* return (
|
|
51
|
+
* <button onClick={() => navigate(locale === 'en' ? 'tr' : 'en')}>
|
|
52
|
+
* Toggle: {locale}
|
|
53
|
+
* </button>
|
|
54
|
+
* )
|
|
55
|
+
* }
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
export declare function useLocale(): {
|
|
59
|
+
locale: string;
|
|
60
|
+
isLoading: boolean;
|
|
61
|
+
};
|
|
62
|
+
export { useTranslations, useFormatter, useMessages, useNow, useTimeZone, useLocale as useIntlLocale, } from "use-intl";
|
|
63
|
+
//# sourceMappingURL=hooks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAIA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,YAAY;;;EAO3B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAgB,SAAS;;;EAOxB;AAGD,OAAO,EACL,eAAe,EACf,YAAY,EACZ,WAAW,EACX,MAAM,EACN,WAAW,EACX,SAAS,IAAI,aAAa,GAC3B,MAAM,UAAU,CAAC"}
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
|
|
3
|
-
import { useBetterI18n } from "./context";
|
|
4
|
-
|
|
2
|
+
import { useBetterI18n } from "./context.js";
|
|
5
3
|
/**
|
|
6
4
|
* Hook to get available languages from CDN manifest
|
|
7
5
|
*
|
|
@@ -25,14 +23,12 @@ import { useBetterI18n } from "./context";
|
|
|
25
23
|
* ```
|
|
26
24
|
*/
|
|
27
25
|
export function useLanguages() {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
};
|
|
26
|
+
const { languages, isLoadingLanguages } = useBetterI18n();
|
|
27
|
+
return {
|
|
28
|
+
languages,
|
|
29
|
+
isLoading: isLoadingLanguages,
|
|
30
|
+
};
|
|
34
31
|
}
|
|
35
|
-
|
|
36
32
|
/**
|
|
37
33
|
* Hook to get current locale (read-only)
|
|
38
34
|
*
|
|
@@ -65,20 +61,12 @@ export function useLanguages() {
|
|
|
65
61
|
* ```
|
|
66
62
|
*/
|
|
67
63
|
export function useLocale() {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
};
|
|
64
|
+
const { locale, isLoadingMessages } = useBetterI18n();
|
|
65
|
+
return {
|
|
66
|
+
locale,
|
|
67
|
+
isLoading: isLoadingMessages,
|
|
68
|
+
};
|
|
74
69
|
}
|
|
75
|
-
|
|
76
70
|
// Re-export use-intl hooks for convenience
|
|
77
|
-
export {
|
|
78
|
-
|
|
79
|
-
useFormatter,
|
|
80
|
-
useMessages,
|
|
81
|
-
useNow,
|
|
82
|
-
useTimeZone,
|
|
83
|
-
useLocale as useIntlLocale,
|
|
84
|
-
} from "use-intl";
|
|
71
|
+
export { useTranslations, useFormatter, useMessages, useNow, useTimeZone, useLocale as useIntlLocale, } from "use-intl";
|
|
72
|
+
//# sourceMappingURL=hooks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hooks.js","sourceRoot":"","sources":["../src/hooks.ts"],"names":[],"mappings":"AAAA,YAAY,CAAC;AAEb,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAE7C;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,UAAU,YAAY;IAC1B,MAAM,EAAE,SAAS,EAAE,kBAAkB,EAAE,GAAG,aAAa,EAAE,CAAC;IAE1D,OAAO;QACL,SAAS;QACT,SAAS,EAAE,kBAAkB;KAC9B,CAAC;AACJ,CAAC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,EAAE,MAAM,EAAE,iBAAiB,EAAE,GAAG,aAAa,EAAE,CAAC;IAEtD,OAAO;QACL,MAAM;QACN,SAAS,EAAE,iBAAiB;KAC7B,CAAC;AACJ,CAAC;AAED,2CAA2C;AAC3C,OAAO,EACL,eAAe,EACf,YAAY,EACZ,WAAW,EACX,MAAM,EACN,WAAW,EACX,SAAS,IAAI,aAAa,GAC3B,MAAM,UAAU,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { BetterI18nProvider } from "./provider.js";
|
|
2
|
+
export type { BetterI18nProviderProps } from "./provider.js";
|
|
3
|
+
export { useBetterI18n } from "./context.js";
|
|
4
|
+
export { useLanguages, useLocale, useTranslations, useFormatter, useMessages, useNow, useTimeZone, } from "./hooks.js";
|
|
5
|
+
export { useLocaleRouter } from "./hooks/useLocaleRouter.js";
|
|
6
|
+
export type { UseLocaleRouterReturn } from "./hooks/useLocaleRouter.js";
|
|
7
|
+
export { LanguageSwitcher } from "./components.js";
|
|
8
|
+
export type { LanguageSwitcherProps } from "./components.js";
|
|
9
|
+
export { LocaleDropdown } from "./components/locale-dropdown.js";
|
|
10
|
+
export type { LocaleDropdownProps, LocaleDropdownRenderContext, } from "./components/locale-dropdown.js";
|
|
11
|
+
export type { Messages, BetterI18nProviderConfig, BetterI18nContextValue, } from "./types.js";
|
|
12
|
+
export { extractLocale, getLocaleFromPath, hasLocalePrefix, removeLocalePrefix, addLocalePrefix, replaceLocaleInPath, createLocalePath, type LocaleConfig, } from "@better-i18n/core";
|
|
13
|
+
export { IntlProvider } from "use-intl";
|
|
14
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AACnD,YAAY,EAAE,uBAAuB,EAAE,MAAM,eAAe,CAAC;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,YAAY,EACZ,SAAS,EAET,eAAe,EACf,YAAY,EACZ,WAAW,EACX,MAAM,EACN,WAAW,GACZ,MAAM,YAAY,CAAC;AAGpB,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAC7D,YAAY,EAAE,qBAAqB,EAAE,MAAM,4BAA4B,CAAC;AAGxE,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AACnD,YAAY,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAC;AAE7D,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,YAAY,EACV,mBAAmB,EACnB,2BAA2B,GAC5B,MAAM,iCAAiC,CAAC;AAGzC,YAAY,EACV,QAAQ,EACR,wBAAwB,EACxB,sBAAsB,GACvB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,EAChB,KAAK,YAAY,GAClB,MAAM,mBAAmB,CAAC;AAG3B,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Provider
|
|
2
|
+
export { BetterI18nProvider } from "./provider.js";
|
|
3
|
+
// Context & Hooks
|
|
4
|
+
export { useBetterI18n } from "./context.js";
|
|
5
|
+
export { useLanguages, useLocale,
|
|
6
|
+
// Re-exported from use-intl
|
|
7
|
+
useTranslations, useFormatter, useMessages, useNow, useTimeZone, } from "./hooks.js";
|
|
8
|
+
// Router Integration (TanStack Router)
|
|
9
|
+
export { useLocaleRouter } from "./hooks/useLocaleRouter.js";
|
|
10
|
+
// Components
|
|
11
|
+
export { LanguageSwitcher } from "./components.js";
|
|
12
|
+
export { LocaleDropdown } from "./components/locale-dropdown.js";
|
|
13
|
+
// Re-export locale utilities from core (convenience)
|
|
14
|
+
export { extractLocale, getLocaleFromPath, hasLocalePrefix, removeLocalePrefix, addLocalePrefix, replaceLocaleInPath, createLocalePath, } from "@better-i18n/core";
|
|
15
|
+
// Re-export commonly used use-intl components
|
|
16
|
+
export { IntlProvider } from "use-intl";
|
|
17
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,WAAW;AACX,OAAO,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGnD,kBAAkB;AAClB,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAC7C,OAAO,EACL,YAAY,EACZ,SAAS;AACT,4BAA4B;AAC5B,eAAe,EACf,YAAY,EACZ,WAAW,EACX,MAAM,EACN,WAAW,GACZ,MAAM,YAAY,CAAC;AAEpB,uCAAuC;AACvC,OAAO,EAAE,eAAe,EAAE,MAAM,4BAA4B,CAAC;AAG7D,aAAa;AACb,OAAO,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAGnD,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAajE,qDAAqD;AACrD,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,eAAe,EACf,kBAAkB,EAClB,eAAe,EACf,mBAAmB,EACnB,gBAAgB,GAEjB,MAAM,mBAAmB,CAAC;AAE3B,8CAA8C;AAC9C,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE9D,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,oBAAoB,OAsGtE"}
|
|
@@ -0,0 +1,68 @@
|
|
|
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
|
+
export function createBetterI18nMiddleware(config) {
|
|
6
|
+
const { project, defaultLocale, localePrefix = "as-needed", detection = {} } = config;
|
|
7
|
+
const { cookie = true, browserLanguage = true, cookieName = "locale", cookieMaxAge = 31536000, } = detection;
|
|
8
|
+
return createMiddleware().server(async ({ next, request, }) => {
|
|
9
|
+
// 1. Fetch available locales from CDN (cached)
|
|
10
|
+
const availableLocales = await getLocales({ project });
|
|
11
|
+
// 2. Extract locale indicators
|
|
12
|
+
const url = new URL(request.url);
|
|
13
|
+
const pathSegment = url.pathname.split("/")[1];
|
|
14
|
+
const hasLocaleInPath = !!pathSegment && availableLocales.includes(pathSegment);
|
|
15
|
+
// Dynamic imports for TanStack Start server functions to avoid bundling them in client
|
|
16
|
+
// @ts-ignore - optional runtime dependency, types may not be available
|
|
17
|
+
const startServer = await import("@tanstack/react-start/server");
|
|
18
|
+
const getCookie = startServer.getCookie;
|
|
19
|
+
const setCookie = startServer.setCookie;
|
|
20
|
+
const getRequestHeader = startServer.getRequestHeader;
|
|
21
|
+
const cookieLocale = cookie ? getCookie(cookieName) : null;
|
|
22
|
+
const headerLocale = browserLanguage
|
|
23
|
+
? getRequestHeader("accept-language")?.split(",")[0]?.split("-")[0]
|
|
24
|
+
: null;
|
|
25
|
+
// 3. Detect locale using core logic
|
|
26
|
+
const result = detectLocale({
|
|
27
|
+
project,
|
|
28
|
+
defaultLocale,
|
|
29
|
+
pathLocale: pathSegment,
|
|
30
|
+
cookieLocale,
|
|
31
|
+
headerLocale,
|
|
32
|
+
availableLocales,
|
|
33
|
+
});
|
|
34
|
+
// 4. Redirect if locale prefix is needed but missing
|
|
35
|
+
// Skip API routes and paths that already have a locale prefix
|
|
36
|
+
const isApiRoute = url.pathname.startsWith("/api/");
|
|
37
|
+
if (localePrefix !== "never" &&
|
|
38
|
+
!hasLocaleInPath &&
|
|
39
|
+
!isApiRoute &&
|
|
40
|
+
result.detectedFrom !== "path") {
|
|
41
|
+
const shouldRedirect = localePrefix === "always" ||
|
|
42
|
+
(localePrefix === "as-needed" && result.locale !== defaultLocale);
|
|
43
|
+
if (shouldRedirect) {
|
|
44
|
+
const redirectUrl = new URL(`/${result.locale}${url.pathname}`, url.origin);
|
|
45
|
+
redirectUrl.search = url.search;
|
|
46
|
+
// Build redirect response with locale cookie
|
|
47
|
+
const headers = new Headers({
|
|
48
|
+
Location: redirectUrl.toString(),
|
|
49
|
+
});
|
|
50
|
+
if (cookie && result.shouldSetCookie) {
|
|
51
|
+
headers.set("Set-Cookie", `${cookieName}=${result.locale}; Path=/; Max-Age=${cookieMaxAge}; SameSite=Lax`);
|
|
52
|
+
}
|
|
53
|
+
return new Response(null, { status: 302, headers });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
// 5. Set cookie if needed (non-redirect path)
|
|
57
|
+
if (cookie && result.shouldSetCookie) {
|
|
58
|
+
setCookie(cookieName, result.locale, {
|
|
59
|
+
path: "/",
|
|
60
|
+
maxAge: cookieMaxAge,
|
|
61
|
+
sameSite: "lax",
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// 6. Pass locale to route context
|
|
65
|
+
return next({ context: { locale: result.locale } });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/middleware/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { LanguageOption } from "@better-i18n/core";
|
|
2
|
+
import { type ReactNode } from "react";
|
|
3
|
+
import type { BetterI18nProviderConfig } from "./types.js";
|
|
4
|
+
export interface BetterI18nProviderProps extends BetterI18nProviderConfig {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
/** Pre-loaded languages from SSR loader — skips loading state on first render */
|
|
7
|
+
initialLanguages?: LanguageOption[];
|
|
8
|
+
/** Custom fallback when a message key is missing */
|
|
9
|
+
getMessageFallback?: (info: {
|
|
10
|
+
error: Error;
|
|
11
|
+
key: string;
|
|
12
|
+
namespace?: string;
|
|
13
|
+
}) => string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Provider component that combines Better i18n CDN with use-intl
|
|
17
|
+
*
|
|
18
|
+
* The locale is controlled externally (from URL/router). Use useLocaleRouter()
|
|
19
|
+
* for locale switching with proper router integration.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* // Basic usage (CSR - fetches messages on client)
|
|
24
|
+
* function App() {
|
|
25
|
+
* return (
|
|
26
|
+
* <BetterI18nProvider
|
|
27
|
+
* project="acme/dashboard"
|
|
28
|
+
* locale="en"
|
|
29
|
+
* >
|
|
30
|
+
* <MyComponent />
|
|
31
|
+
* </BetterI18nProvider>
|
|
32
|
+
* )
|
|
33
|
+
* }
|
|
34
|
+
*
|
|
35
|
+
* // TanStack Router SSR usage (pre-loaded messages from loader)
|
|
36
|
+
* function RootComponent() {
|
|
37
|
+
* const { messages, locale } = Route.useLoaderData()
|
|
38
|
+
* return (
|
|
39
|
+
* <BetterI18nProvider
|
|
40
|
+
* project="acme/dashboard"
|
|
41
|
+
* locale={locale}
|
|
42
|
+
* messages={messages}
|
|
43
|
+
* >
|
|
44
|
+
* <Outlet />
|
|
45
|
+
* </BetterI18nProvider>
|
|
46
|
+
* )
|
|
47
|
+
* }
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export declare function BetterI18nProvider({ children, project, locale: propLocale, messages: propMessages, timeZone, now, onError, cdnBaseUrl, debug, logLevel, fetch: customFetch, storage, staticData, fetchTimeout, retryCount, initialLanguages, getMessageFallback: customGetMessageFallback, }: BetterI18nProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxD,OAAO,EAAgC,KAAK,SAAS,EAAE,MAAM,OAAO,CAAC;AAGrE,OAAO,KAAK,EAAE,wBAAwB,EAAY,MAAM,YAAY,CAAC;AAErE,MAAM,WAAW,uBAAwB,SAAQ,wBAAwB;IACvE,QAAQ,EAAE,SAAS,CAAC;IACpB,iFAAiF;IACjF,gBAAgB,CAAC,EAAE,cAAc,EAAE,CAAC;IACpC,oDAAoD;IACpD,kBAAkB,CAAC,EAAE,CAAC,IAAI,EAAE;QAC1B,KAAK,EAAE,KAAK,CAAC;QACb,GAAG,EAAE,MAAM,CAAC;QACZ,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB,KAAK,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,QAAQ,EACR,OAAO,EACP,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,YAAY,EACtB,QAAQ,EACR,GAAG,EACH,OAAO,EACP,UAAU,EACV,KAAK,EACL,QAAQ,EACR,KAAK,EAAE,WAAW,EAClB,OAAO,EACP,UAAU,EACV,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,kBAAkB,EAAE,wBAAwB,GAC7C,EAAE,uBAAuB,2CA4IzB"}
|