i18n-typed-store 0.1.0 → 0.1.1
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 +445 -110
- package/dist/context-Dp43aQ0V.d.mts +91 -0
- package/dist/context-Dp43aQ0V.d.ts +91 -0
- package/dist/index.d.mts +201 -22
- package/dist/index.d.ts +201 -22
- package/dist/index.js +338 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +338 -24
- package/dist/index.mjs.map +1 -1
- package/dist/react/index.d.mts +296 -0
- package/dist/react/index.d.ts +296 -0
- package/dist/react/index.js +231 -0
- package/dist/react/index.js.map +1 -0
- package/dist/react/index.mjs +221 -0
- package/dist/react/index.mjs.map +1 -0
- package/package.json +15 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var react = require('react');
|
|
4
|
+
var jsxRuntime = require('react/jsx-runtime');
|
|
5
|
+
|
|
6
|
+
// src/react/I18nTypedStoreProvider.tsx
|
|
7
|
+
var I18nTypedStoreContext = react.createContext(null);
|
|
8
|
+
var I18nTypedStoreProvider = ({
|
|
9
|
+
store,
|
|
10
|
+
children,
|
|
11
|
+
suspenseMode = "first-load-locale"
|
|
12
|
+
}) => {
|
|
13
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
14
|
+
I18nTypedStoreContext.Provider,
|
|
15
|
+
{
|
|
16
|
+
value: {
|
|
17
|
+
store,
|
|
18
|
+
suspenseMode
|
|
19
|
+
},
|
|
20
|
+
children
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// src/react/Safe.tsx
|
|
26
|
+
var Safe = ({
|
|
27
|
+
children,
|
|
28
|
+
errorComponent,
|
|
29
|
+
errorHandler
|
|
30
|
+
}) => {
|
|
31
|
+
try {
|
|
32
|
+
const result = children();
|
|
33
|
+
return result;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
errorHandler?.(error);
|
|
36
|
+
return errorComponent ?? "";
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
var useI18nTypedStoreContext = () => {
|
|
40
|
+
const context = react.useContext(I18nTypedStoreContext);
|
|
41
|
+
if (!context) {
|
|
42
|
+
throw new Error("useI18nTypedStoreContext must be used within I18nTypedStoreProvider");
|
|
43
|
+
}
|
|
44
|
+
return context;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// src/react/useI18nTranslation.ts
|
|
48
|
+
var useI18nTranslation = (namespace, fromCache = true) => {
|
|
49
|
+
const { store, suspenseMode } = useI18nTypedStoreContext();
|
|
50
|
+
const namespaceState = store.translations[namespace];
|
|
51
|
+
const locale = store.currentLocale;
|
|
52
|
+
const [_, setUpdate] = react.useReducer(() => ({}), {});
|
|
53
|
+
const forceUpdate = () => {
|
|
54
|
+
setUpdate();
|
|
55
|
+
};
|
|
56
|
+
const load = async (needUpdate) => {
|
|
57
|
+
try {
|
|
58
|
+
await store.translations[namespace].load(store.currentLocale, fromCache);
|
|
59
|
+
} catch (error) {
|
|
60
|
+
throw error;
|
|
61
|
+
} finally {
|
|
62
|
+
{
|
|
63
|
+
forceUpdate();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
react.useEffect(() => {
|
|
68
|
+
const listener = () => {
|
|
69
|
+
load();
|
|
70
|
+
};
|
|
71
|
+
store.addChangeLocaleListener(listener);
|
|
72
|
+
return () => {
|
|
73
|
+
store.removeChangeLocaleListener(listener);
|
|
74
|
+
};
|
|
75
|
+
}, [namespace, fromCache]);
|
|
76
|
+
react.useEffect(() => {
|
|
77
|
+
const namespaceState2 = store.translations[namespace];
|
|
78
|
+
const locale2 = store.currentLocale;
|
|
79
|
+
if (!namespaceState2.translations[locale2].namespace) {
|
|
80
|
+
load();
|
|
81
|
+
}
|
|
82
|
+
}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);
|
|
83
|
+
return namespaceState.translations[locale].namespace || namespaceState.currentTranslation;
|
|
84
|
+
};
|
|
85
|
+
var useI18nTranslationLazy = (namespace, fromCache = true) => {
|
|
86
|
+
const { store, suspenseMode } = useI18nTypedStoreContext();
|
|
87
|
+
const namespaceState = store.translations[namespace];
|
|
88
|
+
const locale = store.currentLocale;
|
|
89
|
+
const [_, setUpdate] = react.useReducer(() => ({}), {});
|
|
90
|
+
const forceUpdate = () => {
|
|
91
|
+
setUpdate();
|
|
92
|
+
};
|
|
93
|
+
const load = async (needUpdate) => {
|
|
94
|
+
try {
|
|
95
|
+
await store.translations[namespace].load(store.currentLocale, fromCache);
|
|
96
|
+
} catch (error) {
|
|
97
|
+
throw error;
|
|
98
|
+
} finally {
|
|
99
|
+
if (needUpdate) {
|
|
100
|
+
forceUpdate();
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
};
|
|
104
|
+
react.useEffect(() => {
|
|
105
|
+
const listener = (locale2) => {
|
|
106
|
+
const namespaceState2 = store.translations[namespace];
|
|
107
|
+
if ((suspenseMode === "first-load-locale" || suspenseMode === "change-locale") && namespaceState2.currentLocale !== locale2) {
|
|
108
|
+
setUpdate();
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
load(true);
|
|
112
|
+
};
|
|
113
|
+
store.addChangeLocaleListener(listener);
|
|
114
|
+
return () => {
|
|
115
|
+
store.removeChangeLocaleListener(listener);
|
|
116
|
+
};
|
|
117
|
+
}, [namespace, suspenseMode, fromCache]);
|
|
118
|
+
react.useEffect(() => {
|
|
119
|
+
const namespaceState2 = store.translations[namespace];
|
|
120
|
+
const locale2 = store.currentLocale;
|
|
121
|
+
if (!namespaceState2.translations[locale2].namespace) {
|
|
122
|
+
load(true);
|
|
123
|
+
}
|
|
124
|
+
}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);
|
|
125
|
+
if (!namespaceState.translations[locale].namespace && suspenseMode === "first-load-locale" || suspenseMode === "change-locale" && namespaceState.currentLocale !== locale) {
|
|
126
|
+
throw load(false);
|
|
127
|
+
}
|
|
128
|
+
if (!namespaceState.currentTranslation) {
|
|
129
|
+
throw load(false);
|
|
130
|
+
}
|
|
131
|
+
return namespaceState.translations[locale].namespace || namespaceState.currentTranslation;
|
|
132
|
+
};
|
|
133
|
+
var useI18nLocale = () => {
|
|
134
|
+
const { store } = useI18nTypedStoreContext();
|
|
135
|
+
const locale = react.useSyncExternalStore(
|
|
136
|
+
(notify) => {
|
|
137
|
+
const listener = (locale2) => {
|
|
138
|
+
notify();
|
|
139
|
+
};
|
|
140
|
+
store.addChangeLocaleListener(listener);
|
|
141
|
+
return () => {
|
|
142
|
+
store.removeChangeLocaleListener(listener);
|
|
143
|
+
};
|
|
144
|
+
},
|
|
145
|
+
() => store.currentLocale,
|
|
146
|
+
() => store.currentLocale
|
|
147
|
+
// Server snapshot (same as client for initial render)
|
|
148
|
+
);
|
|
149
|
+
const updateLocale = (locale2) => {
|
|
150
|
+
store.changeLocale(locale2);
|
|
151
|
+
};
|
|
152
|
+
return { locale, setLocale: updateLocale };
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
// src/react/ssr.ts
|
|
156
|
+
function parseAcceptLanguage(acceptLanguage, availableLocales, defaultLocale) {
|
|
157
|
+
if (!acceptLanguage) {
|
|
158
|
+
return defaultLocale;
|
|
159
|
+
}
|
|
160
|
+
const languages = acceptLanguage.split(",").map((lang) => {
|
|
161
|
+
const [locale, q = "1"] = lang.trim().split(";");
|
|
162
|
+
const quality = q.includes("q=") ? parseFloat(q.split("q=")[1]) : 1;
|
|
163
|
+
return { locale: locale.toLowerCase(), quality };
|
|
164
|
+
}).sort((a, b) => b.quality - a.quality);
|
|
165
|
+
for (const { locale } of languages) {
|
|
166
|
+
const exactMatch = availableLocales.find((l) => l.toLowerCase() === locale);
|
|
167
|
+
if (exactMatch) {
|
|
168
|
+
return exactMatch;
|
|
169
|
+
}
|
|
170
|
+
const languageCode = locale.split("-")[0];
|
|
171
|
+
const languageMatch = availableLocales.find((l) => l.toLowerCase().startsWith(languageCode));
|
|
172
|
+
if (languageMatch) {
|
|
173
|
+
return languageMatch;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return defaultLocale;
|
|
177
|
+
}
|
|
178
|
+
function getLocaleFromRequest(context, options) {
|
|
179
|
+
const {
|
|
180
|
+
queryParamName = "locale",
|
|
181
|
+
cookieName,
|
|
182
|
+
headerName = "accept-language",
|
|
183
|
+
defaultLocale,
|
|
184
|
+
availableLocales,
|
|
185
|
+
parseAcceptLanguage: shouldParseAcceptLanguage = true
|
|
186
|
+
} = options;
|
|
187
|
+
if (queryParamName && context.query?.[queryParamName]) {
|
|
188
|
+
const queryLocale = Array.isArray(context.query[queryParamName]) ? context.query[queryParamName][0] : context.query[queryParamName];
|
|
189
|
+
if (typeof queryLocale === "string" && availableLocales.includes(queryLocale)) {
|
|
190
|
+
return queryLocale;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (cookieName && context.cookies?.[cookieName]) {
|
|
194
|
+
const cookieLocale = context.cookies[cookieName];
|
|
195
|
+
if (typeof cookieLocale === "string" && availableLocales.includes(cookieLocale)) {
|
|
196
|
+
return cookieLocale;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (headerName && context.headers) {
|
|
200
|
+
let headerValue;
|
|
201
|
+
if (context.headers instanceof Headers) {
|
|
202
|
+
headerValue = context.headers.get(headerName) || void 0;
|
|
203
|
+
} else {
|
|
204
|
+
const header = context.headers[headerName];
|
|
205
|
+
headerValue = Array.isArray(header) ? header[0] : header;
|
|
206
|
+
}
|
|
207
|
+
if (headerName.toLowerCase() === "accept-language" && shouldParseAcceptLanguage) {
|
|
208
|
+
const parsedLocale = parseAcceptLanguage(headerValue, availableLocales, defaultLocale);
|
|
209
|
+
return parsedLocale;
|
|
210
|
+
}
|
|
211
|
+
if (headerValue && availableLocales.includes(headerValue)) {
|
|
212
|
+
return headerValue;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
return defaultLocale;
|
|
216
|
+
}
|
|
217
|
+
function initializeStore(store, locale) {
|
|
218
|
+
store.changeLocale(locale);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
exports.I18nTypedStoreContext = I18nTypedStoreContext;
|
|
222
|
+
exports.I18nTypedStoreProvider = I18nTypedStoreProvider;
|
|
223
|
+
exports.Safe = Safe;
|
|
224
|
+
exports.getLocaleFromRequest = getLocaleFromRequest;
|
|
225
|
+
exports.initializeStore = initializeStore;
|
|
226
|
+
exports.useI18nLocale = useI18nLocale;
|
|
227
|
+
exports.useI18nTranslation = useI18nTranslation;
|
|
228
|
+
exports.useI18nTranslationLazy = useI18nTranslationLazy;
|
|
229
|
+
exports.useI18nTypedStoreContext = useI18nTypedStoreContext;
|
|
230
|
+
//# sourceMappingURL=index.js.map
|
|
231
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/I18nTypedStoreProvider.tsx","../../src/react/Safe.tsx","../../src/react/useI18nTypedStoreContext.ts","../../src/react/useI18nTranslation.ts","../../src/react/useI18nTranslationLazy.ts","../../src/react/useLocale.ts","../../src/react/ssr.ts"],"names":["createContext","jsx","useContext","useReducer","useEffect","namespaceState","locale","useSyncExternalStore"],"mappings":"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","file":"index.js","sourcesContent":["import { createContext, type ReactNode } from 'react';\nimport type { TranslationStore } from '../types/translation-store';\nimport type { II18nTypedStoreContext } from '../types/context';\n\n/**\n * React context for I18n typed store.\n * Used internally by hooks to access the translation store.\n */\nexport const I18nTypedStoreContext = createContext<II18nTypedStoreContext<any, any, any> | null>(null);\n\n/**\n * Provider component for I18n typed store.\n * Wraps your application to provide translation store context to child components.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n *\n * @param props - Provider props\n * @param props.store - Translation store instance\n * @param props.children - React children\n * @param props.suspenseMode - Suspense mode: 'once' | 'first-load-locale' | 'change-locale'\n * @returns Provider component\n *\n * @example\n * ```tsx\n * const store = storeFactory.type<MyTranslations>();\n *\n * function App() {\n * return (\n * <I18nTypedStoreProvider store={store}>\n * <MyComponent />\n * </I18nTypedStoreProvider>\n * );\n * }\n * ```\n */\nexport const I18nTypedStoreProvider = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n>({\n\tstore,\n\tchildren,\n\tsuspenseMode = 'first-load-locale',\n}: {\n\tstore: TranslationStore<T, L, M>;\n\tchildren: ReactNode;\n\tsuspenseMode?: II18nTypedStoreContext<T, L, M>['suspenseMode'];\n}) => {\n\treturn (\n\t\t<I18nTypedStoreContext.Provider\n\t\t\tvalue={\n\t\t\t\t{\n\t\t\t\t\tstore,\n\t\t\t\t\tsuspenseMode,\n\t\t\t\t} as II18nTypedStoreContext<T, L, M>\n\t\t\t}\n\t\t>\n\t\t\t{children}\n\t\t</I18nTypedStoreContext.Provider>\n\t);\n};\n","import type { FC, ReactNode } from 'react';\n\n/**\n * Safe component that catches errors during string extraction from translation objects.\n * Useful for safely accessing nested translation keys that might throw errors.\n *\n * @param props - Component props\n * @param props.children - Function that returns a string (called during render)\n * @param props.errorComponent - Component to display if an error occurs (default: empty string)\n * @param props.errorHandler - Optional error handler callback\n * @returns Rendered string wrapped in a span or error component\n *\n * @example\n * ```tsx\n * <Safe\n * errorComponent={<span>N/A</span>}\n * errorHandler={(error) => console.error(error)}\n * >\n * {() => translations.common.pages.main.title}\n * </Safe>\n * ```\n */\nexport const Safe: FC<{ children: () => string; errorComponent?: ReactNode; errorHandler?: (error: unknown) => void }> = ({\n\tchildren,\n\terrorComponent,\n\terrorHandler,\n}) => {\n\ttry {\n\t\tconst result = children();\n\t\treturn result;\n\t} catch (error) {\n\t\terrorHandler?.(error);\n\t\treturn errorComponent ?? '';\n\t}\n};\n","import { useContext } from 'react';\nimport { I18nTypedStoreContext } from './I18nTypedStoreProvider';\nimport type { II18nTypedStoreContext } from '../types/context';\n\n/**\n * Hook for accessing the I18n typed store context.\n * Must be used within an I18nTypedStoreProvider.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n *\n * @returns I18n typed store context value\n * @throws Error if used outside of I18nTypedStoreProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { store, suspenseMode } = useI18nTypedStoreContext();\n * // Use store, suspenseMode\n * }\n * ```\n */\nexport const useI18nTypedStoreContext = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n>(): II18nTypedStoreContext<T, L, M> => {\n\tconst context = useContext(I18nTypedStoreContext);\n\tif (!context) {\n\t\tthrow new Error('useI18nTypedStoreContext must be used within I18nTypedStoreProvider');\n\t}\n\treturn context as II18nTypedStoreContext<T, L, M>;\n};\n","import { useEffect, useReducer } from 'react';\nimport { useI18nTypedStoreContext } from './useI18nTypedStoreContext';\n\n/**\n * Hook for accessing translations with automatic loading and locale change handling.\n * Returns undefined if translation is not yet loaded.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n * @template K - Namespace key from translations object\n *\n * @param namespace - Namespace key to load translations for\n * @param fromCache - Whether to use cached translation if available (default: true)\n * @returns Translation object for the specified namespace, or undefined if not loaded\n *\n * @example\n * ```tsx\n * const translations = useI18nTranslation('common');\n * if (translations) {\n * console.log(translations.greeting);\n * }\n * ```\n */\nexport const useI18nTranslation = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n\tK extends keyof T,\n>(\n\tnamespace: K,\n\tfromCache: boolean = true,\n): M[K] | undefined => {\n\tconst { store, suspenseMode } = useI18nTypedStoreContext<T, L, M>();\n\n\tconst namespaceState = store.translations[namespace];\n\tconst locale = store.currentLocale;\n\tconst [_, setUpdate] = useReducer(() => ({}), {});\n\n\tconst forceUpdate = () => {\n\t\tsetUpdate();\n\t};\n\n\tconst load = async (needUpdate: boolean) => {\n\t\ttry {\n\t\t\tawait store.translations[namespace].load(store.currentLocale, fromCache);\n\t\t} catch (error) {\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (needUpdate) {\n\t\t\t\tforceUpdate();\n\t\t\t}\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tconst listener = () => {\n\t\t\tload(true);\n\t\t};\n\t\tstore.addChangeLocaleListener(listener);\n\t\treturn () => {\n\t\t\tstore.removeChangeLocaleListener(listener);\n\t\t};\n\t}, [namespace, fromCache]);\n\n\tuseEffect(() => {\n\t\tconst namespaceState = store.translations[namespace];\n\t\tconst locale = store.currentLocale;\n\t\tif (!namespaceState.translations[locale].namespace) {\n\t\t\tload(true);\n\t\t}\n\t}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);\n\n\treturn namespaceState.translations[locale].namespace || namespaceState.currentTranslation;\n};\n","import { useEffect, useReducer } from 'react';\nimport { useI18nTypedStoreContext } from './useI18nTypedStoreContext';\n\n/**\n * Hook for accessing translations with lazy loading and suspense support.\n * Throws a promise if translation is not yet loaded (for React Suspense).\n * Always returns a translation object (never undefined).\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n * @template K - Namespace key from translations object\n *\n * @param namespace - Namespace key to load translations for\n * @param fromCache - Whether to use cached translation if available (default: true)\n * @returns Translation object for the specified namespace (never undefined)\n * @throws Promise if translation is not yet loaded (for React Suspense)\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const translations = useI18nTranslationLazy('common');\n * return <div>{translations.greeting}</div>;\n * }\n *\n * function App() {\n * return (\n * <Suspense fallback={<Loading />}>\n * <MyComponent />\n * </Suspense>\n * );\n * }\n * ```\n */\nexport const useI18nTranslationLazy = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n\tK extends keyof T,\n>(\n\tnamespace: K,\n\tfromCache: boolean = true,\n): M[K] => {\n\tconst { store, suspenseMode } = useI18nTypedStoreContext<T, L, M>();\n\n\tconst namespaceState = store.translations[namespace];\n\tconst locale = store.currentLocale;\n\tconst [_, setUpdate] = useReducer(() => ({}), {});\n\n\tconst forceUpdate = () => {\n\t\tsetUpdate();\n\t};\n\n\tconst load = async (needUpdate: boolean) => {\n\t\ttry {\n\t\t\tawait store.translations[namespace].load(store.currentLocale, fromCache);\n\t\t} catch (error) {\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (needUpdate) {\n\t\t\t\tforceUpdate();\n\t\t\t}\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tconst listener = (locale: keyof L) => {\n\t\t\tconst namespaceState = store.translations[namespace];\n\t\t\tif ((suspenseMode === 'first-load-locale' || suspenseMode === 'change-locale') && namespaceState.currentLocale !== locale) {\n\t\t\t\tsetUpdate();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tload(true);\n\t\t};\n\t\tstore.addChangeLocaleListener(listener);\n\t\treturn () => {\n\t\t\tstore.removeChangeLocaleListener(listener);\n\t\t};\n\t}, [namespace, suspenseMode, fromCache]);\n\n\tuseEffect(() => {\n\t\tconst namespaceState = store.translations[namespace];\n\t\tconst locale = store.currentLocale;\n\t\tif (!namespaceState.translations[locale].namespace) {\n\t\t\tload(true);\n\t\t}\n\t}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);\n\n\tif (\n\t\t(!namespaceState.translations[locale].namespace && suspenseMode === 'first-load-locale') ||\n\t\t(suspenseMode === 'change-locale' && namespaceState.currentLocale !== locale)\n\t) {\n\t\tthrow load(false);\n\t}\n\n\tif (!namespaceState.currentTranslation) {\n\t\tthrow load(false);\n\t}\n\n\treturn namespaceState.translations[locale].namespace || namespaceState.currentTranslation;\n};\n","import { useSyncExternalStore } from 'react';\nimport { useI18nTypedStoreContext } from './useI18nTypedStoreContext';\n\n/**\n * Hook for accessing and managing the current locale.\n * Returns the current locale and a function to change it.\n * Supports SSR/SSG by using useSyncExternalStore for proper hydration.\n *\n * @template T - Type of translations object\n * @template L - Type of locales object\n * @template M - Type of translation modules mapping\n *\n * @returns Object with current locale and setLocale function\n *\n * @example\n * ```tsx\n * function LocaleSwitcher() {\n * const { locale, setLocale } = useI18nLocale();\n * return (\n * <select value={locale} onChange={(e) => setLocale(e.target.value as keyof Locales)}>\n * <option value=\"en\">English</option>\n * <option value=\"ru\">Русский</option>\n * </select>\n * );\n * }\n * ```\n */\nexport const useI18nLocale = <T extends Record<string, string>, L extends Record<string, string>, M extends { [K in keyof T]: any }>() => {\n\tconst { store } = useI18nTypedStoreContext<T, L, M>();\n\n\t// Use useSyncExternalStore for proper SSR/SSG hydration\n\tconst locale = useSyncExternalStore(\n\t\t(notify) => {\n\t\t\tconst listener = (locale: keyof L) => {\n\t\t\t\tnotify();\n\t\t\t};\n\t\t\tstore.addChangeLocaleListener(listener);\n\t\t\treturn () => {\n\t\t\t\tstore.removeChangeLocaleListener(listener);\n\t\t\t};\n\t\t},\n\t\t() => store.currentLocale,\n\t\t() => store.currentLocale, // Server snapshot (same as client for initial render)\n\t);\n\n\tconst updateLocale = (locale: keyof L) => {\n\t\tstore.changeLocale(locale);\n\t};\n\n\treturn { locale, setLocale: updateLocale };\n};\n","import type { TranslationStore } from '../types/translation-store';\n\n/**\n * Request context interface for locale detection in SSR environments.\n * Compatible with various SSR frameworks (Next.js, Remix, SvelteKit, etc.)\n * and standard request objects that provide query, cookies, and headers.\n */\nexport interface RequestContext {\n\tquery?: Record<string, string | string[]>;\n\tcookies?: Record<string, string>;\n\theaders?: Record<string, string | string[]> | Headers;\n}\n\n/**\n * Options for getting locale from SSR request.\n */\nexport interface GetLocaleFromRequestOptions {\n\t/**\n\t * Header name to read locale from (e.g., 'accept-language', 'x-locale')\n\t * @default 'accept-language'\n\t */\n\theaderName?: string;\n\t/**\n\t * Cookie name to read locale from\n\t */\n\tcookieName?: string;\n\t/**\n\t * Query parameter name to read locale from (e.g., 'locale', 'lang')\n\t */\n\tqueryParamName?: string;\n\t/**\n\t * Default locale to use if locale cannot be determined\n\t */\n\tdefaultLocale: string;\n\t/**\n\t * Available locales to validate against\n\t */\n\tavailableLocales: readonly string[];\n\t/**\n\t * Whether to parse Accept-Language header and find best match\n\t * @default true\n\t */\n\tparseAcceptLanguage?: boolean;\n}\n\n/**\n * Parses Accept-Language header and returns the best matching locale.\n *\n * @param acceptLanguage - Accept-Language header value\n * @param availableLocales - Available locales\n * @param defaultLocale - Default locale to use if no match found\n * @returns Best matching locale\n */\nfunction parseAcceptLanguage(\n\tacceptLanguage: string | undefined,\n\tavailableLocales: readonly string[],\n\tdefaultLocale: string,\n): string {\n\tif (!acceptLanguage) {\n\t\treturn defaultLocale;\n\t}\n\n\t// Parse Accept-Language header (e.g., \"en-US,en;q=0.9,ru;q=0.8\")\n\tconst languages = acceptLanguage\n\t\t.split(',')\n\t\t.map((lang) => {\n\t\t\tconst [locale, q = '1'] = lang.trim().split(';');\n\t\t\tconst quality = q.includes('q=') ? parseFloat(q.split('q=')[1]) : 1;\n\t\t\treturn { locale: locale.toLowerCase(), quality };\n\t\t})\n\t\t.sort((a, b) => b.quality - a.quality);\n\n\t// Try to find exact match first\n\tfor (const { locale } of languages) {\n\t\tconst exactMatch = availableLocales.find((l) => l.toLowerCase() === locale);\n\t\tif (exactMatch) {\n\t\t\treturn exactMatch;\n\t\t}\n\n\t\t// Try to find language match (e.g., 'en' matches 'en-US')\n\t\tconst languageCode = locale.split('-')[0];\n\t\tconst languageMatch = availableLocales.find((l) => l.toLowerCase().startsWith(languageCode));\n\t\tif (languageMatch) {\n\t\t\treturn languageMatch;\n\t\t}\n\t}\n\n\treturn defaultLocale;\n}\n\n/**\n * Gets locale from SSR request context.\n * Checks query params, cookies, and headers in that order.\n * Works with any SSR framework that provides these standard request properties.\n *\n * @template L - Type of locales object\n * @param context - SSR request context with query, cookies, and headers\n * @param options - Options for locale detection\n * @returns Detected locale key\n *\n * @example\n * ```ts\n * // Next.js example\n * import type { GetServerSidePropsContext } from 'next';\n *\n * export async function getServerSideProps(context: GetServerSidePropsContext) {\n * const locale = getLocaleFromRequest(context, {\n * defaultLocale: 'en',\n * availableLocales: ['en', 'ru'],\n * cookieName: 'locale',\n * queryParamName: 'locale',\n * });\n *\n * const store = storeFactory.type<MyTranslations>();\n * initializeStore(store, locale);\n *\n * return { props: { locale } };\n * }\n * ```\n *\n * @example\n * ```ts\n * // Remix example\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const url = new URL(request.url);\n * const context: RequestContext = {\n * query: Object.fromEntries(url.searchParams),\n * headers: Object.fromEntries(request.headers),\n * };\n *\n * const locale = getLocaleFromRequest(context, {\n * defaultLocale: 'en',\n * availableLocales: ['en', 'ru'],\n * });\n *\n * return { locale };\n * }\n * ```\n */\nexport function getLocaleFromRequest<L extends Record<string, string>>(\n\tcontext: RequestContext,\n\toptions: GetLocaleFromRequestOptions,\n): keyof L {\n\tconst {\n\t\tqueryParamName = 'locale',\n\t\tcookieName,\n\t\theaderName = 'accept-language',\n\t\tdefaultLocale,\n\t\tavailableLocales,\n\t\tparseAcceptLanguage: shouldParseAcceptLanguage = true,\n\t} = options;\n\n\t// 1. Check query parameter\n\tif (queryParamName && context.query?.[queryParamName]) {\n\t\tconst queryLocale = Array.isArray(context.query[queryParamName])\n\t\t\t? context.query[queryParamName][0]\n\t\t\t: context.query[queryParamName];\n\t\tif (typeof queryLocale === 'string' && availableLocales.includes(queryLocale)) {\n\t\t\treturn queryLocale as keyof L;\n\t\t}\n\t}\n\n\t// 2. Check cookie\n\tif (cookieName && context.cookies?.[cookieName]) {\n\t\tconst cookieLocale = context.cookies[cookieName];\n\t\tif (typeof cookieLocale === 'string' && availableLocales.includes(cookieLocale)) {\n\t\t\treturn cookieLocale as keyof L;\n\t\t}\n\t}\n\n\t// 3. Check header (for Accept-Language, parse it)\n\tif (headerName && context.headers) {\n\t\tlet headerValue: string | undefined;\n\n\t\tif (context.headers instanceof Headers) {\n\t\t\theaderValue = context.headers.get(headerName) || undefined;\n\t\t} else {\n\t\t\tconst header = context.headers[headerName];\n\t\t\theaderValue = Array.isArray(header) ? header[0] : header;\n\t\t}\n\n\t\t// For Accept-Language, always call parseAcceptLanguage (even if headerValue is empty/undefined)\n\t\tif (headerName.toLowerCase() === 'accept-language' && shouldParseAcceptLanguage) {\n\t\t\tconst parsedLocale = parseAcceptLanguage(headerValue, availableLocales, defaultLocale);\n\t\t\treturn parsedLocale as keyof L;\n\t\t}\n\n\t\tif (headerValue && availableLocales.includes(headerValue)) {\n\t\t\treturn headerValue as keyof L;\n\t\t}\n\t}\n\n\treturn defaultLocale as keyof L;\n}\n\n/**\n * Initializes translation store with a specific locale.\n * Useful for SSR/SSG where you need to set the locale before rendering.\n *\n * @template T - Type of translations object\n * @template L - Type of locales object\n * @template M - Type of translation modules mapping\n * @param store - Translation store instance\n * @param locale - Locale to initialize with\n *\n * @example\n * ```ts\n * // In SSR handler\n * const locale = getLocaleFromRequest(context, {\n * defaultLocale: 'en',\n * availableLocales: ['en', 'ru'],\n * });\n *\n * const store = storeFactory.type<MyTranslations>();\n * initializeStore(store, locale);\n *\n * // Preload translations if needed\n * await store.common.load(locale);\n * ```\n */\nexport function initializeStore<\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n>(store: TranslationStore<T, L, M>, locale: keyof L): void {\n\tstore.changeLocale(locale);\n}\n\n"]}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import { createContext, useContext, useReducer, useEffect, useSyncExternalStore } from 'react';
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
|
|
4
|
+
// src/react/I18nTypedStoreProvider.tsx
|
|
5
|
+
var I18nTypedStoreContext = createContext(null);
|
|
6
|
+
var I18nTypedStoreProvider = ({
|
|
7
|
+
store,
|
|
8
|
+
children,
|
|
9
|
+
suspenseMode = "first-load-locale"
|
|
10
|
+
}) => {
|
|
11
|
+
return /* @__PURE__ */ jsx(
|
|
12
|
+
I18nTypedStoreContext.Provider,
|
|
13
|
+
{
|
|
14
|
+
value: {
|
|
15
|
+
store,
|
|
16
|
+
suspenseMode
|
|
17
|
+
},
|
|
18
|
+
children
|
|
19
|
+
}
|
|
20
|
+
);
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// src/react/Safe.tsx
|
|
24
|
+
var Safe = ({
|
|
25
|
+
children,
|
|
26
|
+
errorComponent,
|
|
27
|
+
errorHandler
|
|
28
|
+
}) => {
|
|
29
|
+
try {
|
|
30
|
+
const result = children();
|
|
31
|
+
return result;
|
|
32
|
+
} catch (error) {
|
|
33
|
+
errorHandler?.(error);
|
|
34
|
+
return errorComponent ?? "";
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
var useI18nTypedStoreContext = () => {
|
|
38
|
+
const context = useContext(I18nTypedStoreContext);
|
|
39
|
+
if (!context) {
|
|
40
|
+
throw new Error("useI18nTypedStoreContext must be used within I18nTypedStoreProvider");
|
|
41
|
+
}
|
|
42
|
+
return context;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// src/react/useI18nTranslation.ts
|
|
46
|
+
var useI18nTranslation = (namespace, fromCache = true) => {
|
|
47
|
+
const { store, suspenseMode } = useI18nTypedStoreContext();
|
|
48
|
+
const namespaceState = store.translations[namespace];
|
|
49
|
+
const locale = store.currentLocale;
|
|
50
|
+
const [_, setUpdate] = useReducer(() => ({}), {});
|
|
51
|
+
const forceUpdate = () => {
|
|
52
|
+
setUpdate();
|
|
53
|
+
};
|
|
54
|
+
const load = async (needUpdate) => {
|
|
55
|
+
try {
|
|
56
|
+
await store.translations[namespace].load(store.currentLocale, fromCache);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
throw error;
|
|
59
|
+
} finally {
|
|
60
|
+
{
|
|
61
|
+
forceUpdate();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
};
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
const listener = () => {
|
|
67
|
+
load();
|
|
68
|
+
};
|
|
69
|
+
store.addChangeLocaleListener(listener);
|
|
70
|
+
return () => {
|
|
71
|
+
store.removeChangeLocaleListener(listener);
|
|
72
|
+
};
|
|
73
|
+
}, [namespace, fromCache]);
|
|
74
|
+
useEffect(() => {
|
|
75
|
+
const namespaceState2 = store.translations[namespace];
|
|
76
|
+
const locale2 = store.currentLocale;
|
|
77
|
+
if (!namespaceState2.translations[locale2].namespace) {
|
|
78
|
+
load();
|
|
79
|
+
}
|
|
80
|
+
}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);
|
|
81
|
+
return namespaceState.translations[locale].namespace || namespaceState.currentTranslation;
|
|
82
|
+
};
|
|
83
|
+
var useI18nTranslationLazy = (namespace, fromCache = true) => {
|
|
84
|
+
const { store, suspenseMode } = useI18nTypedStoreContext();
|
|
85
|
+
const namespaceState = store.translations[namespace];
|
|
86
|
+
const locale = store.currentLocale;
|
|
87
|
+
const [_, setUpdate] = useReducer(() => ({}), {});
|
|
88
|
+
const forceUpdate = () => {
|
|
89
|
+
setUpdate();
|
|
90
|
+
};
|
|
91
|
+
const load = async (needUpdate) => {
|
|
92
|
+
try {
|
|
93
|
+
await store.translations[namespace].load(store.currentLocale, fromCache);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw error;
|
|
96
|
+
} finally {
|
|
97
|
+
if (needUpdate) {
|
|
98
|
+
forceUpdate();
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
useEffect(() => {
|
|
103
|
+
const listener = (locale2) => {
|
|
104
|
+
const namespaceState2 = store.translations[namespace];
|
|
105
|
+
if ((suspenseMode === "first-load-locale" || suspenseMode === "change-locale") && namespaceState2.currentLocale !== locale2) {
|
|
106
|
+
setUpdate();
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
load(true);
|
|
110
|
+
};
|
|
111
|
+
store.addChangeLocaleListener(listener);
|
|
112
|
+
return () => {
|
|
113
|
+
store.removeChangeLocaleListener(listener);
|
|
114
|
+
};
|
|
115
|
+
}, [namespace, suspenseMode, fromCache]);
|
|
116
|
+
useEffect(() => {
|
|
117
|
+
const namespaceState2 = store.translations[namespace];
|
|
118
|
+
const locale2 = store.currentLocale;
|
|
119
|
+
if (!namespaceState2.translations[locale2].namespace) {
|
|
120
|
+
load(true);
|
|
121
|
+
}
|
|
122
|
+
}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);
|
|
123
|
+
if (!namespaceState.translations[locale].namespace && suspenseMode === "first-load-locale" || suspenseMode === "change-locale" && namespaceState.currentLocale !== locale) {
|
|
124
|
+
throw load(false);
|
|
125
|
+
}
|
|
126
|
+
if (!namespaceState.currentTranslation) {
|
|
127
|
+
throw load(false);
|
|
128
|
+
}
|
|
129
|
+
return namespaceState.translations[locale].namespace || namespaceState.currentTranslation;
|
|
130
|
+
};
|
|
131
|
+
var useI18nLocale = () => {
|
|
132
|
+
const { store } = useI18nTypedStoreContext();
|
|
133
|
+
const locale = useSyncExternalStore(
|
|
134
|
+
(notify) => {
|
|
135
|
+
const listener = (locale2) => {
|
|
136
|
+
notify();
|
|
137
|
+
};
|
|
138
|
+
store.addChangeLocaleListener(listener);
|
|
139
|
+
return () => {
|
|
140
|
+
store.removeChangeLocaleListener(listener);
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
() => store.currentLocale,
|
|
144
|
+
() => store.currentLocale
|
|
145
|
+
// Server snapshot (same as client for initial render)
|
|
146
|
+
);
|
|
147
|
+
const updateLocale = (locale2) => {
|
|
148
|
+
store.changeLocale(locale2);
|
|
149
|
+
};
|
|
150
|
+
return { locale, setLocale: updateLocale };
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// src/react/ssr.ts
|
|
154
|
+
function parseAcceptLanguage(acceptLanguage, availableLocales, defaultLocale) {
|
|
155
|
+
if (!acceptLanguage) {
|
|
156
|
+
return defaultLocale;
|
|
157
|
+
}
|
|
158
|
+
const languages = acceptLanguage.split(",").map((lang) => {
|
|
159
|
+
const [locale, q = "1"] = lang.trim().split(";");
|
|
160
|
+
const quality = q.includes("q=") ? parseFloat(q.split("q=")[1]) : 1;
|
|
161
|
+
return { locale: locale.toLowerCase(), quality };
|
|
162
|
+
}).sort((a, b) => b.quality - a.quality);
|
|
163
|
+
for (const { locale } of languages) {
|
|
164
|
+
const exactMatch = availableLocales.find((l) => l.toLowerCase() === locale);
|
|
165
|
+
if (exactMatch) {
|
|
166
|
+
return exactMatch;
|
|
167
|
+
}
|
|
168
|
+
const languageCode = locale.split("-")[0];
|
|
169
|
+
const languageMatch = availableLocales.find((l) => l.toLowerCase().startsWith(languageCode));
|
|
170
|
+
if (languageMatch) {
|
|
171
|
+
return languageMatch;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return defaultLocale;
|
|
175
|
+
}
|
|
176
|
+
function getLocaleFromRequest(context, options) {
|
|
177
|
+
const {
|
|
178
|
+
queryParamName = "locale",
|
|
179
|
+
cookieName,
|
|
180
|
+
headerName = "accept-language",
|
|
181
|
+
defaultLocale,
|
|
182
|
+
availableLocales,
|
|
183
|
+
parseAcceptLanguage: shouldParseAcceptLanguage = true
|
|
184
|
+
} = options;
|
|
185
|
+
if (queryParamName && context.query?.[queryParamName]) {
|
|
186
|
+
const queryLocale = Array.isArray(context.query[queryParamName]) ? context.query[queryParamName][0] : context.query[queryParamName];
|
|
187
|
+
if (typeof queryLocale === "string" && availableLocales.includes(queryLocale)) {
|
|
188
|
+
return queryLocale;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
if (cookieName && context.cookies?.[cookieName]) {
|
|
192
|
+
const cookieLocale = context.cookies[cookieName];
|
|
193
|
+
if (typeof cookieLocale === "string" && availableLocales.includes(cookieLocale)) {
|
|
194
|
+
return cookieLocale;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
if (headerName && context.headers) {
|
|
198
|
+
let headerValue;
|
|
199
|
+
if (context.headers instanceof Headers) {
|
|
200
|
+
headerValue = context.headers.get(headerName) || void 0;
|
|
201
|
+
} else {
|
|
202
|
+
const header = context.headers[headerName];
|
|
203
|
+
headerValue = Array.isArray(header) ? header[0] : header;
|
|
204
|
+
}
|
|
205
|
+
if (headerName.toLowerCase() === "accept-language" && shouldParseAcceptLanguage) {
|
|
206
|
+
const parsedLocale = parseAcceptLanguage(headerValue, availableLocales, defaultLocale);
|
|
207
|
+
return parsedLocale;
|
|
208
|
+
}
|
|
209
|
+
if (headerValue && availableLocales.includes(headerValue)) {
|
|
210
|
+
return headerValue;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
return defaultLocale;
|
|
214
|
+
}
|
|
215
|
+
function initializeStore(store, locale) {
|
|
216
|
+
store.changeLocale(locale);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export { I18nTypedStoreContext, I18nTypedStoreProvider, Safe, getLocaleFromRequest, initializeStore, useI18nLocale, useI18nTranslation, useI18nTranslationLazy, useI18nTypedStoreContext };
|
|
220
|
+
//# sourceMappingURL=index.mjs.map
|
|
221
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/react/I18nTypedStoreProvider.tsx","../../src/react/Safe.tsx","../../src/react/useI18nTypedStoreContext.ts","../../src/react/useI18nTranslation.ts","../../src/react/useI18nTranslationLazy.ts","../../src/react/useLocale.ts","../../src/react/ssr.ts"],"names":["namespaceState","locale","useReducer","useEffect"],"mappings":"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","file":"index.mjs","sourcesContent":["import { createContext, type ReactNode } from 'react';\nimport type { TranslationStore } from '../types/translation-store';\nimport type { II18nTypedStoreContext } from '../types/context';\n\n/**\n * React context for I18n typed store.\n * Used internally by hooks to access the translation store.\n */\nexport const I18nTypedStoreContext = createContext<II18nTypedStoreContext<any, any, any> | null>(null);\n\n/**\n * Provider component for I18n typed store.\n * Wraps your application to provide translation store context to child components.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n *\n * @param props - Provider props\n * @param props.store - Translation store instance\n * @param props.children - React children\n * @param props.suspenseMode - Suspense mode: 'once' | 'first-load-locale' | 'change-locale'\n * @returns Provider component\n *\n * @example\n * ```tsx\n * const store = storeFactory.type<MyTranslations>();\n *\n * function App() {\n * return (\n * <I18nTypedStoreProvider store={store}>\n * <MyComponent />\n * </I18nTypedStoreProvider>\n * );\n * }\n * ```\n */\nexport const I18nTypedStoreProvider = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n>({\n\tstore,\n\tchildren,\n\tsuspenseMode = 'first-load-locale',\n}: {\n\tstore: TranslationStore<T, L, M>;\n\tchildren: ReactNode;\n\tsuspenseMode?: II18nTypedStoreContext<T, L, M>['suspenseMode'];\n}) => {\n\treturn (\n\t\t<I18nTypedStoreContext.Provider\n\t\t\tvalue={\n\t\t\t\t{\n\t\t\t\t\tstore,\n\t\t\t\t\tsuspenseMode,\n\t\t\t\t} as II18nTypedStoreContext<T, L, M>\n\t\t\t}\n\t\t>\n\t\t\t{children}\n\t\t</I18nTypedStoreContext.Provider>\n\t);\n};\n","import type { FC, ReactNode } from 'react';\n\n/**\n * Safe component that catches errors during string extraction from translation objects.\n * Useful for safely accessing nested translation keys that might throw errors.\n *\n * @param props - Component props\n * @param props.children - Function that returns a string (called during render)\n * @param props.errorComponent - Component to display if an error occurs (default: empty string)\n * @param props.errorHandler - Optional error handler callback\n * @returns Rendered string wrapped in a span or error component\n *\n * @example\n * ```tsx\n * <Safe\n * errorComponent={<span>N/A</span>}\n * errorHandler={(error) => console.error(error)}\n * >\n * {() => translations.common.pages.main.title}\n * </Safe>\n * ```\n */\nexport const Safe: FC<{ children: () => string; errorComponent?: ReactNode; errorHandler?: (error: unknown) => void }> = ({\n\tchildren,\n\terrorComponent,\n\terrorHandler,\n}) => {\n\ttry {\n\t\tconst result = children();\n\t\treturn result;\n\t} catch (error) {\n\t\terrorHandler?.(error);\n\t\treturn errorComponent ?? '';\n\t}\n};\n","import { useContext } from 'react';\nimport { I18nTypedStoreContext } from './I18nTypedStoreProvider';\nimport type { II18nTypedStoreContext } from '../types/context';\n\n/**\n * Hook for accessing the I18n typed store context.\n * Must be used within an I18nTypedStoreProvider.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n *\n * @returns I18n typed store context value\n * @throws Error if used outside of I18nTypedStoreProvider\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const { store, suspenseMode } = useI18nTypedStoreContext();\n * // Use store, suspenseMode\n * }\n * ```\n */\nexport const useI18nTypedStoreContext = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n>(): II18nTypedStoreContext<T, L, M> => {\n\tconst context = useContext(I18nTypedStoreContext);\n\tif (!context) {\n\t\tthrow new Error('useI18nTypedStoreContext must be used within I18nTypedStoreProvider');\n\t}\n\treturn context as II18nTypedStoreContext<T, L, M>;\n};\n","import { useEffect, useReducer } from 'react';\nimport { useI18nTypedStoreContext } from './useI18nTypedStoreContext';\n\n/**\n * Hook for accessing translations with automatic loading and locale change handling.\n * Returns undefined if translation is not yet loaded.\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n * @template K - Namespace key from translations object\n *\n * @param namespace - Namespace key to load translations for\n * @param fromCache - Whether to use cached translation if available (default: true)\n * @returns Translation object for the specified namespace, or undefined if not loaded\n *\n * @example\n * ```tsx\n * const translations = useI18nTranslation('common');\n * if (translations) {\n * console.log(translations.greeting);\n * }\n * ```\n */\nexport const useI18nTranslation = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n\tK extends keyof T,\n>(\n\tnamespace: K,\n\tfromCache: boolean = true,\n): M[K] | undefined => {\n\tconst { store, suspenseMode } = useI18nTypedStoreContext<T, L, M>();\n\n\tconst namespaceState = store.translations[namespace];\n\tconst locale = store.currentLocale;\n\tconst [_, setUpdate] = useReducer(() => ({}), {});\n\n\tconst forceUpdate = () => {\n\t\tsetUpdate();\n\t};\n\n\tconst load = async (needUpdate: boolean) => {\n\t\ttry {\n\t\t\tawait store.translations[namespace].load(store.currentLocale, fromCache);\n\t\t} catch (error) {\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (needUpdate) {\n\t\t\t\tforceUpdate();\n\t\t\t}\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tconst listener = () => {\n\t\t\tload(true);\n\t\t};\n\t\tstore.addChangeLocaleListener(listener);\n\t\treturn () => {\n\t\t\tstore.removeChangeLocaleListener(listener);\n\t\t};\n\t}, [namespace, fromCache]);\n\n\tuseEffect(() => {\n\t\tconst namespaceState = store.translations[namespace];\n\t\tconst locale = store.currentLocale;\n\t\tif (!namespaceState.translations[locale].namespace) {\n\t\t\tload(true);\n\t\t}\n\t}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);\n\n\treturn namespaceState.translations[locale].namespace || namespaceState.currentTranslation;\n};\n","import { useEffect, useReducer } from 'react';\nimport { useI18nTypedStoreContext } from './useI18nTypedStoreContext';\n\n/**\n * Hook for accessing translations with lazy loading and suspense support.\n * Throws a promise if translation is not yet loaded (for React Suspense).\n * Always returns a translation object (never undefined).\n *\n * @template T - Type of translations object (e.g., { common: 'common', errors: 'errors' })\n * @template L - Type of locales object (e.g., { en: 'en', ru: 'ru' })\n * @template M - Type of translation modules mapping\n * @template K - Namespace key from translations object\n *\n * @param namespace - Namespace key to load translations for\n * @param fromCache - Whether to use cached translation if available (default: true)\n * @returns Translation object for the specified namespace (never undefined)\n * @throws Promise if translation is not yet loaded (for React Suspense)\n *\n * @example\n * ```tsx\n * function MyComponent() {\n * const translations = useI18nTranslationLazy('common');\n * return <div>{translations.greeting}</div>;\n * }\n *\n * function App() {\n * return (\n * <Suspense fallback={<Loading />}>\n * <MyComponent />\n * </Suspense>\n * );\n * }\n * ```\n */\nexport const useI18nTranslationLazy = <\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n\tK extends keyof T,\n>(\n\tnamespace: K,\n\tfromCache: boolean = true,\n): M[K] => {\n\tconst { store, suspenseMode } = useI18nTypedStoreContext<T, L, M>();\n\n\tconst namespaceState = store.translations[namespace];\n\tconst locale = store.currentLocale;\n\tconst [_, setUpdate] = useReducer(() => ({}), {});\n\n\tconst forceUpdate = () => {\n\t\tsetUpdate();\n\t};\n\n\tconst load = async (needUpdate: boolean) => {\n\t\ttry {\n\t\t\tawait store.translations[namespace].load(store.currentLocale, fromCache);\n\t\t} catch (error) {\n\t\t\tthrow error;\n\t\t} finally {\n\t\t\tif (needUpdate) {\n\t\t\t\tforceUpdate();\n\t\t\t}\n\t\t}\n\t};\n\n\tuseEffect(() => {\n\t\tconst listener = (locale: keyof L) => {\n\t\t\tconst namespaceState = store.translations[namespace];\n\t\t\tif ((suspenseMode === 'first-load-locale' || suspenseMode === 'change-locale') && namespaceState.currentLocale !== locale) {\n\t\t\t\tsetUpdate();\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tload(true);\n\t\t};\n\t\tstore.addChangeLocaleListener(listener);\n\t\treturn () => {\n\t\t\tstore.removeChangeLocaleListener(listener);\n\t\t};\n\t}, [namespace, suspenseMode, fromCache]);\n\n\tuseEffect(() => {\n\t\tconst namespaceState = store.translations[namespace];\n\t\tconst locale = store.currentLocale;\n\t\tif (!namespaceState.translations[locale].namespace) {\n\t\t\tload(true);\n\t\t}\n\t}, [suspenseMode, namespace, fromCache, store.translations[namespace], store.currentLocale]);\n\n\tif (\n\t\t(!namespaceState.translations[locale].namespace && suspenseMode === 'first-load-locale') ||\n\t\t(suspenseMode === 'change-locale' && namespaceState.currentLocale !== locale)\n\t) {\n\t\tthrow load(false);\n\t}\n\n\tif (!namespaceState.currentTranslation) {\n\t\tthrow load(false);\n\t}\n\n\treturn namespaceState.translations[locale].namespace || namespaceState.currentTranslation;\n};\n","import { useSyncExternalStore } from 'react';\nimport { useI18nTypedStoreContext } from './useI18nTypedStoreContext';\n\n/**\n * Hook for accessing and managing the current locale.\n * Returns the current locale and a function to change it.\n * Supports SSR/SSG by using useSyncExternalStore for proper hydration.\n *\n * @template T - Type of translations object\n * @template L - Type of locales object\n * @template M - Type of translation modules mapping\n *\n * @returns Object with current locale and setLocale function\n *\n * @example\n * ```tsx\n * function LocaleSwitcher() {\n * const { locale, setLocale } = useI18nLocale();\n * return (\n * <select value={locale} onChange={(e) => setLocale(e.target.value as keyof Locales)}>\n * <option value=\"en\">English</option>\n * <option value=\"ru\">Русский</option>\n * </select>\n * );\n * }\n * ```\n */\nexport const useI18nLocale = <T extends Record<string, string>, L extends Record<string, string>, M extends { [K in keyof T]: any }>() => {\n\tconst { store } = useI18nTypedStoreContext<T, L, M>();\n\n\t// Use useSyncExternalStore for proper SSR/SSG hydration\n\tconst locale = useSyncExternalStore(\n\t\t(notify) => {\n\t\t\tconst listener = (locale: keyof L) => {\n\t\t\t\tnotify();\n\t\t\t};\n\t\t\tstore.addChangeLocaleListener(listener);\n\t\t\treturn () => {\n\t\t\t\tstore.removeChangeLocaleListener(listener);\n\t\t\t};\n\t\t},\n\t\t() => store.currentLocale,\n\t\t() => store.currentLocale, // Server snapshot (same as client for initial render)\n\t);\n\n\tconst updateLocale = (locale: keyof L) => {\n\t\tstore.changeLocale(locale);\n\t};\n\n\treturn { locale, setLocale: updateLocale };\n};\n","import type { TranslationStore } from '../types/translation-store';\n\n/**\n * Request context interface for locale detection in SSR environments.\n * Compatible with various SSR frameworks (Next.js, Remix, SvelteKit, etc.)\n * and standard request objects that provide query, cookies, and headers.\n */\nexport interface RequestContext {\n\tquery?: Record<string, string | string[]>;\n\tcookies?: Record<string, string>;\n\theaders?: Record<string, string | string[]> | Headers;\n}\n\n/**\n * Options for getting locale from SSR request.\n */\nexport interface GetLocaleFromRequestOptions {\n\t/**\n\t * Header name to read locale from (e.g., 'accept-language', 'x-locale')\n\t * @default 'accept-language'\n\t */\n\theaderName?: string;\n\t/**\n\t * Cookie name to read locale from\n\t */\n\tcookieName?: string;\n\t/**\n\t * Query parameter name to read locale from (e.g., 'locale', 'lang')\n\t */\n\tqueryParamName?: string;\n\t/**\n\t * Default locale to use if locale cannot be determined\n\t */\n\tdefaultLocale: string;\n\t/**\n\t * Available locales to validate against\n\t */\n\tavailableLocales: readonly string[];\n\t/**\n\t * Whether to parse Accept-Language header and find best match\n\t * @default true\n\t */\n\tparseAcceptLanguage?: boolean;\n}\n\n/**\n * Parses Accept-Language header and returns the best matching locale.\n *\n * @param acceptLanguage - Accept-Language header value\n * @param availableLocales - Available locales\n * @param defaultLocale - Default locale to use if no match found\n * @returns Best matching locale\n */\nfunction parseAcceptLanguage(\n\tacceptLanguage: string | undefined,\n\tavailableLocales: readonly string[],\n\tdefaultLocale: string,\n): string {\n\tif (!acceptLanguage) {\n\t\treturn defaultLocale;\n\t}\n\n\t// Parse Accept-Language header (e.g., \"en-US,en;q=0.9,ru;q=0.8\")\n\tconst languages = acceptLanguage\n\t\t.split(',')\n\t\t.map((lang) => {\n\t\t\tconst [locale, q = '1'] = lang.trim().split(';');\n\t\t\tconst quality = q.includes('q=') ? parseFloat(q.split('q=')[1]) : 1;\n\t\t\treturn { locale: locale.toLowerCase(), quality };\n\t\t})\n\t\t.sort((a, b) => b.quality - a.quality);\n\n\t// Try to find exact match first\n\tfor (const { locale } of languages) {\n\t\tconst exactMatch = availableLocales.find((l) => l.toLowerCase() === locale);\n\t\tif (exactMatch) {\n\t\t\treturn exactMatch;\n\t\t}\n\n\t\t// Try to find language match (e.g., 'en' matches 'en-US')\n\t\tconst languageCode = locale.split('-')[0];\n\t\tconst languageMatch = availableLocales.find((l) => l.toLowerCase().startsWith(languageCode));\n\t\tif (languageMatch) {\n\t\t\treturn languageMatch;\n\t\t}\n\t}\n\n\treturn defaultLocale;\n}\n\n/**\n * Gets locale from SSR request context.\n * Checks query params, cookies, and headers in that order.\n * Works with any SSR framework that provides these standard request properties.\n *\n * @template L - Type of locales object\n * @param context - SSR request context with query, cookies, and headers\n * @param options - Options for locale detection\n * @returns Detected locale key\n *\n * @example\n * ```ts\n * // Next.js example\n * import type { GetServerSidePropsContext } from 'next';\n *\n * export async function getServerSideProps(context: GetServerSidePropsContext) {\n * const locale = getLocaleFromRequest(context, {\n * defaultLocale: 'en',\n * availableLocales: ['en', 'ru'],\n * cookieName: 'locale',\n * queryParamName: 'locale',\n * });\n *\n * const store = storeFactory.type<MyTranslations>();\n * initializeStore(store, locale);\n *\n * return { props: { locale } };\n * }\n * ```\n *\n * @example\n * ```ts\n * // Remix example\n * export async function loader({ request }: LoaderFunctionArgs) {\n * const url = new URL(request.url);\n * const context: RequestContext = {\n * query: Object.fromEntries(url.searchParams),\n * headers: Object.fromEntries(request.headers),\n * };\n *\n * const locale = getLocaleFromRequest(context, {\n * defaultLocale: 'en',\n * availableLocales: ['en', 'ru'],\n * });\n *\n * return { locale };\n * }\n * ```\n */\nexport function getLocaleFromRequest<L extends Record<string, string>>(\n\tcontext: RequestContext,\n\toptions: GetLocaleFromRequestOptions,\n): keyof L {\n\tconst {\n\t\tqueryParamName = 'locale',\n\t\tcookieName,\n\t\theaderName = 'accept-language',\n\t\tdefaultLocale,\n\t\tavailableLocales,\n\t\tparseAcceptLanguage: shouldParseAcceptLanguage = true,\n\t} = options;\n\n\t// 1. Check query parameter\n\tif (queryParamName && context.query?.[queryParamName]) {\n\t\tconst queryLocale = Array.isArray(context.query[queryParamName])\n\t\t\t? context.query[queryParamName][0]\n\t\t\t: context.query[queryParamName];\n\t\tif (typeof queryLocale === 'string' && availableLocales.includes(queryLocale)) {\n\t\t\treturn queryLocale as keyof L;\n\t\t}\n\t}\n\n\t// 2. Check cookie\n\tif (cookieName && context.cookies?.[cookieName]) {\n\t\tconst cookieLocale = context.cookies[cookieName];\n\t\tif (typeof cookieLocale === 'string' && availableLocales.includes(cookieLocale)) {\n\t\t\treturn cookieLocale as keyof L;\n\t\t}\n\t}\n\n\t// 3. Check header (for Accept-Language, parse it)\n\tif (headerName && context.headers) {\n\t\tlet headerValue: string | undefined;\n\n\t\tif (context.headers instanceof Headers) {\n\t\t\theaderValue = context.headers.get(headerName) || undefined;\n\t\t} else {\n\t\t\tconst header = context.headers[headerName];\n\t\t\theaderValue = Array.isArray(header) ? header[0] : header;\n\t\t}\n\n\t\t// For Accept-Language, always call parseAcceptLanguage (even if headerValue is empty/undefined)\n\t\tif (headerName.toLowerCase() === 'accept-language' && shouldParseAcceptLanguage) {\n\t\t\tconst parsedLocale = parseAcceptLanguage(headerValue, availableLocales, defaultLocale);\n\t\t\treturn parsedLocale as keyof L;\n\t\t}\n\n\t\tif (headerValue && availableLocales.includes(headerValue)) {\n\t\t\treturn headerValue as keyof L;\n\t\t}\n\t}\n\n\treturn defaultLocale as keyof L;\n}\n\n/**\n * Initializes translation store with a specific locale.\n * Useful for SSR/SSG where you need to set the locale before rendering.\n *\n * @template T - Type of translations object\n * @template L - Type of locales object\n * @template M - Type of translation modules mapping\n * @param store - Translation store instance\n * @param locale - Locale to initialize with\n *\n * @example\n * ```ts\n * // In SSR handler\n * const locale = getLocaleFromRequest(context, {\n * defaultLocale: 'en',\n * availableLocales: ['en', 'ru'],\n * });\n *\n * const store = storeFactory.type<MyTranslations>();\n * initializeStore(store, locale);\n *\n * // Preload translations if needed\n * await store.common.load(locale);\n * ```\n */\nexport function initializeStore<\n\tT extends Record<string, string>,\n\tL extends Record<string, string>,\n\tM extends { [K in keyof T]: any },\n>(store: TranslationStore<T, L, M>, locale: keyof L): void {\n\tstore.changeLocale(locale);\n}\n\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "i18n-typed-store",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "Type-safe translation store for managing i18n locales with full TypeScript support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -32,15 +32,29 @@
|
|
|
32
32
|
},
|
|
33
33
|
"license": "MIT",
|
|
34
34
|
"type": "commonjs",
|
|
35
|
+
"peerDependencies": {
|
|
36
|
+
"react": ">=16.8.0",
|
|
37
|
+
"react-dom": ">=16.8.0"
|
|
38
|
+
},
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"type-fest": "^5.3.1"
|
|
41
|
+
},
|
|
35
42
|
"devDependencies": {
|
|
36
43
|
"@eslint/js": "^9.39.1",
|
|
37
44
|
"@eslint/json": "^0.14.0",
|
|
38
45
|
"@eslint/markdown": "^7.5.1",
|
|
46
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
47
|
+
"@testing-library/react": "^16.3.1",
|
|
39
48
|
"@types/node": "^24.10.1",
|
|
49
|
+
"@types/react": "^18.2.0",
|
|
50
|
+
"@types/react-dom": "^18.2.0",
|
|
51
|
+
"@types/testing-library__jest-dom": "^5.14.9",
|
|
40
52
|
"@vitest/coverage-v8": "^4.0.14",
|
|
41
53
|
"eslint": "^9.39.1",
|
|
42
54
|
"globals": "^16.5.0",
|
|
55
|
+
"jsdom": "^27.3.0",
|
|
43
56
|
"prettier": "^3.6.2",
|
|
57
|
+
"react": "^19.2.3",
|
|
44
58
|
"tsup": "^8.5.1",
|
|
45
59
|
"typescript": "^5.9.3",
|
|
46
60
|
"typescript-eslint": "^8.48.0",
|