@better-i18n/next 0.5.2 → 0.5.4
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 +5 -0
- package/dist/__tests__/client-fallback.test.d.ts +2 -0
- package/dist/__tests__/client-fallback.test.d.ts.map +1 -0
- package/dist/__tests__/client-fallback.test.js +108 -0
- package/dist/__tests__/client-fallback.test.js.map +1 -0
- package/dist/client.d.ts +104 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +233 -0
- package/dist/client.js.map +1 -0
- package/dist/config.d.ts +12 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +25 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +69 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +82 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +78 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +177 -0
- package/dist/middleware.js.map +1 -0
- package/dist/proxy.d.ts +2 -0
- package/dist/proxy.d.ts.map +1 -0
- package/dist/proxy.js +2 -0
- package/dist/proxy.js.map +1 -0
- package/dist/server.d.ts +71 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +144 -0
- package/dist/server.js.map +1 -0
- package/dist/types.d.ts +58 -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 +24 -16
- package/src/client.tsx +0 -324
- package/src/config.ts +0 -40
- package/src/index.ts +0 -116
- package/src/middleware.ts +0 -243
- package/src/proxy.ts +0 -1
- package/src/server.ts +0 -164
- package/src/types.ts +0 -66
package/README.md
CHANGED
|
@@ -139,8 +139,13 @@ const languages = await getManifestLanguages(config);
|
|
|
139
139
|
| `defaultLocale` | `string` | required | Default/fallback locale code |
|
|
140
140
|
| `cdnBaseUrl` | `string` | auto | CDN base URL (auto-detected) |
|
|
141
141
|
| `localePrefix` | `"as-needed"` \| `"always"` \| `"never"` | `"as-needed"` | URL locale prefix behavior |
|
|
142
|
+
| `cookieName` | `string` | `"locale"` | Cookie name for locale persistence |
|
|
142
143
|
| `manifestRevalidateSeconds` | `number` | `3600` | Next.js ISR revalidation for manifest |
|
|
143
144
|
| `messagesRevalidateSeconds` | `number` | `30` | Next.js ISR revalidation for messages |
|
|
145
|
+
| `storage` | `TranslationStorage` | `undefined` | Persistent cache for offline fallback (see `@better-i18n/core`) |
|
|
146
|
+
| `staticData` | `Record \| () => Promise` | `undefined` | Bundled translations as last-resort fallback |
|
|
147
|
+
| `fetchTimeout` | `number` | `10000` | CDN fetch timeout in ms |
|
|
148
|
+
| `retryCount` | `number` | `1` | Retry attempts on CDN failure |
|
|
144
149
|
| `debug` | `boolean` | `false` | Enable debug logging |
|
|
145
150
|
| `logLevel` | `LogLevel` | `"warn"` | Logging verbosity |
|
|
146
151
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-fallback.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/client-fallback.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { normalizeConfig } from "../config.js";
|
|
3
|
+
// ─── Test fixtures ──────────────────────────────────────────────────
|
|
4
|
+
const BASE_CONFIG = {
|
|
5
|
+
project: "acme/dashboard",
|
|
6
|
+
defaultLocale: "en",
|
|
7
|
+
};
|
|
8
|
+
const mockStorage = {
|
|
9
|
+
get: async () => null,
|
|
10
|
+
set: async () => { },
|
|
11
|
+
};
|
|
12
|
+
const mockStaticData = {
|
|
13
|
+
en: { common: { hello: "Hello" } },
|
|
14
|
+
tr: { common: { hello: "Merhaba" } },
|
|
15
|
+
};
|
|
16
|
+
// ─── Tests ──────────────────────────────────────────────────────────
|
|
17
|
+
describe("normalizeConfig — fallback fields passthrough", () => {
|
|
18
|
+
it("passes storage to core config", () => {
|
|
19
|
+
const config = normalizeConfig({
|
|
20
|
+
...BASE_CONFIG,
|
|
21
|
+
storage: mockStorage,
|
|
22
|
+
});
|
|
23
|
+
expect(config.storage).toBe(mockStorage);
|
|
24
|
+
});
|
|
25
|
+
it("passes staticData to core config", () => {
|
|
26
|
+
const config = normalizeConfig({
|
|
27
|
+
...BASE_CONFIG,
|
|
28
|
+
staticData: mockStaticData,
|
|
29
|
+
});
|
|
30
|
+
expect(config.staticData).toBe(mockStaticData);
|
|
31
|
+
});
|
|
32
|
+
it("passes fetchTimeout to core config", () => {
|
|
33
|
+
const config = normalizeConfig({
|
|
34
|
+
...BASE_CONFIG,
|
|
35
|
+
fetchTimeout: 5000,
|
|
36
|
+
});
|
|
37
|
+
expect(config.fetchTimeout).toBe(5000);
|
|
38
|
+
});
|
|
39
|
+
it("passes retryCount to core config", () => {
|
|
40
|
+
const config = normalizeConfig({
|
|
41
|
+
...BASE_CONFIG,
|
|
42
|
+
retryCount: 3,
|
|
43
|
+
});
|
|
44
|
+
expect(config.retryCount).toBe(3);
|
|
45
|
+
});
|
|
46
|
+
it("passes all fallback fields together", () => {
|
|
47
|
+
const config = normalizeConfig({
|
|
48
|
+
...BASE_CONFIG,
|
|
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("applies default fetchTimeout when not specified", () => {
|
|
60
|
+
const config = normalizeConfig(BASE_CONFIG);
|
|
61
|
+
expect(config.fetchTimeout).toBe(10000);
|
|
62
|
+
});
|
|
63
|
+
it("applies default retryCount when not specified", () => {
|
|
64
|
+
const config = normalizeConfig(BASE_CONFIG);
|
|
65
|
+
expect(config.retryCount).toBe(1);
|
|
66
|
+
});
|
|
67
|
+
it("leaves storage undefined when not specified", () => {
|
|
68
|
+
const config = normalizeConfig(BASE_CONFIG);
|
|
69
|
+
expect(config.storage).toBeUndefined();
|
|
70
|
+
});
|
|
71
|
+
it("leaves staticData undefined when not specified", () => {
|
|
72
|
+
const config = normalizeConfig(BASE_CONFIG);
|
|
73
|
+
expect(config.staticData).toBeUndefined();
|
|
74
|
+
});
|
|
75
|
+
it("preserves Next.js-specific defaults alongside fallback fields", () => {
|
|
76
|
+
const config = normalizeConfig({
|
|
77
|
+
...BASE_CONFIG,
|
|
78
|
+
storage: mockStorage,
|
|
79
|
+
});
|
|
80
|
+
// Next.js defaults should still be set
|
|
81
|
+
expect(config.localePrefix).toBe("as-needed");
|
|
82
|
+
expect(config.cookieName).toBe("locale");
|
|
83
|
+
expect(config.manifestRevalidateSeconds).toBe(3600);
|
|
84
|
+
expect(config.messagesRevalidateSeconds).toBe(30);
|
|
85
|
+
// Fallback field should be present
|
|
86
|
+
expect(config.storage).toBe(mockStorage);
|
|
87
|
+
});
|
|
88
|
+
it("supports lazy staticData function", () => {
|
|
89
|
+
const lazyStaticData = async () => mockStaticData;
|
|
90
|
+
const config = normalizeConfig({
|
|
91
|
+
...BASE_CONFIG,
|
|
92
|
+
staticData: lazyStaticData,
|
|
93
|
+
});
|
|
94
|
+
expect(config.staticData).toBe(lazyStaticData);
|
|
95
|
+
});
|
|
96
|
+
it("passes timeZone when provided", () => {
|
|
97
|
+
const config = normalizeConfig({
|
|
98
|
+
...BASE_CONFIG,
|
|
99
|
+
timeZone: "Europe/Istanbul",
|
|
100
|
+
});
|
|
101
|
+
expect(config.timeZone).toBe("Europe/Istanbul");
|
|
102
|
+
});
|
|
103
|
+
it("leaves timeZone undefined when not specified", () => {
|
|
104
|
+
const config = normalizeConfig(BASE_CONFIG);
|
|
105
|
+
expect(config.timeZone).toBeUndefined();
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
//# sourceMappingURL=client-fallback.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client-fallback.test.js","sourceRoot":"","sources":["../../src/__tests__/client-fallback.test.ts"],"names":[],"mappings":"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"}
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
import type { LanguageOption, Messages } from "@better-i18n/core";
|
|
3
|
+
import type { I18nConfig } from "./types.js";
|
|
4
|
+
export interface BetterI18nProviderProps {
|
|
5
|
+
/** Initial locale from server (getLocale()) */
|
|
6
|
+
locale: string;
|
|
7
|
+
/** Initial messages from server (getMessages()) */
|
|
8
|
+
messages: Messages;
|
|
9
|
+
/** i18n config — only project and defaultLocale are required */
|
|
10
|
+
config: I18nConfig;
|
|
11
|
+
/**
|
|
12
|
+
* IANA time zone for date/time formatting consistency.
|
|
13
|
+
* Falls back to `config.timeZone`, then auto-detects via `Intl.DateTimeFormat`.
|
|
14
|
+
*/
|
|
15
|
+
timeZone?: string;
|
|
16
|
+
children: ReactNode;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Provider that wraps `NextIntlClientProvider` and enables instant locale
|
|
20
|
+
* switching without a server round-trip.
|
|
21
|
+
*
|
|
22
|
+
* When `useSetLocale()` is called inside this provider, it:
|
|
23
|
+
* 1. Sets a cookie (for server-side persistence on next navigation)
|
|
24
|
+
* 2. Fetches new messages from CDN on the client
|
|
25
|
+
* 3. Re-renders the tree with new locale + messages instantly
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* ```tsx
|
|
29
|
+
* // app/layout.tsx
|
|
30
|
+
* import { BetterI18nProvider } from '@better-i18n/next/client'
|
|
31
|
+
*
|
|
32
|
+
* export default async function RootLayout({ children }) {
|
|
33
|
+
* const locale = await getLocale()
|
|
34
|
+
* const messages = await getMessages()
|
|
35
|
+
*
|
|
36
|
+
* return (
|
|
37
|
+
* <BetterI18nProvider
|
|
38
|
+
* locale={locale}
|
|
39
|
+
* messages={messages}
|
|
40
|
+
* config={{ project: 'acme/dashboard', defaultLocale: 'en' }}
|
|
41
|
+
* >
|
|
42
|
+
* {children}
|
|
43
|
+
* </BetterI18nProvider>
|
|
44
|
+
* )
|
|
45
|
+
* }
|
|
46
|
+
* ```
|
|
47
|
+
*/
|
|
48
|
+
export declare function BetterI18nProvider({ locale: initialLocale, messages: initialMessages, config, timeZone: timeZoneProp, children, }: BetterI18nProviderProps): import("react/jsx-runtime").JSX.Element;
|
|
49
|
+
export type UseManifestLanguagesResult = {
|
|
50
|
+
languages: LanguageOption[];
|
|
51
|
+
isLoading: boolean;
|
|
52
|
+
error: Error | null;
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* React hook to fetch manifest languages on the client
|
|
56
|
+
*
|
|
57
|
+
* Uses `createI18nCore` from `@better-i18n/core` internally with
|
|
58
|
+
* request deduplication to prevent duplicate fetches.
|
|
59
|
+
*
|
|
60
|
+
* @example
|
|
61
|
+
* ```tsx
|
|
62
|
+
* const { languages, isLoading, error } = useManifestLanguages({
|
|
63
|
+
* project: 'acme/dashboard',
|
|
64
|
+
* defaultLocale: 'en',
|
|
65
|
+
* })
|
|
66
|
+
*
|
|
67
|
+
* if (isLoading) return <Spinner />
|
|
68
|
+
* if (error) return <Error message={error.message} />
|
|
69
|
+
*
|
|
70
|
+
* return (
|
|
71
|
+
* <select>
|
|
72
|
+
* {languages.map(lang => (
|
|
73
|
+
* <option key={lang.code} value={lang.code}>
|
|
74
|
+
* {lang.nativeName || lang.name || lang.code}
|
|
75
|
+
* </option>
|
|
76
|
+
* ))}
|
|
77
|
+
* </select>
|
|
78
|
+
* )
|
|
79
|
+
* ```
|
|
80
|
+
*/
|
|
81
|
+
export declare const useManifestLanguages: (config: I18nConfig) => UseManifestLanguagesResult;
|
|
82
|
+
/**
|
|
83
|
+
* React hook that returns a function to switch the active locale.
|
|
84
|
+
*
|
|
85
|
+
* - **With `BetterI18nProvider`**: Fetches new messages client-side from CDN
|
|
86
|
+
* and updates the UI instantly — no page refresh needed.
|
|
87
|
+
* - **Without provider (standalone)**: Pass a config object. Sets a cookie and
|
|
88
|
+
* calls `router.refresh()` for a soft server re-render.
|
|
89
|
+
*
|
|
90
|
+
* @example With provider (recommended — instant switching)
|
|
91
|
+
* ```tsx
|
|
92
|
+
* // Wrap your layout with BetterI18nProvider, then:
|
|
93
|
+
* const setLocale = useSetLocale()
|
|
94
|
+
* <button onClick={() => setLocale('tr')}>Türkçe</button>
|
|
95
|
+
* ```
|
|
96
|
+
*
|
|
97
|
+
* @example Standalone (soft refresh)
|
|
98
|
+
* ```tsx
|
|
99
|
+
* const setLocale = useSetLocale({ project: 'acme/dashboard', defaultLocale: 'en' })
|
|
100
|
+
* <button onClick={() => setLocale('tr')}>Türkçe</button>
|
|
101
|
+
* ```
|
|
102
|
+
*/
|
|
103
|
+
export declare function useSetLocale(config?: I18nConfig): (locale: string) => void;
|
|
104
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAEA,OAAO,EAOL,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAIf,OAAO,KAAK,EAAE,cAAc,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAGlE,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAU7C,MAAM,WAAW,uBAAuB;IACtC,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,mDAAmD;IACnD,QAAQ,EAAE,QAAQ,CAAC;IACnB,gEAAgE;IAChE,MAAM,EAAE,UAAU,CAAC;IACnB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,SAAS,CAAC;CACrB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,kBAAkB,CAAC,EACjC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,eAAe,EACzB,MAAM,EACN,QAAQ,EAAE,YAAY,EACtB,QAAQ,GACT,EAAE,uBAAuB,2CAmEzB;AAID,MAAM,MAAM,0BAA0B,GAAG;IACvC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,KAAK,GAAG,IAAI,CAAC;CACrB,CAAC;AAcF;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,eAAO,MAAM,oBAAoB,GAAI,QAAQ,UAAU,KAAG,0BAoFzD,CAAC;AAIF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAAC,MAAM,CAAC,EAAE,UAAU,GAAG,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAkB1E"}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createContext, useCallback, useContext, useEffect, useMemo, useState, } from "react";
|
|
4
|
+
import { useRouter } from "next/navigation";
|
|
5
|
+
import { NextIntlClientProvider } from "next-intl";
|
|
6
|
+
import { createI18nCore } from "@better-i18n/core";
|
|
7
|
+
import { normalizeConfig } from "./config.js";
|
|
8
|
+
const BetterI18nContext = createContext(null);
|
|
9
|
+
/**
|
|
10
|
+
* Provider that wraps `NextIntlClientProvider` and enables instant locale
|
|
11
|
+
* switching without a server round-trip.
|
|
12
|
+
*
|
|
13
|
+
* When `useSetLocale()` is called inside this provider, it:
|
|
14
|
+
* 1. Sets a cookie (for server-side persistence on next navigation)
|
|
15
|
+
* 2. Fetches new messages from CDN on the client
|
|
16
|
+
* 3. Re-renders the tree with new locale + messages instantly
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* // app/layout.tsx
|
|
21
|
+
* import { BetterI18nProvider } from '@better-i18n/next/client'
|
|
22
|
+
*
|
|
23
|
+
* export default async function RootLayout({ children }) {
|
|
24
|
+
* const locale = await getLocale()
|
|
25
|
+
* const messages = await getMessages()
|
|
26
|
+
*
|
|
27
|
+
* return (
|
|
28
|
+
* <BetterI18nProvider
|
|
29
|
+
* locale={locale}
|
|
30
|
+
* messages={messages}
|
|
31
|
+
* config={{ project: 'acme/dashboard', defaultLocale: 'en' }}
|
|
32
|
+
* >
|
|
33
|
+
* {children}
|
|
34
|
+
* </BetterI18nProvider>
|
|
35
|
+
* )
|
|
36
|
+
* }
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
export function BetterI18nProvider({ locale: initialLocale, messages: initialMessages, config, timeZone: timeZoneProp, children, }) {
|
|
40
|
+
// Resolve timeZone: prop > config > runtime auto-detection.
|
|
41
|
+
// This avoids next-intl's ENVIRONMENT_FALLBACK warning.
|
|
42
|
+
const timeZone = useMemo(() => timeZoneProp ??
|
|
43
|
+
config.timeZone ??
|
|
44
|
+
Intl.DateTimeFormat().resolvedOptions().timeZone, [timeZoneProp, config.timeZone]);
|
|
45
|
+
const normalized = useMemo(() => normalizeConfig(config), [
|
|
46
|
+
config.project,
|
|
47
|
+
config.defaultLocale,
|
|
48
|
+
config.cookieName,
|
|
49
|
+
config.cdnBaseUrl,
|
|
50
|
+
config.storage,
|
|
51
|
+
config.staticData,
|
|
52
|
+
config.fetchTimeout,
|
|
53
|
+
config.retryCount,
|
|
54
|
+
]);
|
|
55
|
+
// Provider-level memoized core instance — reused by setLocale
|
|
56
|
+
const i18nCore = useMemo(() => createI18nCore(normalized), [normalized]);
|
|
57
|
+
const [locale, setLocaleState] = useState(initialLocale);
|
|
58
|
+
const [messages, setMessages] = useState(initialMessages);
|
|
59
|
+
// Sync with server-provided values when they change (e.g. navigation to a new page)
|
|
60
|
+
useEffect(() => {
|
|
61
|
+
setLocaleState(initialLocale);
|
|
62
|
+
setMessages(initialMessages);
|
|
63
|
+
}, [initialLocale, initialMessages]);
|
|
64
|
+
const setLocale = useCallback(async (newLocale) => {
|
|
65
|
+
if (newLocale === locale)
|
|
66
|
+
return;
|
|
67
|
+
// 1. Set cookie for server-side persistence
|
|
68
|
+
document.cookie = `${normalized.cookieName}=${newLocale}; path=/; max-age=31536000; samesite=lax`;
|
|
69
|
+
// 2. Fetch new messages with full fallback chain (CDN → Storage → StaticData)
|
|
70
|
+
try {
|
|
71
|
+
const newMessages = await i18nCore.getMessages(newLocale);
|
|
72
|
+
// 3. Instant client-side update — no server round-trip
|
|
73
|
+
setLocaleState(newLocale);
|
|
74
|
+
setMessages(newMessages);
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.error("[better-i18n] Client-side locale switch failed, falling back to refresh:", err);
|
|
78
|
+
// Fallback: let the server handle it
|
|
79
|
+
window.location.reload();
|
|
80
|
+
}
|
|
81
|
+
}, [locale, normalized.cookieName, i18nCore]);
|
|
82
|
+
return (_jsx(BetterI18nContext.Provider, { value: { setLocale }, children: _jsx(NextIntlClientProvider, { locale: locale, messages: messages, timeZone: timeZone, children: children }) }));
|
|
83
|
+
}
|
|
84
|
+
// Client-side request deduplication cache
|
|
85
|
+
const clientCache = new Map();
|
|
86
|
+
const getCacheKey = (project, cdnBaseUrl) => `${cdnBaseUrl || "https://cdn.better-i18n.com"}|${project}`;
|
|
87
|
+
/**
|
|
88
|
+
* React hook to fetch manifest languages on the client
|
|
89
|
+
*
|
|
90
|
+
* Uses `createI18nCore` from `@better-i18n/core` internally with
|
|
91
|
+
* request deduplication to prevent duplicate fetches.
|
|
92
|
+
*
|
|
93
|
+
* @example
|
|
94
|
+
* ```tsx
|
|
95
|
+
* const { languages, isLoading, error } = useManifestLanguages({
|
|
96
|
+
* project: 'acme/dashboard',
|
|
97
|
+
* defaultLocale: 'en',
|
|
98
|
+
* })
|
|
99
|
+
*
|
|
100
|
+
* if (isLoading) return <Spinner />
|
|
101
|
+
* if (error) return <Error message={error.message} />
|
|
102
|
+
*
|
|
103
|
+
* return (
|
|
104
|
+
* <select>
|
|
105
|
+
* {languages.map(lang => (
|
|
106
|
+
* <option key={lang.code} value={lang.code}>
|
|
107
|
+
* {lang.nativeName || lang.name || lang.code}
|
|
108
|
+
* </option>
|
|
109
|
+
* ))}
|
|
110
|
+
* </select>
|
|
111
|
+
* )
|
|
112
|
+
* ```
|
|
113
|
+
*/
|
|
114
|
+
export const useManifestLanguages = (config) => {
|
|
115
|
+
const normalized = useMemo(() => normalizeConfig(config), [
|
|
116
|
+
config.project,
|
|
117
|
+
config.defaultLocale,
|
|
118
|
+
config.cdnBaseUrl,
|
|
119
|
+
config.debug,
|
|
120
|
+
config.logLevel,
|
|
121
|
+
config.storage,
|
|
122
|
+
config.staticData,
|
|
123
|
+
config.fetchTimeout,
|
|
124
|
+
config.retryCount,
|
|
125
|
+
]);
|
|
126
|
+
const i18nCore = useMemo(() => createI18nCore(normalized), [normalized]);
|
|
127
|
+
const cacheKey = getCacheKey(normalized.project, normalized.cdnBaseUrl);
|
|
128
|
+
const cached = clientCache.get(cacheKey);
|
|
129
|
+
const [languages, setLanguages] = useState(cached?.data ?? []);
|
|
130
|
+
const [isLoading, setIsLoading] = useState(!cached?.data);
|
|
131
|
+
const [error, setError] = useState(cached?.error ?? null);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
let isMounted = true;
|
|
134
|
+
const run = async () => {
|
|
135
|
+
const entry = clientCache.get(cacheKey) ?? {};
|
|
136
|
+
// Return cached data if available
|
|
137
|
+
if (entry.data) {
|
|
138
|
+
if (isMounted) {
|
|
139
|
+
setLanguages(entry.data);
|
|
140
|
+
setIsLoading(false);
|
|
141
|
+
}
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
// Deduplicate in-flight requests
|
|
145
|
+
if (!entry.promise) {
|
|
146
|
+
entry.promise = i18nCore
|
|
147
|
+
.getLanguages()
|
|
148
|
+
.then((langs) => {
|
|
149
|
+
clientCache.set(cacheKey, { data: langs });
|
|
150
|
+
return langs;
|
|
151
|
+
})
|
|
152
|
+
.catch((err) => {
|
|
153
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
154
|
+
clientCache.set(cacheKey, { error });
|
|
155
|
+
throw error;
|
|
156
|
+
});
|
|
157
|
+
clientCache.set(cacheKey, entry);
|
|
158
|
+
}
|
|
159
|
+
try {
|
|
160
|
+
const langs = await entry.promise;
|
|
161
|
+
if (isMounted) {
|
|
162
|
+
setLanguages(langs);
|
|
163
|
+
setError(null);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch (err) {
|
|
167
|
+
if (isMounted) {
|
|
168
|
+
setError(err instanceof Error ? err : new Error(String(err)));
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
finally {
|
|
172
|
+
if (isMounted) {
|
|
173
|
+
setIsLoading(false);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
run();
|
|
178
|
+
return () => {
|
|
179
|
+
isMounted = false;
|
|
180
|
+
};
|
|
181
|
+
}, [cacheKey, i18nCore]);
|
|
182
|
+
return { languages, isLoading, error };
|
|
183
|
+
};
|
|
184
|
+
// ─── useSetLocale ────────────────────────────────────────────────────
|
|
185
|
+
/**
|
|
186
|
+
* React hook that returns a function to switch the active locale.
|
|
187
|
+
*
|
|
188
|
+
* - **With `BetterI18nProvider`**: Fetches new messages client-side from CDN
|
|
189
|
+
* and updates the UI instantly — no page refresh needed.
|
|
190
|
+
* - **Without provider (standalone)**: Pass a config object. Sets a cookie and
|
|
191
|
+
* calls `router.refresh()` for a soft server re-render.
|
|
192
|
+
*
|
|
193
|
+
* @example With provider (recommended — instant switching)
|
|
194
|
+
* ```tsx
|
|
195
|
+
* // Wrap your layout with BetterI18nProvider, then:
|
|
196
|
+
* const setLocale = useSetLocale()
|
|
197
|
+
* <button onClick={() => setLocale('tr')}>Türkçe</button>
|
|
198
|
+
* ```
|
|
199
|
+
*
|
|
200
|
+
* @example Standalone (soft refresh)
|
|
201
|
+
* ```tsx
|
|
202
|
+
* const setLocale = useSetLocale({ project: 'acme/dashboard', defaultLocale: 'en' })
|
|
203
|
+
* <button onClick={() => setLocale('tr')}>Türkçe</button>
|
|
204
|
+
* ```
|
|
205
|
+
*/
|
|
206
|
+
export function useSetLocale(config) {
|
|
207
|
+
const ctx = useContext(BetterI18nContext);
|
|
208
|
+
// If BetterI18nProvider is in the tree, use instant client-side switching
|
|
209
|
+
if (ctx) {
|
|
210
|
+
return ctx.setLocale;
|
|
211
|
+
}
|
|
212
|
+
// Standalone fallback: cookie + router.refresh()
|
|
213
|
+
if (!config) {
|
|
214
|
+
throw new Error("[better-i18n] useSetLocale() requires either a <BetterI18nProvider> ancestor " +
|
|
215
|
+
"or a config argument. Use useSetLocale({ project, defaultLocale }) for standalone mode.");
|
|
216
|
+
}
|
|
217
|
+
// eslint-disable-next-line react-hooks/rules-of-hooks
|
|
218
|
+
return useStandaloneSetLocale(config);
|
|
219
|
+
}
|
|
220
|
+
/** Internal standalone hook — cookie + router.refresh() */
|
|
221
|
+
function useStandaloneSetLocale(config) {
|
|
222
|
+
const normalized = useMemo(() => normalizeConfig(config), [
|
|
223
|
+
config.project,
|
|
224
|
+
config.defaultLocale,
|
|
225
|
+
config.cookieName,
|
|
226
|
+
]);
|
|
227
|
+
const router = useRouter();
|
|
228
|
+
return useCallback((locale) => {
|
|
229
|
+
document.cookie = `${normalized.cookieName}=${locale}; path=/; max-age=31536000; samesite=lax`;
|
|
230
|
+
router.refresh();
|
|
231
|
+
}, [normalized.cookieName, router]);
|
|
232
|
+
}
|
|
233
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.tsx"],"names":[],"mappings":"AAAA,YAAY,CAAC;;AAEb,OAAO,EACL,aAAa,EACb,WAAW,EACX,UAAU,EACV,SAAS,EACT,OAAO,EACP,QAAQ,GAET,MAAM,OAAO,CAAC;AACf,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,EAAE,sBAAsB,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAGnD,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAS9C,MAAM,iBAAiB,GAAG,aAAa,CAAgC,IAAI,CAAC,CAAC;AAiB7E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,MAAM,UAAU,kBAAkB,CAAC,EACjC,MAAM,EAAE,aAAa,EACrB,QAAQ,EAAE,eAAe,EACzB,MAAM,EACN,QAAQ,EAAE,YAAY,EACtB,QAAQ,GACgB;IACxB,4DAA4D;IAC5D,wDAAwD;IACxD,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CACH,YAAY;QACZ,MAAM,CAAC,QAAQ;QACf,IAAI,CAAC,cAAc,EAAE,CAAC,eAAe,EAAE,CAAC,QAAQ,EAClD,CAAC,YAAY,EAAE,MAAM,CAAC,QAAQ,CAAC,CAChC,CAAC;IAEF,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE;QACxD,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,aAAa;QACpB,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,UAAU;KAClB,CAAC,CAAC;IAEH,8DAA8D;IAC9D,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,EAChC,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,GAAG,QAAQ,CAAC,aAAa,CAAC,CAAC;IACzD,MAAM,CAAC,QAAQ,EAAE,WAAW,CAAC,GAAG,QAAQ,CAAW,eAAe,CAAC,CAAC;IAEpE,oFAAoF;IACpF,SAAS,CAAC,GAAG,EAAE;QACb,cAAc,CAAC,aAAa,CAAC,CAAC;QAC9B,WAAW,CAAC,eAAe,CAAC,CAAC;IAC/B,CAAC,EAAE,CAAC,aAAa,EAAE,eAAe,CAAC,CAAC,CAAC;IAErC,MAAM,SAAS,GAAG,WAAW,CAC3B,KAAK,EAAE,SAAiB,EAAE,EAAE;QAC1B,IAAI,SAAS,KAAK,MAAM;YAAE,OAAO;QAEjC,4CAA4C;QAC5C,QAAQ,CAAC,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,IAAI,SAAS,0CAA0C,CAAC;QAElG,8EAA8E;QAC9E,IAAI,CAAC;YACH,MAAM,WAAW,GAAG,MAAM,QAAQ,CAAC,WAAW,CAAC,SAAS,CAAC,CAAC;YAE1D,uDAAuD;YACvD,cAAc,CAAC,SAAS,CAAC,CAAC;YAC1B,WAAW,CAAC,WAAW,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,OAAO,CAAC,KAAK,CAAC,0EAA0E,EAAE,GAAG,CAAC,CAAC;YAC/F,qCAAqC;YACrC,MAAM,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QAC3B,CAAC;IACH,CAAC,EACD,CAAC,MAAM,EAAE,UAAU,CAAC,UAAU,EAAE,QAAQ,CAAC,CAC1C,CAAC;IAEF,OAAO,CACL,KAAC,iBAAiB,CAAC,QAAQ,IAAC,KAAK,EAAE,EAAE,SAAS,EAAE,YAC9C,KAAC,sBAAsB,IAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,YAC3E,QAAQ,GACc,GACE,CAC9B,CAAC;AACJ,CAAC;AAgBD,0CAA0C;AAC1C,MAAM,WAAW,GAAG,IAAI,GAAG,EAA4B,CAAC;AAExD,MAAM,WAAW,GAAG,CAAC,OAAe,EAAE,UAAmB,EAAE,EAAE,CAC3D,GAAG,UAAU,IAAI,6BAA6B,IAAI,OAAO,EAAE,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,MAAkB,EAA8B,EAAE;IACrF,MAAM,UAAU,GAAG,OAAO,CACxB,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,EAC7B;QACE,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,aAAa;QACpB,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,KAAK;QACZ,MAAM,CAAC,QAAQ;QACf,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,UAAU;QACjB,MAAM,CAAC,YAAY;QACnB,MAAM,CAAC,UAAU;KAClB,CACF,CAAC;IAEF,MAAM,QAAQ,GAAG,OAAO,CACtB,GAAG,EAAE,CAAC,cAAc,CAAC,UAAU,CAAC,EAChC,CAAC,UAAU,CAAC,CACb,CAAC;IAEF,MAAM,QAAQ,GAAG,WAAW,CAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,UAAU,CAAC,CAAC;IACxE,MAAM,MAAM,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;IAEzC,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAmB,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC,CAAC;IACjF,MAAM,CAAC,SAAS,EAAE,YAAY,CAAC,GAAG,QAAQ,CAAC,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;IAC1D,MAAM,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,QAAQ,CAAe,MAAM,EAAE,KAAK,IAAI,IAAI,CAAC,CAAC;IAExE,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,SAAS,GAAG,IAAI,CAAC;QAErB,MAAM,GAAG,GAAG,KAAK,IAAI,EAAE;YACrB,MAAM,KAAK,GAAG,WAAW,CAAC,GAAG,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;YAE9C,kCAAkC;YAClC,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC;gBACf,IAAI,SAAS,EAAE,CAAC;oBACd,YAAY,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;oBACzB,YAAY,CAAC,KAAK,CAAC,CAAC;gBACtB,CAAC;gBACD,OAAO;YACT,CAAC;YAED,iCAAiC;YACjC,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;gBACnB,KAAK,CAAC,OAAO,GAAG,QAAQ;qBACrB,YAAY,EAAE;qBACd,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;oBACd,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;oBAC3C,OAAO,KAAK,CAAC;gBACf,CAAC,CAAC;qBACD,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;oBACb,MAAM,KAAK,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;oBAClE,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;oBACrC,MAAM,KAAK,CAAC;gBACd,CAAC,CAAC,CAAC;gBACL,WAAW,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;YACnC,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,KAAK,GAAG,MAAM,KAAK,CAAC,OAAO,CAAC;gBAClC,IAAI,SAAS,EAAE,CAAC;oBACd,YAAY,CAAC,KAAK,CAAC,CAAC;oBACpB,QAAQ,CAAC,IAAI,CAAC,CAAC;gBACjB,CAAC;YACH,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,IAAI,SAAS,EAAE,CAAC;oBACd,QAAQ,CAAC,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;gBAChE,CAAC;YACH,CAAC;oBAAS,CAAC;gBACT,IAAI,SAAS,EAAE,CAAC;oBACd,YAAY,CAAC,KAAK,CAAC,CAAC;gBACtB,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,GAAG,EAAE,CAAC;QAEN,OAAO,GAAG,EAAE;YACV,SAAS,GAAG,KAAK,CAAC;QACpB,CAAC,CAAC;IACJ,CAAC,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC,CAAC;IAEzB,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC;AACzC,CAAC,CAAC;AAEF,wEAAwE;AAExE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,UAAU,YAAY,CAAC,MAAmB;IAC9C,MAAM,GAAG,GAAG,UAAU,CAAC,iBAAiB,CAAC,CAAC;IAE1C,0EAA0E;IAC1E,IAAI,GAAG,EAAE,CAAC;QACR,OAAO,GAAG,CAAC,SAAS,CAAC;IACvB,CAAC;IAED,iDAAiD;IACjD,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CACb,+EAA+E;YAC/E,yFAAyF,CAC1F,CAAC;IACJ,CAAC;IAED,sDAAsD;IACtD,OAAO,sBAAsB,CAAC,MAAM,CAAC,CAAC;AACxC,CAAC;AAED,2DAA2D;AAC3D,SAAS,sBAAsB,CAAC,MAAkB;IAChD,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE;QACxD,MAAM,CAAC,OAAO;QACd,MAAM,CAAC,aAAa;QACpB,MAAM,CAAC,UAAU;KAClB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAE3B,OAAO,WAAW,CAChB,CAAC,MAAc,EAAE,EAAE;QACjB,QAAQ,CAAC,MAAM,GAAG,GAAG,UAAU,CAAC,UAAU,IAAI,MAAM,0CAA0C,CAAC;QAC/F,MAAM,CAAC,OAAO,EAAE,CAAC;IACnB,CAAC,EACD,CAAC,UAAU,CAAC,UAAU,EAAE,MAAM,CAAC,CAChC,CAAC;AACJ,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { parseProject } from "@better-i18n/core";
|
|
2
|
+
import type { I18nConfig, NormalizedConfig } from "./types.js";
|
|
3
|
+
export { parseProject };
|
|
4
|
+
/**
|
|
5
|
+
* Normalize Next.js i18n config with defaults
|
|
6
|
+
*/
|
|
7
|
+
export declare const normalizeConfig: (config: I18nConfig) => NormalizedConfig;
|
|
8
|
+
/**
|
|
9
|
+
* Get the project base URL on CDN
|
|
10
|
+
*/
|
|
11
|
+
export declare const getProjectBaseUrl: (config: NormalizedConfig) => string;
|
|
12
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EAGb,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,UAAU,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAG/D,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,QAAQ,UAAU,KAAG,gBAsBpD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,iBAAiB,GAAI,QAAQ,gBAAgB,KAAG,MAC9B,CAAC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { parseProject, getProjectBaseUrl as coreGetProjectBaseUrl, normalizeConfig as coreNormalizeConfig, } from "@better-i18n/core";
|
|
2
|
+
// Re-export parseProject from core
|
|
3
|
+
export { parseProject };
|
|
4
|
+
/**
|
|
5
|
+
* Normalize Next.js i18n config with defaults
|
|
6
|
+
*/
|
|
7
|
+
export const normalizeConfig = (config) => {
|
|
8
|
+
// Destructure Next.js-specific fields, pass the rest to core as-is.
|
|
9
|
+
// This way, when core adds new fields, this file doesn't need updating.
|
|
10
|
+
const { localePrefix, cookieName, manifestRevalidateSeconds, messagesRevalidateSeconds, timeZone, ...coreFields } = config;
|
|
11
|
+
const coreConfig = coreNormalizeConfig(coreFields);
|
|
12
|
+
return {
|
|
13
|
+
...coreConfig,
|
|
14
|
+
localePrefix: localePrefix ?? "as-needed",
|
|
15
|
+
cookieName: cookieName ?? "locale",
|
|
16
|
+
manifestRevalidateSeconds: manifestRevalidateSeconds ?? 3600,
|
|
17
|
+
messagesRevalidateSeconds: messagesRevalidateSeconds ?? 30,
|
|
18
|
+
timeZone,
|
|
19
|
+
};
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Get the project base URL on CDN
|
|
23
|
+
*/
|
|
24
|
+
export const getProjectBaseUrl = (config) => coreGetProjectBaseUrl(config);
|
|
25
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,YAAY,EACZ,iBAAiB,IAAI,qBAAqB,EAC1C,eAAe,IAAI,mBAAmB,GACvC,MAAM,mBAAmB,CAAC;AAG3B,mCAAmC;AACnC,OAAO,EAAE,YAAY,EAAE,CAAC;AAExB;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,MAAkB,EAAoB,EAAE;IACtE,oEAAoE;IACpE,wEAAwE;IACxE,MAAM,EACJ,YAAY,EACZ,UAAU,EACV,yBAAyB,EACzB,yBAAyB,EACzB,QAAQ,EACR,GAAG,UAAU,EACd,GAAG,MAAM,CAAC;IAEX,MAAM,UAAU,GAAG,mBAAmB,CAAC,UAAU,CAAC,CAAC;IAEnD,OAAO;QACL,GAAG,UAAU;QACb,YAAY,EAAE,YAAY,IAAI,WAAW;QACzC,UAAU,EAAE,UAAU,IAAI,QAAQ;QAClC,yBAAyB,EAAE,yBAAyB,IAAI,IAAI;QAC5D,yBAAyB,EAAE,yBAAyB,IAAI,EAAE;QAC1D,QAAQ;KACT,CAAC;AACJ,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,CAAC,MAAM,iBAAiB,GAAG,CAAC,MAAwB,EAAU,EAAE,CACpE,qBAAqB,CAAC,MAAM,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { I18nConfig } from "./types.js";
|
|
2
|
+
import { createBetterI18nMiddleware, composeMiddleware, type MiddlewareCallback } from "./middleware.js";
|
|
3
|
+
/**
|
|
4
|
+
* Create a complete i18n setup for Next.js
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```ts
|
|
8
|
+
* // i18n/config.ts
|
|
9
|
+
* import { createI18n } from '@better-i18n/next'
|
|
10
|
+
*
|
|
11
|
+
* export const i18n = createI18n({
|
|
12
|
+
* project: 'acme/dashboard',
|
|
13
|
+
* defaultLocale: 'en',
|
|
14
|
+
* localePrefix: 'always',
|
|
15
|
+
* })
|
|
16
|
+
*
|
|
17
|
+
* // i18n/request.ts
|
|
18
|
+
* export default i18n.requestConfig
|
|
19
|
+
*
|
|
20
|
+
* // middleware.ts (simple)
|
|
21
|
+
* export default i18n.middleware
|
|
22
|
+
*
|
|
23
|
+
* // middleware.ts (with auth - Clerk-style)
|
|
24
|
+
* export default i18n.betterMiddleware(async (request, { locale }) => {
|
|
25
|
+
* if (needsLogin) {
|
|
26
|
+
* return NextResponse.redirect(new URL(`/${locale}/login`, request.url));
|
|
27
|
+
* }
|
|
28
|
+
* });
|
|
29
|
+
* ```
|
|
30
|
+
*/
|
|
31
|
+
export declare const createI18n: (config: I18nConfig) => {
|
|
32
|
+
config: import("./types.js").NormalizedConfig;
|
|
33
|
+
requestConfig: (params: import("next-intl/server").GetRequestConfigParams) => import("next-intl/server").RequestConfig | Promise<import("next-intl/server").RequestConfig>;
|
|
34
|
+
/** @deprecated Use betterMiddleware() for Clerk-style callback support */
|
|
35
|
+
middleware: (request: import("next/server.js").NextRequest) => Promise<import("next/server.js").NextResponse<unknown>>;
|
|
36
|
+
proxy: (request: import("next/server.js").NextRequest) => Promise<import("next/server.js").NextResponse<unknown>>;
|
|
37
|
+
getManifest: (options?: {
|
|
38
|
+
forceRefresh?: boolean;
|
|
39
|
+
}) => Promise<import("@better-i18n/core").ManifestResponse>;
|
|
40
|
+
getLocales: () => Promise<string[]>;
|
|
41
|
+
getMessages: (locale: string) => Promise<import("@better-i18n/core").Messages>;
|
|
42
|
+
/**
|
|
43
|
+
* Create middleware with optional Clerk-style callback for auth integration
|
|
44
|
+
*
|
|
45
|
+
* @example Simple usage
|
|
46
|
+
* ```ts
|
|
47
|
+
* export default i18n.betterMiddleware();
|
|
48
|
+
* ```
|
|
49
|
+
*
|
|
50
|
+
* @example With auth callback
|
|
51
|
+
* ```ts
|
|
52
|
+
* export default i18n.betterMiddleware(async (request, { locale, response }) => {
|
|
53
|
+
* if (needsLogin) {
|
|
54
|
+
* return NextResponse.redirect(new URL(`/${locale}/login`, request.url));
|
|
55
|
+
* }
|
|
56
|
+
* // Return nothing = i18n response is used (headers preserved!)
|
|
57
|
+
* });
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
betterMiddleware: (callback?: MiddlewareCallback) => (request: import("next/server.js").NextRequest) => Promise<import("next/server.js").NextResponse>;
|
|
61
|
+
};
|
|
62
|
+
export { createBetterI18nMiddleware, composeMiddleware };
|
|
63
|
+
export type { MiddlewareContext, MiddlewareCallback } from "./middleware.js";
|
|
64
|
+
export { createNextI18nCore } from "./server.js";
|
|
65
|
+
export { BetterI18nProvider, useManifestLanguages, useSetLocale } from "./client.js";
|
|
66
|
+
export type { BetterI18nProviderProps } from "./client.js";
|
|
67
|
+
export type { I18nConfig, LanguageOption, Locale, LocalePrefix, LogLevel, ManifestLanguage, ManifestResponse, Messages, } from "./types.js";
|
|
68
|
+
export type { I18nMiddlewareConfig } from "@better-i18n/core";
|
|
69
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAM7C,OAAO,EAGL,0BAA0B,EAC1B,iBAAiB,EACjB,KAAK,kBAAkB,EACxB,MAAM,iBAAiB,CAAC;AAEzB;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,eAAO,MAAM,UAAU,GAAI,QAAQ,UAAU;;;IAOzC,0EAA0E;;;;oBAmE8nC,CAAC;;;;IA5DzsC;;;;;;;;;;;;;;;;;OAiBG;kCAC2B,kBAAkB;CAgBnD,CAAC;AAGF,OAAO,EAAE,0BAA0B,EAAE,iBAAiB,EAAE,CAAC;AACzD,YAAY,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAG7E,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGjD,OAAO,EAAE,kBAAkB,EAAE,oBAAoB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACrF,YAAY,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC;AAG3D,YAAY,EACV,UAAU,EACV,cAAc,EACd,MAAM,EACN,YAAY,EACZ,QAAQ,EACR,gBAAgB,EAChB,gBAAgB,EAChB,QAAQ,GACT,MAAM,YAAY,CAAC;AAEpB,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC"}
|