@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
package/dist/provider.js
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
3
|
+
import { createI18nCore } from "@better-i18n/core";
|
|
4
|
+
import { useEffect, useMemo, useState } from "react";
|
|
5
|
+
import { IntlProvider } from "use-intl";
|
|
6
|
+
import { BetterI18nContext } from "./context.js";
|
|
7
|
+
/**
|
|
8
|
+
* Provider component that combines Better i18n CDN with use-intl
|
|
9
|
+
*
|
|
10
|
+
* The locale is controlled externally (from URL/router). Use useLocaleRouter()
|
|
11
|
+
* for locale switching with proper router integration.
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* // Basic usage (CSR - fetches messages on client)
|
|
16
|
+
* function App() {
|
|
17
|
+
* return (
|
|
18
|
+
* <BetterI18nProvider
|
|
19
|
+
* project="acme/dashboard"
|
|
20
|
+
* locale="en"
|
|
21
|
+
* >
|
|
22
|
+
* <MyComponent />
|
|
23
|
+
* </BetterI18nProvider>
|
|
24
|
+
* )
|
|
25
|
+
* }
|
|
26
|
+
*
|
|
27
|
+
* // TanStack Router SSR usage (pre-loaded messages from loader)
|
|
28
|
+
* function RootComponent() {
|
|
29
|
+
* const { messages, locale } = Route.useLoaderData()
|
|
30
|
+
* return (
|
|
31
|
+
* <BetterI18nProvider
|
|
32
|
+
* project="acme/dashboard"
|
|
33
|
+
* locale={locale}
|
|
34
|
+
* messages={messages}
|
|
35
|
+
* >
|
|
36
|
+
* <Outlet />
|
|
37
|
+
* </BetterI18nProvider>
|
|
38
|
+
* )
|
|
39
|
+
* }
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function BetterI18nProvider({ children, project, locale: propLocale, messages: propMessages, timeZone, now, onError, cdnBaseUrl, debug, logLevel, fetch: customFetch, storage, staticData, fetchTimeout, retryCount, initialLanguages, getMessageFallback: customGetMessageFallback, }) {
|
|
43
|
+
// Locale is controlled by props (from URL/router)
|
|
44
|
+
const locale = propLocale;
|
|
45
|
+
// clientMessages is only used in CSR mode (when propMessages is not provided).
|
|
46
|
+
// In SSG/SSR mode, propMessages comes from the loader and is used directly —
|
|
47
|
+
// synchronous derivation avoids the useEffect delay that causes white flash on hydration.
|
|
48
|
+
const [clientMessages, setClientMessages] = useState();
|
|
49
|
+
const messages = propMessages ?? clientMessages;
|
|
50
|
+
const [languages, setLanguages] = useState(initialLanguages ?? []);
|
|
51
|
+
const [isLoadingMessages, setIsLoadingMessages] = useState(propMessages === undefined);
|
|
52
|
+
const [isLoadingLanguages, setIsLoadingLanguages] = useState(!initialLanguages);
|
|
53
|
+
// Create i18n core instance
|
|
54
|
+
const i18nCore = useMemo(() => createI18nCore({
|
|
55
|
+
project,
|
|
56
|
+
defaultLocale: locale,
|
|
57
|
+
cdnBaseUrl,
|
|
58
|
+
debug,
|
|
59
|
+
logLevel,
|
|
60
|
+
fetch: customFetch,
|
|
61
|
+
storage,
|
|
62
|
+
staticData,
|
|
63
|
+
fetchTimeout,
|
|
64
|
+
retryCount,
|
|
65
|
+
}), [project, locale, cdnBaseUrl, debug, logLevel, customFetch, storage, staticData, fetchTimeout, retryCount]);
|
|
66
|
+
// Load languages on mount — skip if SSR already provided them
|
|
67
|
+
useEffect(() => {
|
|
68
|
+
if (initialLanguages)
|
|
69
|
+
return;
|
|
70
|
+
let cancelled = false;
|
|
71
|
+
const loadLanguages = async () => {
|
|
72
|
+
try {
|
|
73
|
+
const langs = await i18nCore.getLanguages();
|
|
74
|
+
if (!cancelled) {
|
|
75
|
+
setLanguages(langs);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
console.error("[better-i18n] Failed to load languages:", error);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
if (!cancelled) {
|
|
83
|
+
setIsLoadingLanguages(false);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
loadLanguages();
|
|
88
|
+
return () => {
|
|
89
|
+
cancelled = true;
|
|
90
|
+
};
|
|
91
|
+
}, [i18nCore, initialLanguages]);
|
|
92
|
+
// Load messages when locale changes and no pre-loaded messages available
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
// Skip if we already have messages for this render
|
|
95
|
+
if (propMessages) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
let cancelled = false;
|
|
99
|
+
const loadMessages = async () => {
|
|
100
|
+
setIsLoadingMessages(true);
|
|
101
|
+
try {
|
|
102
|
+
const msgs = await i18nCore.getMessages(locale);
|
|
103
|
+
if (!cancelled) {
|
|
104
|
+
setClientMessages(msgs);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
catch (error) {
|
|
108
|
+
console.error(`[better-i18n] Failed to load messages for locale "${locale}":`, error);
|
|
109
|
+
}
|
|
110
|
+
finally {
|
|
111
|
+
if (!cancelled) {
|
|
112
|
+
setIsLoadingMessages(false);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
loadMessages();
|
|
117
|
+
return () => {
|
|
118
|
+
cancelled = true;
|
|
119
|
+
};
|
|
120
|
+
}, [locale, i18nCore, propMessages]);
|
|
121
|
+
// Context value (read-only locale - use useLocaleRouter for navigation)
|
|
122
|
+
const contextValue = useMemo(() => ({
|
|
123
|
+
locale,
|
|
124
|
+
languages,
|
|
125
|
+
isLoadingLanguages,
|
|
126
|
+
isLoadingMessages,
|
|
127
|
+
project,
|
|
128
|
+
}), [locale, languages, isLoadingLanguages, isLoadingMessages, project]);
|
|
129
|
+
if (!messages) {
|
|
130
|
+
// Render children with empty messages instead of blanking the screen.
|
|
131
|
+
// This converts a full DOM structural mismatch (null vs full tree)
|
|
132
|
+
// into a text content mismatch — React recovers silently and re-renders
|
|
133
|
+
// once the loader delivers real messages.
|
|
134
|
+
return (_jsx(BetterI18nContext.Provider, { value: contextValue, children: _jsx(IntlProvider, { locale: locale, messages: {}, timeZone: timeZone, onError: () => { }, getMessageFallback: customGetMessageFallback, children: children }) }));
|
|
135
|
+
}
|
|
136
|
+
return (_jsx(BetterI18nContext.Provider, { value: contextValue, children: _jsx(IntlProvider, { locale: locale, messages: messages, timeZone: timeZone, now: now, onError: onError ?? (() => { }), getMessageFallback: customGetMessageFallback, children: children }) }));
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../src/provider.tsx"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { I18nCoreConfig, LanguageOption } from "@better-i18n/core";
|
|
2
|
+
import { createFormatter } from "use-intl/core";
|
|
3
|
+
import type { Messages } from "./types.js";
|
|
4
|
+
export interface GetMessagesConfig extends Omit<I18nCoreConfig, "defaultLocale"> {
|
|
5
|
+
/**
|
|
6
|
+
* Locale to fetch messages for
|
|
7
|
+
*/
|
|
8
|
+
locale: string;
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* Fetch messages for a locale (server-side)
|
|
12
|
+
*/
|
|
13
|
+
export declare function getMessages(config: GetMessagesConfig): Promise<Messages>;
|
|
14
|
+
/**
|
|
15
|
+
* Fetch available locales (server-side)
|
|
16
|
+
*/
|
|
17
|
+
export declare function getLocales(config: Omit<I18nCoreConfig, "defaultLocale"> & {
|
|
18
|
+
defaultLocale?: string;
|
|
19
|
+
}): Promise<string[]>;
|
|
20
|
+
/**
|
|
21
|
+
* Fetch available languages with metadata (server-side)
|
|
22
|
+
*/
|
|
23
|
+
export declare function getLanguages(config: Omit<I18nCoreConfig, "defaultLocale"> & {
|
|
24
|
+
defaultLocale?: string;
|
|
25
|
+
}): Promise<LanguageOption[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Create a translator function for use outside React (server-side)
|
|
28
|
+
*/
|
|
29
|
+
export declare function createServerTranslator(config: {
|
|
30
|
+
locale: string;
|
|
31
|
+
messages: Messages;
|
|
32
|
+
namespace?: string;
|
|
33
|
+
}): import("use-intl")._Translator<{
|
|
34
|
+
[x: string]: any;
|
|
35
|
+
}, string>;
|
|
36
|
+
/**
|
|
37
|
+
* Create a formatter for use outside React (server-side)
|
|
38
|
+
*/
|
|
39
|
+
export declare function createServerFormatter(config: {
|
|
40
|
+
locale: string;
|
|
41
|
+
timeZone?: string;
|
|
42
|
+
now?: Date;
|
|
43
|
+
}): ReturnType<typeof createFormatter>;
|
|
44
|
+
export { createTranslator, createFormatter } from "use-intl/core";
|
|
45
|
+
/**
|
|
46
|
+
* Parse an RFC 5646 Accept-Language header value into a prioritized list of language tags.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* parseAcceptLanguage("tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7")
|
|
50
|
+
* // → ["tr-TR", "tr", "en-US", "en"]
|
|
51
|
+
*/
|
|
52
|
+
export declare function parseAcceptLanguage(header: string | null | undefined): string[];
|
|
53
|
+
/**
|
|
54
|
+
* Find the best matching locale from a parsed language list against available locales.
|
|
55
|
+
* Matching strategy (in order):
|
|
56
|
+
* 1. Exact match: "tr-TR" → "tr-TR"
|
|
57
|
+
* 2. Base language match: "tr-TR" → "tr"
|
|
58
|
+
* 3. Region expansion: "tr" matches "tr-TR" (first available variant)
|
|
59
|
+
*
|
|
60
|
+
* Returns null if no match found.
|
|
61
|
+
*/
|
|
62
|
+
export declare function matchLocale(languages: string[], availableLocales: string[]): string | null;
|
|
63
|
+
/**
|
|
64
|
+
* Detect best locale from Accept-Language header (server) or navigator.languages (client).
|
|
65
|
+
*
|
|
66
|
+
* - Server (Web Request): reads `request.headers.get('accept-language')`
|
|
67
|
+
* - Server (Express/Hono/etc.): pass `acceptLanguage` header string directly
|
|
68
|
+
* - Client: reads `navigator.languages` / `navigator.language`
|
|
69
|
+
*
|
|
70
|
+
* Falls back to `defaultLocale` if no match is found.
|
|
71
|
+
*/
|
|
72
|
+
export declare function detectLocale(options: {
|
|
73
|
+
request?: Request | null;
|
|
74
|
+
/** Directly pass an Accept-Language header string (e.g. from Express/Hono req.headers) */
|
|
75
|
+
acceptLanguage?: string | null;
|
|
76
|
+
availableLocales: string[];
|
|
77
|
+
defaultLocale: string;
|
|
78
|
+
}): string;
|
|
79
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAoB,MAAM,eAAe,CAAC;AAClE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,WAAW,iBAAkB,SAAQ,IAAI,CAC7C,cAAc,EACd,eAAe,CAChB;IACC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,iBAAiB,GACxB,OAAO,CAAC,QAAQ,CAAC,CAqBnB;AAED;;GAEG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE,OAAO,CAAC,MAAM,EAAE,CAAC,CAenB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC,GAAG;IAAE,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,GACzE,OAAO,CAAC,cAAc,EAAE,CAAC,CAe3B;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE;IAC7C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,QAAQ,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;;WAQA;AAED;;GAEG;AACH,wBAAgB,qBAAqB,CAAC,MAAM,EAAE;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,GAAG,CAAC,EAAE,IAAI,CAAC;CACZ,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAMrC;AAED,OAAO,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAElE;;;;;;GAMG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,EAAE,CAoB/E;AAED;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,MAAM,EAAE,EACnB,gBAAgB,EAAE,MAAM,EAAE,GACzB,MAAM,GAAG,IAAI,CAcf;AAED;;;;;;;;GAQG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,OAAO,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,0FAA0F;IAC1F,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;CACvB,GAAG,MAAM,CAiBT"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
import { createI18nCore } from "@better-i18n/core";
|
|
2
|
+
import { createFormatter, createTranslator } from "use-intl/core";
|
|
3
|
+
/**
|
|
4
|
+
* Fetch messages for a locale (server-side)
|
|
5
|
+
*/
|
|
6
|
+
export async function getMessages(config) {
|
|
7
|
+
const i18n = createI18nCore({
|
|
8
|
+
project: config.project,
|
|
9
|
+
defaultLocale: config.locale,
|
|
10
|
+
cdnBaseUrl: config.cdnBaseUrl,
|
|
11
|
+
debug: config.debug,
|
|
12
|
+
logLevel: config.logLevel,
|
|
13
|
+
fetch: config.fetch,
|
|
14
|
+
storage: config.storage,
|
|
15
|
+
staticData: config.staticData,
|
|
16
|
+
fetchTimeout: config.fetchTimeout,
|
|
17
|
+
retryCount: config.retryCount,
|
|
18
|
+
});
|
|
19
|
+
const messages = (await i18n.getMessages(config.locale));
|
|
20
|
+
// better-i18n convention: JSON matches exact namespace structure.
|
|
21
|
+
// if CDN returns { "hero": { "title": "..." } }, use-intl expects exactly that
|
|
22
|
+
// if it's deeply nested, use-intl also handles nested objects.
|
|
23
|
+
return messages;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Fetch available locales (server-side)
|
|
27
|
+
*/
|
|
28
|
+
export async function getLocales(config) {
|
|
29
|
+
const i18n = createI18nCore({
|
|
30
|
+
project: config.project,
|
|
31
|
+
defaultLocale: config.defaultLocale || "en",
|
|
32
|
+
cdnBaseUrl: config.cdnBaseUrl,
|
|
33
|
+
debug: config.debug,
|
|
34
|
+
logLevel: config.logLevel,
|
|
35
|
+
fetch: config.fetch,
|
|
36
|
+
storage: config.storage,
|
|
37
|
+
staticData: config.staticData,
|
|
38
|
+
fetchTimeout: config.fetchTimeout,
|
|
39
|
+
retryCount: config.retryCount,
|
|
40
|
+
});
|
|
41
|
+
return i18n.getLocales();
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Fetch available languages with metadata (server-side)
|
|
45
|
+
*/
|
|
46
|
+
export async function getLanguages(config) {
|
|
47
|
+
const i18n = createI18nCore({
|
|
48
|
+
project: config.project,
|
|
49
|
+
defaultLocale: config.defaultLocale || "en",
|
|
50
|
+
cdnBaseUrl: config.cdnBaseUrl,
|
|
51
|
+
debug: config.debug,
|
|
52
|
+
logLevel: config.logLevel,
|
|
53
|
+
fetch: config.fetch,
|
|
54
|
+
storage: config.storage,
|
|
55
|
+
staticData: config.staticData,
|
|
56
|
+
fetchTimeout: config.fetchTimeout,
|
|
57
|
+
retryCount: config.retryCount,
|
|
58
|
+
});
|
|
59
|
+
return i18n.getLanguages();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Create a translator function for use outside React (server-side)
|
|
63
|
+
*/
|
|
64
|
+
export function createServerTranslator(config) {
|
|
65
|
+
return createTranslator({
|
|
66
|
+
locale: config.locale,
|
|
67
|
+
messages: config.messages,
|
|
68
|
+
namespace: config.namespace,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Create a formatter for use outside React (server-side)
|
|
73
|
+
*/
|
|
74
|
+
export function createServerFormatter(config) {
|
|
75
|
+
return createFormatter({
|
|
76
|
+
locale: config.locale,
|
|
77
|
+
timeZone: config.timeZone,
|
|
78
|
+
now: config.now,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
export { createTranslator, createFormatter } from "use-intl/core";
|
|
82
|
+
/**
|
|
83
|
+
* Parse an RFC 5646 Accept-Language header value into a prioritized list of language tags.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* parseAcceptLanguage("tr-TR,tr;q=0.9,en-US;q=0.8,en;q=0.7")
|
|
87
|
+
* // → ["tr-TR", "tr", "en-US", "en"]
|
|
88
|
+
*/
|
|
89
|
+
export function parseAcceptLanguage(header) {
|
|
90
|
+
if (!header)
|
|
91
|
+
return [];
|
|
92
|
+
return (header
|
|
93
|
+
.split(",")
|
|
94
|
+
.map((entry) => {
|
|
95
|
+
const [tag, qPart] = entry.trim().split(";");
|
|
96
|
+
const language = tag?.trim();
|
|
97
|
+
if (!language || language === "*")
|
|
98
|
+
return null;
|
|
99
|
+
const q = qPart ? parseFloat(qPart.trim().replace("q=", "")) : 1.0;
|
|
100
|
+
const quality = Number.isNaN(q) ? 1.0 : q;
|
|
101
|
+
return { language, quality };
|
|
102
|
+
})
|
|
103
|
+
.filter((item) => item !== null)
|
|
104
|
+
.sort((a, b) => b.quality - a.quality)
|
|
105
|
+
.map((item) => item.language));
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Find the best matching locale from a parsed language list against available locales.
|
|
109
|
+
* Matching strategy (in order):
|
|
110
|
+
* 1. Exact match: "tr-TR" → "tr-TR"
|
|
111
|
+
* 2. Base language match: "tr-TR" → "tr"
|
|
112
|
+
* 3. Region expansion: "tr" matches "tr-TR" (first available variant)
|
|
113
|
+
*
|
|
114
|
+
* Returns null if no match found.
|
|
115
|
+
*/
|
|
116
|
+
export function matchLocale(languages, availableLocales) {
|
|
117
|
+
for (const lang of languages) {
|
|
118
|
+
if (availableLocales.includes(lang))
|
|
119
|
+
return lang;
|
|
120
|
+
const base = lang.split("-")[0];
|
|
121
|
+
if (base && availableLocales.includes(base))
|
|
122
|
+
return base;
|
|
123
|
+
const expanded = availableLocales.find((l) => l === base || l.startsWith(`${base}-`));
|
|
124
|
+
if (expanded)
|
|
125
|
+
return expanded;
|
|
126
|
+
}
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Detect best locale from Accept-Language header (server) or navigator.languages (client).
|
|
131
|
+
*
|
|
132
|
+
* - Server (Web Request): reads `request.headers.get('accept-language')`
|
|
133
|
+
* - Server (Express/Hono/etc.): pass `acceptLanguage` header string directly
|
|
134
|
+
* - Client: reads `navigator.languages` / `navigator.language`
|
|
135
|
+
*
|
|
136
|
+
* Falls back to `defaultLocale` if no match is found.
|
|
137
|
+
*/
|
|
138
|
+
export function detectLocale(options) {
|
|
139
|
+
const languages = [];
|
|
140
|
+
if (options.request) {
|
|
141
|
+
languages.push(...parseAcceptLanguage(options.request.headers.get("accept-language")));
|
|
142
|
+
}
|
|
143
|
+
else if (options.acceptLanguage !== undefined) {
|
|
144
|
+
languages.push(...parseAcceptLanguage(options.acceptLanguage));
|
|
145
|
+
}
|
|
146
|
+
else if (typeof navigator !== "undefined") {
|
|
147
|
+
const navLangs = Array.isArray(navigator.languages)
|
|
148
|
+
? [...navigator.languages]
|
|
149
|
+
: navigator.language
|
|
150
|
+
? [navigator.language]
|
|
151
|
+
: [];
|
|
152
|
+
languages.push(...navLangs);
|
|
153
|
+
}
|
|
154
|
+
return matchLocale(languages, options.availableLocales) ?? options.defaultLocale;
|
|
155
|
+
}
|
|
156
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"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"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { I18nCoreConfig, LanguageOption } from "@better-i18n/core";
|
|
2
|
+
import type { ComponentProps } from "react";
|
|
3
|
+
import type { IntlProvider } from "use-intl";
|
|
4
|
+
/**
|
|
5
|
+
* Messages type (compatible with use-intl)
|
|
6
|
+
*/
|
|
7
|
+
export type Messages = ComponentProps<typeof IntlProvider>["messages"];
|
|
8
|
+
/**
|
|
9
|
+
* Configuration for BetterI18nProvider
|
|
10
|
+
*/
|
|
11
|
+
export interface BetterI18nProviderConfig extends Omit<I18nCoreConfig, "defaultLocale"> {
|
|
12
|
+
/**
|
|
13
|
+
* Current locale
|
|
14
|
+
*/
|
|
15
|
+
locale: string;
|
|
16
|
+
/**
|
|
17
|
+
* Pre-loaded messages (for SSR hydration)
|
|
18
|
+
*/
|
|
19
|
+
messages?: Messages;
|
|
20
|
+
/**
|
|
21
|
+
* Timezone for date/time formatting
|
|
22
|
+
* @default undefined (uses browser timezone)
|
|
23
|
+
*/
|
|
24
|
+
timeZone?: string;
|
|
25
|
+
/**
|
|
26
|
+
* Current date/time (useful for SSR to prevent hydration mismatches)
|
|
27
|
+
*/
|
|
28
|
+
now?: Date;
|
|
29
|
+
/**
|
|
30
|
+
* Error handler for missing translations
|
|
31
|
+
*/
|
|
32
|
+
onError?: ComponentProps<typeof IntlProvider>["onError"];
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Better i18n context value
|
|
36
|
+
*
|
|
37
|
+
* Note: Locale is read-only. Use useLocaleRouter().navigate() for locale changes
|
|
38
|
+
* to ensure proper router integration.
|
|
39
|
+
*/
|
|
40
|
+
export interface BetterI18nContextValue {
|
|
41
|
+
/**
|
|
42
|
+
* Current locale (read-only - use useLocaleRouter().navigate() to change)
|
|
43
|
+
*/
|
|
44
|
+
locale: string;
|
|
45
|
+
/**
|
|
46
|
+
* Available languages with metadata from CDN manifest
|
|
47
|
+
*/
|
|
48
|
+
languages: LanguageOption[];
|
|
49
|
+
/**
|
|
50
|
+
* Whether languages are still loading from CDN
|
|
51
|
+
*/
|
|
52
|
+
isLoadingLanguages: boolean;
|
|
53
|
+
/**
|
|
54
|
+
* Whether messages are still loading
|
|
55
|
+
*/
|
|
56
|
+
isLoadingMessages: boolean;
|
|
57
|
+
/**
|
|
58
|
+
* Project identifier
|
|
59
|
+
*/
|
|
60
|
+
project: string;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Server-side configuration for getMessages
|
|
64
|
+
*/
|
|
65
|
+
export interface GetMessagesConfig extends I18nCoreConfig {
|
|
66
|
+
/**
|
|
67
|
+
* Locale to fetch messages for
|
|
68
|
+
*/
|
|
69
|
+
locale: string;
|
|
70
|
+
}
|
|
71
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACxE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,OAAO,CAAC;AAC5C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAE7C;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC,UAAU,CAAC,CAAC;AAEvE;;GAEG;AACH,MAAM,WAAW,wBACf,SAAQ,IAAI,CAAC,cAAc,EAAE,eAAe,CAAC;IAC7C;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,QAAQ,CAAC,EAAE,QAAQ,CAAC;IAEpB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAElB;;OAEG;IACH,GAAG,CAAC,EAAE,IAAI,CAAC;IAEX;;OAEG;IACH,OAAO,CAAC,EAAE,cAAc,CAAC,OAAO,YAAY,CAAC,CAAC,SAAS,CAAC,CAAC;CAC1D;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;IAEf;;OAEG;IACH,SAAS,EAAE,cAAc,EAAE,CAAC;IAE5B;;OAEG;IACH,kBAAkB,EAAE,OAAO,CAAC;IAE5B;;OAEG;IACH,iBAAiB,EAAE,OAAO,CAAC;IAE3B;;OAEG;IACH,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,iBAAkB,SAAQ,cAAc;IACvD;;OAEG;IACH,MAAM,EAAE,MAAM,CAAC;CAChB"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@better-i18n/use-intl",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Better i18n integration for use-intl (React, TanStack Start)",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -13,16 +13,28 @@
|
|
|
13
13
|
},
|
|
14
14
|
"homepage": "https://github.com/better-i18n/better-i18n/tree/main/packages/use-intl",
|
|
15
15
|
"type": "module",
|
|
16
|
-
"main": "./
|
|
17
|
-
"types": "./
|
|
16
|
+
"main": "./dist/index.js",
|
|
17
|
+
"types": "./dist/index.d.ts",
|
|
18
18
|
"exports": {
|
|
19
|
-
".":
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./dist/index.d.ts",
|
|
21
|
+
"bun": "./src/index.ts",
|
|
22
|
+
"default": "./dist/index.js"
|
|
23
|
+
},
|
|
24
|
+
"./server": {
|
|
25
|
+
"types": "./dist/server.d.ts",
|
|
26
|
+
"bun": "./src/server.ts",
|
|
27
|
+
"default": "./dist/server.js"
|
|
28
|
+
},
|
|
29
|
+
"./middleware": {
|
|
30
|
+
"types": "./dist/middleware/index.d.ts",
|
|
31
|
+
"bun": "./src/middleware/index.ts",
|
|
32
|
+
"default": "./dist/middleware/index.js"
|
|
33
|
+
},
|
|
22
34
|
"./package.json": "./package.json"
|
|
23
35
|
},
|
|
24
36
|
"files": [
|
|
25
|
-
"
|
|
37
|
+
"dist",
|
|
26
38
|
"package.json",
|
|
27
39
|
"README.md"
|
|
28
40
|
],
|
|
@@ -41,10 +53,11 @@
|
|
|
41
53
|
"scripts": {
|
|
42
54
|
"build": "tsc",
|
|
43
55
|
"typecheck": "tsc --noEmit",
|
|
44
|
-
"clean": "rm -rf dist"
|
|
56
|
+
"clean": "rm -rf dist",
|
|
57
|
+
"prepublishOnly": "bun run build"
|
|
45
58
|
},
|
|
46
59
|
"dependencies": {
|
|
47
|
-
"@better-i18n/core": "0.
|
|
60
|
+
"@better-i18n/core": "^0.3.0"
|
|
48
61
|
},
|
|
49
62
|
"peerDependencies": {
|
|
50
63
|
"react": ">=18.0.0",
|
package/src/components.tsx
DELETED
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
"use client";
|
|
2
|
-
|
|
3
|
-
import type { ComponentProps, ReactNode } from "react";
|
|
4
|
-
import { useLocaleRouter } from "./hooks/useLocaleRouter";
|
|
5
|
-
import { useLanguages } from "./hooks";
|
|
6
|
-
|
|
7
|
-
export interface LanguageSwitcherProps
|
|
8
|
-
extends Omit<ComponentProps<"select">, "value" | "onChange" | "children"> {
|
|
9
|
-
/**
|
|
10
|
-
* Render function for custom option display
|
|
11
|
-
*/
|
|
12
|
-
renderOption?: (language: {
|
|
13
|
-
code: string;
|
|
14
|
-
name?: string;
|
|
15
|
-
nativeName?: string;
|
|
16
|
-
flagUrl?: string | null;
|
|
17
|
-
isDefault?: boolean;
|
|
18
|
-
}) => ReactNode;
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Label for loading state
|
|
22
|
-
*/
|
|
23
|
-
loadingLabel?: string;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
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.
|
|
31
|
-
*
|
|
32
|
-
* @example
|
|
33
|
-
* ```tsx
|
|
34
|
-
* // Basic usage - just works!
|
|
35
|
-
* <LanguageSwitcher />
|
|
36
|
-
*
|
|
37
|
-
* // With custom styling
|
|
38
|
-
* <LanguageSwitcher className="my-select" />
|
|
39
|
-
*
|
|
40
|
-
* // Custom option rendering
|
|
41
|
-
* <LanguageSwitcher
|
|
42
|
-
* renderOption={(lang) => (
|
|
43
|
-
* <>
|
|
44
|
-
* {lang.flagUrl && <img src={lang.flagUrl} alt="" />}
|
|
45
|
-
* {lang.nativeName}
|
|
46
|
-
* </>
|
|
47
|
-
* )}
|
|
48
|
-
* />
|
|
49
|
-
* ```
|
|
50
|
-
*/
|
|
51
|
-
export function LanguageSwitcher({
|
|
52
|
-
renderOption,
|
|
53
|
-
loadingLabel = "Loading...",
|
|
54
|
-
...props
|
|
55
|
-
}: LanguageSwitcherProps) {
|
|
56
|
-
const { locale, navigate, isReady } = useLocaleRouter();
|
|
57
|
-
const { languages } = useLanguages();
|
|
58
|
-
|
|
59
|
-
if (!isReady) {
|
|
60
|
-
return (
|
|
61
|
-
<select disabled {...props}>
|
|
62
|
-
<option>{loadingLabel}</option>
|
|
63
|
-
</select>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
return (
|
|
68
|
-
<select value={locale} onChange={(e) => navigate(e.target.value)} {...props}>
|
|
69
|
-
{languages.map((lang) => (
|
|
70
|
-
<option key={lang.code} value={lang.code}>
|
|
71
|
-
{renderOption ? renderOption(lang) : lang.nativeName || lang.code}
|
|
72
|
-
</option>
|
|
73
|
-
))}
|
|
74
|
-
</select>
|
|
75
|
-
);
|
|
76
|
-
}
|