locl-js 1.0.2 → 2.0.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 +518 -0
- package/dist/LocL-fN9Yari8.d.mts +437 -0
- package/dist/LocL-fN9Yari8.d.ts +437 -0
- package/dist/compat.d.mts +44 -0
- package/dist/compat.d.ts +44 -0
- package/dist/compat.js +137 -0
- package/dist/compat.mjs +111 -0
- package/dist/index.d.mts +20 -341
- package/dist/index.d.ts +20 -341
- package/dist/index.js +288 -76
- package/dist/index.mjs +283 -73
- package/dist/react.d.mts +120 -0
- package/dist/react.d.ts +120 -0
- package/dist/react.js +228 -0
- package/dist/react.mjs +188 -0
- package/package.json +24 -1
- package/readme.md +0 -321
package/dist/react.d.ts
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import React, { ReactElement, ReactNode } from 'react';
|
|
2
|
+
import { S as ScopeType, N as NestedKeyOf, T as TranslationObjectFor, P as PluralKeys, I as InterpolationOptions, a as LocL, c as Language } from './LocL-fN9Yari8.js';
|
|
3
|
+
|
|
4
|
+
type TransKey<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> = (NestedKeyOf<TranslationObjectFor<S, T, Fallback>> | PluralKeys<S, T, Fallback>) & string;
|
|
5
|
+
interface TransProps<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>> {
|
|
6
|
+
/** The translation key to look up (with IDE autocomplete + fallback tolerance) */
|
|
7
|
+
i18nKey: K | (string & {});
|
|
8
|
+
/** Values to interpolate into variable placeholders ({name}, {{name}}) */
|
|
9
|
+
values?: InterpolationOptions;
|
|
10
|
+
/**
|
|
11
|
+
* Component elements or functions to substitute for `<tag>content</tag>`
|
|
12
|
+
* @example
|
|
13
|
+
* components={{
|
|
14
|
+
* bold: <strong />,
|
|
15
|
+
* link: (content) => <a href="/terms">{content}</a>
|
|
16
|
+
* }}
|
|
17
|
+
*/
|
|
18
|
+
components?: Record<string, ReactElement | ((content: ReactNode) => ReactNode)>;
|
|
19
|
+
/** Optional custom translator instance if not using LocLContext */
|
|
20
|
+
translator?: LocL<any, any, any, any, any>;
|
|
21
|
+
/** Optional namespace scope for the translation key */
|
|
22
|
+
scope?: S;
|
|
23
|
+
/** Fallback string if key is not found */
|
|
24
|
+
fallback?: string;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Renders translated text with rich React component interpolation without `dangerouslySetInnerHTML`.
|
|
28
|
+
* Subscribes reactively to language updates and supports strict type checking and autocompletion.
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* <Trans
|
|
32
|
+
* i18nKey="agreement"
|
|
33
|
+
* values={{ name: "Alice" }}
|
|
34
|
+
* components={{
|
|
35
|
+
* bold: <strong />,
|
|
36
|
+
* link: <a href="/terms" />
|
|
37
|
+
* }}
|
|
38
|
+
* />
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
declare function Trans<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>>({ i18nKey, values, components, translator: customTranslator, scope, fallback }: TransProps<T, Fallback, S, K>): ReactElement;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Global type registry for LocL React.
|
|
45
|
+
* Augment this interface in user application code for zero-boilerplate,
|
|
46
|
+
* 100% type-safe autocompletion across all hooks and components:
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* declare module "locl-js/react" {
|
|
51
|
+
* interface LocLRegister {
|
|
52
|
+
* translator: typeof translator;
|
|
53
|
+
* }
|
|
54
|
+
* }
|
|
55
|
+
* ```
|
|
56
|
+
*/
|
|
57
|
+
interface LocLRegister {
|
|
58
|
+
}
|
|
59
|
+
type DefaultResources = LocLRegister extends {
|
|
60
|
+
translator: LocL<infer Res, any, any, any, any>;
|
|
61
|
+
} ? Res : LocLRegister extends {
|
|
62
|
+
resources: infer Res extends Record<string, any>;
|
|
63
|
+
} ? Res : Record<string, any>;
|
|
64
|
+
type DefaultFallback<T = DefaultResources> = LocLRegister extends {
|
|
65
|
+
translator: LocL<any, infer FB, any, any, any>;
|
|
66
|
+
} ? FB : LocLRegister extends {
|
|
67
|
+
fallbackLanguage: infer FB extends string;
|
|
68
|
+
} ? FB : [keyof T & string] extends [never] ? string : keyof T & string;
|
|
69
|
+
declare const LocLContext: React.Context<LocL<any, any, any, any, any> | null>;
|
|
70
|
+
interface LocLProviderProps<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> {
|
|
71
|
+
translator: LocL<T, Fallback, S, any, any>;
|
|
72
|
+
children: ReactNode;
|
|
73
|
+
}
|
|
74
|
+
declare function LocLProvider<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined>({ translator, children }: LocLProviderProps<T, Fallback, S>): React.JSX.Element;
|
|
75
|
+
interface UseLocLResult<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> {
|
|
76
|
+
translator: LocL<T, Fallback, S, any, any>;
|
|
77
|
+
language: Language<T>;
|
|
78
|
+
changeLanguage: (lang: Language<T>) => void;
|
|
79
|
+
t: LocL<T, Fallback, S>["t"];
|
|
80
|
+
plural: LocL<T, Fallback, S>["plural"];
|
|
81
|
+
rich: LocL<T, Fallback, S>["rich"];
|
|
82
|
+
format: LocL<T, Fallback, S>["format"];
|
|
83
|
+
}
|
|
84
|
+
interface UseTranslationResult<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined> extends UseLocLResult<T, Fallback, S> {
|
|
85
|
+
}
|
|
86
|
+
declare function useLocL<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<any, any, any, any, any>): UseLocLResult<T, Fallback, S>;
|
|
87
|
+
declare function useTranslation<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>>(scope?: undefined, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
|
|
88
|
+
declare function useTranslation<T extends Record<string, any> = DefaultResources, Fallback extends keyof T & string = DefaultFallback<T>, S extends ScopeType<T, Fallback> = ScopeType<T, Fallback>>(scope: S, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
89
|
+
interface BoundLocLProviderProps<T extends Record<string, any>, Fallback extends keyof T & string> {
|
|
90
|
+
/** Optional override translator; defaults to the instance passed to `createLocLReact` */
|
|
91
|
+
translator?: LocL<any, any, any, any, any>;
|
|
92
|
+
children: ReactNode;
|
|
93
|
+
}
|
|
94
|
+
interface LocLReactSuite<T extends Record<string, any>, Fallback extends keyof T & string> {
|
|
95
|
+
LocLContext: React.Context<LocL<any, any, any, any, any> | null>;
|
|
96
|
+
LocLProvider: (props: BoundLocLProviderProps<T, Fallback>) => ReactElement;
|
|
97
|
+
useLocL: <S extends ScopeType<T, Fallback> = undefined>(customTranslator?: LocL<any, any, any, any, any>) => UseLocLResult<T, Fallback, S>;
|
|
98
|
+
useTranslation: {
|
|
99
|
+
(scope?: undefined, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, undefined>;
|
|
100
|
+
<S extends ScopeType<T, Fallback>>(scope: S, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
101
|
+
<S extends ScopeType<T, Fallback> = any>(scope?: S, customTranslator?: LocL<any, any, any, any, any>): UseTranslationResult<T, Fallback, S>;
|
|
102
|
+
};
|
|
103
|
+
Trans: <S extends ScopeType<T, Fallback> = undefined, K extends string = TransKey<T, Fallback, S>>(props: TransProps<T, Fallback, S, K>) => ReactElement;
|
|
104
|
+
translator: LocL<T, Fallback, any, any, any>;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Creates a pre-bound, zero-declaration React integration suite for a LocL translator instance.
|
|
108
|
+
*
|
|
109
|
+
* Inifers resources, fallback language, translation keys, and namespace scopes
|
|
110
|
+
* automatically without requiring `.d.ts` module declarations or manual generics.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```ts
|
|
114
|
+
* // i18n-react.ts
|
|
115
|
+
* export const { LocLProvider, useTranslation, useLocL, Trans } = createLocLReact(translator);
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare function createLocLReact<T extends Record<string, any>, Fallback extends keyof T & string>(defaultTranslator: LocL<T, Fallback, any, any, any>): LocLReactSuite<T, Fallback>;
|
|
119
|
+
|
|
120
|
+
export { type BoundLocLProviderProps, type DefaultFallback, type DefaultResources, LocLContext, LocLProvider, type LocLProviderProps, type LocLReactSuite, type LocLRegister, Trans, type TransKey, type TransProps, type UseLocLResult, type UseTranslationResult, createLocLReact, useLocL, useTranslation };
|
package/dist/react.js
ADDED
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/react/index.tsx
|
|
31
|
+
var react_exports = {};
|
|
32
|
+
__export(react_exports, {
|
|
33
|
+
LocLContext: () => LocLContext,
|
|
34
|
+
LocLProvider: () => LocLProvider,
|
|
35
|
+
Trans: () => Trans,
|
|
36
|
+
createLocLReact: () => createLocLReact,
|
|
37
|
+
useLocL: () => useLocL,
|
|
38
|
+
useTranslation: () => useTranslation
|
|
39
|
+
});
|
|
40
|
+
module.exports = __toCommonJS(react_exports);
|
|
41
|
+
var import_react2 = require("react");
|
|
42
|
+
|
|
43
|
+
// src/react/Trans.tsx
|
|
44
|
+
var import_react = __toESM(require("react"));
|
|
45
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
46
|
+
function Trans({
|
|
47
|
+
i18nKey,
|
|
48
|
+
values,
|
|
49
|
+
components = {},
|
|
50
|
+
translator: customTranslator,
|
|
51
|
+
scope,
|
|
52
|
+
fallback
|
|
53
|
+
}) {
|
|
54
|
+
const contextTranslator = (0, import_react.useContext)(LocLContext);
|
|
55
|
+
const activeTranslator = customTranslator ?? contextTranslator;
|
|
56
|
+
const getSnapshot = () => activeTranslator ? activeTranslator.getLanguage() : "";
|
|
57
|
+
const language = (0, import_react.useSyncExternalStore)(
|
|
58
|
+
(onStoreChange) => activeTranslator ? activeTranslator.subscribe(onStoreChange) : () => {
|
|
59
|
+
},
|
|
60
|
+
getSnapshot,
|
|
61
|
+
getSnapshot
|
|
62
|
+
);
|
|
63
|
+
const scopeKey = Array.isArray(scope) ? scope.join("|") : scope;
|
|
64
|
+
const scopedTranslator = (0, import_react.useMemo)(() => {
|
|
65
|
+
if (!activeTranslator) return null;
|
|
66
|
+
return scope !== void 0 ? activeTranslator.withConfig({ scope }) : activeTranslator;
|
|
67
|
+
}, [activeTranslator, scopeKey, language]);
|
|
68
|
+
const raw = (0, import_react.useMemo)(() => {
|
|
69
|
+
if (!scopedTranslator) {
|
|
70
|
+
return String(fallback ?? i18nKey);
|
|
71
|
+
}
|
|
72
|
+
const tInstance = scopedTranslator;
|
|
73
|
+
const res = tInstance.t(i18nKey, values);
|
|
74
|
+
const keyExists = tInstance.get(i18nKey) !== void 0;
|
|
75
|
+
if (keyExists) {
|
|
76
|
+
return String(res);
|
|
77
|
+
}
|
|
78
|
+
return String(fallback ?? i18nKey);
|
|
79
|
+
}, [scopedTranslator, i18nKey, values, fallback, language]);
|
|
80
|
+
const tagRegex = /<([a-zA-Z0-9_-]+)>([\s\S]*?)<\/\1>/g;
|
|
81
|
+
const nodes = [];
|
|
82
|
+
let lastIndex = 0;
|
|
83
|
+
let match;
|
|
84
|
+
let counter = 0;
|
|
85
|
+
while ((match = tagRegex.exec(raw)) !== null) {
|
|
86
|
+
if (match.index > lastIndex) {
|
|
87
|
+
nodes.push(raw.slice(lastIndex, match.index));
|
|
88
|
+
}
|
|
89
|
+
const tagName = match[1];
|
|
90
|
+
const content = match[2];
|
|
91
|
+
const component = components[tagName];
|
|
92
|
+
if ((0, import_react.isValidElement)(component)) {
|
|
93
|
+
nodes.push(
|
|
94
|
+
(0, import_react.cloneElement)(component, { key: `trans-${tagName}-${counter++}` }, content)
|
|
95
|
+
);
|
|
96
|
+
} else if (typeof component === "function") {
|
|
97
|
+
nodes.push(
|
|
98
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_react.default.Fragment, { children: component(content) }, `trans-${tagName}-${counter++}`)
|
|
99
|
+
);
|
|
100
|
+
} else {
|
|
101
|
+
nodes.push(match[0]);
|
|
102
|
+
}
|
|
103
|
+
lastIndex = match.index + match[0].length;
|
|
104
|
+
}
|
|
105
|
+
if (lastIndex < raw.length) {
|
|
106
|
+
nodes.push(raw.slice(lastIndex));
|
|
107
|
+
}
|
|
108
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: nodes });
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// src/react/index.tsx
|
|
112
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
113
|
+
var LocLContext = (0, import_react2.createContext)(null);
|
|
114
|
+
function LocLProvider({ translator, children }) {
|
|
115
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LocLContext.Provider, { value: translator, children });
|
|
116
|
+
}
|
|
117
|
+
function useLocL(customTranslator) {
|
|
118
|
+
const contextTranslator = (0, import_react2.useContext)(LocLContext);
|
|
119
|
+
const translator = customTranslator ?? contextTranslator;
|
|
120
|
+
if (!translator) {
|
|
121
|
+
throw new Error(
|
|
122
|
+
"[LocL] `useLocL` or `useTranslation` must be used within a `<LocLProvider>` or passed an explicit translator instance."
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
const getSnapshot = () => translator.getLanguage();
|
|
126
|
+
const language = (0, import_react2.useSyncExternalStore)(
|
|
127
|
+
(onStoreChange) => translator.subscribe(onStoreChange),
|
|
128
|
+
getSnapshot,
|
|
129
|
+
getSnapshot
|
|
130
|
+
);
|
|
131
|
+
return {
|
|
132
|
+
translator,
|
|
133
|
+
language,
|
|
134
|
+
changeLanguage: (lang) => translator.changeLanguage(lang),
|
|
135
|
+
t: translator.t.bind(translator),
|
|
136
|
+
plural: translator.plural.bind(translator),
|
|
137
|
+
rich: translator.rich.bind(translator),
|
|
138
|
+
format: translator.format.bind(translator)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
function useTranslation(scope, customTranslator) {
|
|
142
|
+
const { translator, language, changeLanguage } = useLocL(customTranslator);
|
|
143
|
+
const scopeKey = Array.isArray(scope) ? scope.join("|") : scope;
|
|
144
|
+
const scopedTranslator = (0, import_react2.useMemo)(() => {
|
|
145
|
+
return scope !== void 0 ? translator.withConfig({ scope }) : translator;
|
|
146
|
+
}, [translator, scopeKey]);
|
|
147
|
+
return {
|
|
148
|
+
translator: scopedTranslator,
|
|
149
|
+
language,
|
|
150
|
+
changeLanguage,
|
|
151
|
+
t: scopedTranslator.t.bind(scopedTranslator),
|
|
152
|
+
plural: scopedTranslator.plural.bind(scopedTranslator),
|
|
153
|
+
rich: scopedTranslator.rich.bind(scopedTranslator),
|
|
154
|
+
format: scopedTranslator.format.bind(scopedTranslator)
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
function createLocLReact(defaultTranslator) {
|
|
158
|
+
const BoundContext = (0, import_react2.createContext)(null);
|
|
159
|
+
function BoundLocLProvider({
|
|
160
|
+
translator = defaultTranslator,
|
|
161
|
+
children
|
|
162
|
+
}) {
|
|
163
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(LocLContext.Provider, { value: translator, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(BoundContext.Provider, { value: translator, children }) });
|
|
164
|
+
}
|
|
165
|
+
function useBoundLocL(customTranslator) {
|
|
166
|
+
const contextTranslator = (0, import_react2.useContext)(BoundContext);
|
|
167
|
+
const translator = customTranslator ?? contextTranslator ?? defaultTranslator;
|
|
168
|
+
const getSnapshot = () => translator.getLanguage();
|
|
169
|
+
const language = (0, import_react2.useSyncExternalStore)(
|
|
170
|
+
(onStoreChange) => translator.subscribe(onStoreChange),
|
|
171
|
+
getSnapshot,
|
|
172
|
+
getSnapshot
|
|
173
|
+
);
|
|
174
|
+
return {
|
|
175
|
+
translator,
|
|
176
|
+
language,
|
|
177
|
+
changeLanguage: (lang) => translator.changeLanguage(lang),
|
|
178
|
+
t: translator.t.bind(translator),
|
|
179
|
+
plural: translator.plural.bind(translator),
|
|
180
|
+
rich: translator.rich.bind(translator),
|
|
181
|
+
format: translator.format.bind(translator)
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
function useBoundTranslation(scope, customTranslator) {
|
|
185
|
+
const { translator, language, changeLanguage } = useBoundLocL(customTranslator);
|
|
186
|
+
const scopeKey = Array.isArray(scope) ? scope.join("|") : scope;
|
|
187
|
+
const scopedTranslator = (0, import_react2.useMemo)(() => {
|
|
188
|
+
return scope !== void 0 ? translator.withConfig({ scope }) : translator;
|
|
189
|
+
}, [translator, scopeKey]);
|
|
190
|
+
return {
|
|
191
|
+
translator: scopedTranslator,
|
|
192
|
+
language,
|
|
193
|
+
changeLanguage,
|
|
194
|
+
t: scopedTranslator.t.bind(scopedTranslator),
|
|
195
|
+
plural: scopedTranslator.plural.bind(scopedTranslator),
|
|
196
|
+
rich: scopedTranslator.rich.bind(scopedTranslator),
|
|
197
|
+
format: scopedTranslator.format.bind(scopedTranslator)
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function BoundTrans(props) {
|
|
201
|
+
const contextTranslator = (0, import_react2.useContext)(BoundContext);
|
|
202
|
+
const activeTranslator = props.translator ?? contextTranslator ?? defaultTranslator;
|
|
203
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
204
|
+
Trans,
|
|
205
|
+
{
|
|
206
|
+
...props,
|
|
207
|
+
translator: activeTranslator
|
|
208
|
+
}
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
LocLContext: BoundContext,
|
|
213
|
+
LocLProvider: BoundLocLProvider,
|
|
214
|
+
useLocL: useBoundLocL,
|
|
215
|
+
useTranslation: useBoundTranslation,
|
|
216
|
+
Trans: BoundTrans,
|
|
217
|
+
translator: defaultTranslator
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
221
|
+
0 && (module.exports = {
|
|
222
|
+
LocLContext,
|
|
223
|
+
LocLProvider,
|
|
224
|
+
Trans,
|
|
225
|
+
createLocLReact,
|
|
226
|
+
useLocL,
|
|
227
|
+
useTranslation
|
|
228
|
+
});
|
package/dist/react.mjs
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// src/react/index.tsx
|
|
2
|
+
import { createContext, useContext as useContext2, useSyncExternalStore as useSyncExternalStore2, useMemo as useMemo2 } from "react";
|
|
3
|
+
|
|
4
|
+
// src/react/Trans.tsx
|
|
5
|
+
import React, { isValidElement, cloneElement, useContext, useMemo, useSyncExternalStore } from "react";
|
|
6
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
7
|
+
function Trans({
|
|
8
|
+
i18nKey,
|
|
9
|
+
values,
|
|
10
|
+
components = {},
|
|
11
|
+
translator: customTranslator,
|
|
12
|
+
scope,
|
|
13
|
+
fallback
|
|
14
|
+
}) {
|
|
15
|
+
const contextTranslator = useContext(LocLContext);
|
|
16
|
+
const activeTranslator = customTranslator ?? contextTranslator;
|
|
17
|
+
const getSnapshot = () => activeTranslator ? activeTranslator.getLanguage() : "";
|
|
18
|
+
const language = useSyncExternalStore(
|
|
19
|
+
(onStoreChange) => activeTranslator ? activeTranslator.subscribe(onStoreChange) : () => {
|
|
20
|
+
},
|
|
21
|
+
getSnapshot,
|
|
22
|
+
getSnapshot
|
|
23
|
+
);
|
|
24
|
+
const scopeKey = Array.isArray(scope) ? scope.join("|") : scope;
|
|
25
|
+
const scopedTranslator = useMemo(() => {
|
|
26
|
+
if (!activeTranslator) return null;
|
|
27
|
+
return scope !== void 0 ? activeTranslator.withConfig({ scope }) : activeTranslator;
|
|
28
|
+
}, [activeTranslator, scopeKey, language]);
|
|
29
|
+
const raw = useMemo(() => {
|
|
30
|
+
if (!scopedTranslator) {
|
|
31
|
+
return String(fallback ?? i18nKey);
|
|
32
|
+
}
|
|
33
|
+
const tInstance = scopedTranslator;
|
|
34
|
+
const res = tInstance.t(i18nKey, values);
|
|
35
|
+
const keyExists = tInstance.get(i18nKey) !== void 0;
|
|
36
|
+
if (keyExists) {
|
|
37
|
+
return String(res);
|
|
38
|
+
}
|
|
39
|
+
return String(fallback ?? i18nKey);
|
|
40
|
+
}, [scopedTranslator, i18nKey, values, fallback, language]);
|
|
41
|
+
const tagRegex = /<([a-zA-Z0-9_-]+)>([\s\S]*?)<\/\1>/g;
|
|
42
|
+
const nodes = [];
|
|
43
|
+
let lastIndex = 0;
|
|
44
|
+
let match;
|
|
45
|
+
let counter = 0;
|
|
46
|
+
while ((match = tagRegex.exec(raw)) !== null) {
|
|
47
|
+
if (match.index > lastIndex) {
|
|
48
|
+
nodes.push(raw.slice(lastIndex, match.index));
|
|
49
|
+
}
|
|
50
|
+
const tagName = match[1];
|
|
51
|
+
const content = match[2];
|
|
52
|
+
const component = components[tagName];
|
|
53
|
+
if (isValidElement(component)) {
|
|
54
|
+
nodes.push(
|
|
55
|
+
cloneElement(component, { key: `trans-${tagName}-${counter++}` }, content)
|
|
56
|
+
);
|
|
57
|
+
} else if (typeof component === "function") {
|
|
58
|
+
nodes.push(
|
|
59
|
+
/* @__PURE__ */ jsx(React.Fragment, { children: component(content) }, `trans-${tagName}-${counter++}`)
|
|
60
|
+
);
|
|
61
|
+
} else {
|
|
62
|
+
nodes.push(match[0]);
|
|
63
|
+
}
|
|
64
|
+
lastIndex = match.index + match[0].length;
|
|
65
|
+
}
|
|
66
|
+
if (lastIndex < raw.length) {
|
|
67
|
+
nodes.push(raw.slice(lastIndex));
|
|
68
|
+
}
|
|
69
|
+
return /* @__PURE__ */ jsx(Fragment, { children: nodes });
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// src/react/index.tsx
|
|
73
|
+
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
74
|
+
var LocLContext = createContext(null);
|
|
75
|
+
function LocLProvider({ translator, children }) {
|
|
76
|
+
return /* @__PURE__ */ jsx2(LocLContext.Provider, { value: translator, children });
|
|
77
|
+
}
|
|
78
|
+
function useLocL(customTranslator) {
|
|
79
|
+
const contextTranslator = useContext2(LocLContext);
|
|
80
|
+
const translator = customTranslator ?? contextTranslator;
|
|
81
|
+
if (!translator) {
|
|
82
|
+
throw new Error(
|
|
83
|
+
"[LocL] `useLocL` or `useTranslation` must be used within a `<LocLProvider>` or passed an explicit translator instance."
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
const getSnapshot = () => translator.getLanguage();
|
|
87
|
+
const language = useSyncExternalStore2(
|
|
88
|
+
(onStoreChange) => translator.subscribe(onStoreChange),
|
|
89
|
+
getSnapshot,
|
|
90
|
+
getSnapshot
|
|
91
|
+
);
|
|
92
|
+
return {
|
|
93
|
+
translator,
|
|
94
|
+
language,
|
|
95
|
+
changeLanguage: (lang) => translator.changeLanguage(lang),
|
|
96
|
+
t: translator.t.bind(translator),
|
|
97
|
+
plural: translator.plural.bind(translator),
|
|
98
|
+
rich: translator.rich.bind(translator),
|
|
99
|
+
format: translator.format.bind(translator)
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
function useTranslation(scope, customTranslator) {
|
|
103
|
+
const { translator, language, changeLanguage } = useLocL(customTranslator);
|
|
104
|
+
const scopeKey = Array.isArray(scope) ? scope.join("|") : scope;
|
|
105
|
+
const scopedTranslator = useMemo2(() => {
|
|
106
|
+
return scope !== void 0 ? translator.withConfig({ scope }) : translator;
|
|
107
|
+
}, [translator, scopeKey]);
|
|
108
|
+
return {
|
|
109
|
+
translator: scopedTranslator,
|
|
110
|
+
language,
|
|
111
|
+
changeLanguage,
|
|
112
|
+
t: scopedTranslator.t.bind(scopedTranslator),
|
|
113
|
+
plural: scopedTranslator.plural.bind(scopedTranslator),
|
|
114
|
+
rich: scopedTranslator.rich.bind(scopedTranslator),
|
|
115
|
+
format: scopedTranslator.format.bind(scopedTranslator)
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function createLocLReact(defaultTranslator) {
|
|
119
|
+
const BoundContext = createContext(null);
|
|
120
|
+
function BoundLocLProvider({
|
|
121
|
+
translator = defaultTranslator,
|
|
122
|
+
children
|
|
123
|
+
}) {
|
|
124
|
+
return /* @__PURE__ */ jsx2(LocLContext.Provider, { value: translator, children: /* @__PURE__ */ jsx2(BoundContext.Provider, { value: translator, children }) });
|
|
125
|
+
}
|
|
126
|
+
function useBoundLocL(customTranslator) {
|
|
127
|
+
const contextTranslator = useContext2(BoundContext);
|
|
128
|
+
const translator = customTranslator ?? contextTranslator ?? defaultTranslator;
|
|
129
|
+
const getSnapshot = () => translator.getLanguage();
|
|
130
|
+
const language = useSyncExternalStore2(
|
|
131
|
+
(onStoreChange) => translator.subscribe(onStoreChange),
|
|
132
|
+
getSnapshot,
|
|
133
|
+
getSnapshot
|
|
134
|
+
);
|
|
135
|
+
return {
|
|
136
|
+
translator,
|
|
137
|
+
language,
|
|
138
|
+
changeLanguage: (lang) => translator.changeLanguage(lang),
|
|
139
|
+
t: translator.t.bind(translator),
|
|
140
|
+
plural: translator.plural.bind(translator),
|
|
141
|
+
rich: translator.rich.bind(translator),
|
|
142
|
+
format: translator.format.bind(translator)
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function useBoundTranslation(scope, customTranslator) {
|
|
146
|
+
const { translator, language, changeLanguage } = useBoundLocL(customTranslator);
|
|
147
|
+
const scopeKey = Array.isArray(scope) ? scope.join("|") : scope;
|
|
148
|
+
const scopedTranslator = useMemo2(() => {
|
|
149
|
+
return scope !== void 0 ? translator.withConfig({ scope }) : translator;
|
|
150
|
+
}, [translator, scopeKey]);
|
|
151
|
+
return {
|
|
152
|
+
translator: scopedTranslator,
|
|
153
|
+
language,
|
|
154
|
+
changeLanguage,
|
|
155
|
+
t: scopedTranslator.t.bind(scopedTranslator),
|
|
156
|
+
plural: scopedTranslator.plural.bind(scopedTranslator),
|
|
157
|
+
rich: scopedTranslator.rich.bind(scopedTranslator),
|
|
158
|
+
format: scopedTranslator.format.bind(scopedTranslator)
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
function BoundTrans(props) {
|
|
162
|
+
const contextTranslator = useContext2(BoundContext);
|
|
163
|
+
const activeTranslator = props.translator ?? contextTranslator ?? defaultTranslator;
|
|
164
|
+
return /* @__PURE__ */ jsx2(
|
|
165
|
+
Trans,
|
|
166
|
+
{
|
|
167
|
+
...props,
|
|
168
|
+
translator: activeTranslator
|
|
169
|
+
}
|
|
170
|
+
);
|
|
171
|
+
}
|
|
172
|
+
return {
|
|
173
|
+
LocLContext: BoundContext,
|
|
174
|
+
LocLProvider: BoundLocLProvider,
|
|
175
|
+
useLocL: useBoundLocL,
|
|
176
|
+
useTranslation: useBoundTranslation,
|
|
177
|
+
Trans: BoundTrans,
|
|
178
|
+
translator: defaultTranslator
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
export {
|
|
182
|
+
LocLContext,
|
|
183
|
+
LocLProvider,
|
|
184
|
+
Trans,
|
|
185
|
+
createLocLReact,
|
|
186
|
+
useLocL,
|
|
187
|
+
useTranslation
|
|
188
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "locl-js",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Modern TypeScript internationalization (i18n) library with type-safety, performance, and backend usability",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Sztrida Sámuel <sztsam@gmail.com>",
|
|
@@ -9,8 +9,27 @@
|
|
|
9
9
|
"types": "dist/index.d.ts",
|
|
10
10
|
"exports": {
|
|
11
11
|
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
12
13
|
"import": "./dist/index.mjs",
|
|
13
14
|
"require": "./dist/index.cjs"
|
|
15
|
+
},
|
|
16
|
+
"./react": {
|
|
17
|
+
"types": "./dist/react.d.ts",
|
|
18
|
+
"import": "./dist/react.mjs",
|
|
19
|
+
"require": "./dist/react.cjs"
|
|
20
|
+
},
|
|
21
|
+
"./compat": {
|
|
22
|
+
"types": "./dist/compat.d.ts",
|
|
23
|
+
"import": "./dist/compat.mjs",
|
|
24
|
+
"require": "./dist/compat.cjs"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"react": ">=16.8.0"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"react": {
|
|
32
|
+
"optional": true
|
|
14
33
|
}
|
|
15
34
|
},
|
|
16
35
|
"repository": {
|
|
@@ -26,7 +45,11 @@
|
|
|
26
45
|
"devDependencies": {
|
|
27
46
|
"@types/jest": "^30.0.0",
|
|
28
47
|
"@types/node": "^24.5.2",
|
|
48
|
+
"@types/react": "^19.3.0",
|
|
49
|
+
"@types/react-dom": "^19.3.0",
|
|
29
50
|
"jest": "^30.2.0",
|
|
51
|
+
"react": "^19.3.0",
|
|
52
|
+
"react-dom": "^19.3.0",
|
|
30
53
|
"ts-jest": "^29.4.6",
|
|
31
54
|
"ts-node": "^10.9.2",
|
|
32
55
|
"tsup": "^8.5.1",
|