@belocal/react 0.5.0 → 0.5.2

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/dist/index.cjs CHANGED
@@ -40,17 +40,23 @@ var useBelocalContext = () => {
40
40
  var useBelocal = () => {
41
41
  return useBelocalContext();
42
42
  };
43
+ var translationCache = /* @__PURE__ */ new Map();
44
+ var pendingRequests = /* @__PURE__ */ new Map();
43
45
  var Translate = ({
44
46
  text,
45
47
  lang,
46
48
  source_lang,
47
49
  context,
48
50
  user_context,
49
- fallback
51
+ fallback,
52
+ onSuccess,
53
+ onError,
54
+ dots
50
55
  }) => {
51
56
  const { engine, defaultLang } = useBelocal();
52
57
  const [translatedText, setTranslatedText] = react.useState(fallback || text);
53
58
  const [isLoading, setIsLoading] = react.useState(false);
59
+ const [dotCount, setDotCount] = react.useState(1);
54
60
  const targetLang = lang ?? defaultLang;
55
61
  const finalContext = react.useMemo(() => {
56
62
  const ctx = { ...context };
@@ -59,21 +65,68 @@ var Translate = ({
59
65
  }
60
66
  return ctx;
61
67
  }, [context, user_context]);
62
- react.useEffect(() => {
63
- if (!targetLang) {
64
- setTranslatedText(text);
65
- return;
66
- }
68
+ const cacheKey = react.useMemo(() => {
69
+ return JSON.stringify({
70
+ text,
71
+ targetLang,
72
+ source_lang,
73
+ finalContext
74
+ });
75
+ }, [text, targetLang, source_lang, finalContext]);
76
+ const attachHandlers = (promise) => {
67
77
  setIsLoading(true);
68
- engine.translate(text, targetLang, source_lang, finalContext).then((translation) => {
78
+ promise.then((translation) => {
69
79
  setTranslatedText(translation);
80
+ onSuccess?.(translation);
70
81
  }).catch((error) => {
71
82
  console.error("Translation error:", error);
72
83
  setTranslatedText(text);
84
+ onError?.(error instanceof Error ? error : new Error(String(error)));
73
85
  }).finally(() => {
74
86
  setIsLoading(false);
75
87
  });
76
- }, [text, targetLang, source_lang, finalContext, engine]);
88
+ };
89
+ react.useEffect(() => {
90
+ if (!targetLang) {
91
+ setTranslatedText(text);
92
+ return;
93
+ }
94
+ const cachedTranslation = translationCache.get(cacheKey);
95
+ if (cachedTranslation) {
96
+ setTranslatedText(cachedTranslation);
97
+ return;
98
+ }
99
+ const pendingRequest = pendingRequests.get(cacheKey);
100
+ if (pendingRequest) {
101
+ attachHandlers(pendingRequest);
102
+ return;
103
+ }
104
+ const translationPromise = engine.translate(text, targetLang, source_lang, finalContext).then((translation) => {
105
+ translationCache.set(cacheKey, translation);
106
+ pendingRequests.delete(cacheKey);
107
+ return translation;
108
+ }).catch((error) => {
109
+ pendingRequests.delete(cacheKey);
110
+ throw error;
111
+ });
112
+ pendingRequests.set(cacheKey, translationPromise);
113
+ attachHandlers(translationPromise);
114
+ }, [text, targetLang, source_lang, finalContext, engine, cacheKey]);
115
+ react.useEffect(() => {
116
+ if (!isLoading || !dots) {
117
+ if (!isLoading) {
118
+ setDotCount(1);
119
+ }
120
+ return;
121
+ }
122
+ const interval = setInterval(() => {
123
+ setDotCount((prev) => prev >= 3 ? 1 : prev + 1);
124
+ }, 500);
125
+ return () => clearInterval(interval);
126
+ }, [isLoading, dots]);
127
+ if (isLoading && dots) {
128
+ return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: ".".repeat(dotCount) });
129
+ }
77
130
  if (isLoading) {
78
131
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback || text });
79
132
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/BelocalProvider.tsx","../src/useBelocal.ts","../src/Translate.tsx"],"names":["createContext","useMemo","BelocalEngine","useContext","useState","useEffect","jsx","Fragment"],"mappings":";;;;;;;AAiBA,IAAM,cAAA,GAAiBA,oBAAyC,IAAI,CAAA;AAE7D,IAAM,kBAAkD,CAAC;AAAA,EAC9D,MAAA;AAAA,EACA,OAAA;AAAA,EACA,WAAA;AAAA,EACA,aAAA;AAAA,EACA,SAAA;AAAA,EACA;AACF,CAAA,KAAM;AACJ,EAAA,MAAM,YAAA,GAAeC,cAAQ,MAAM;AACjC,IAAA,MAAM,MAAA,GAAS,IAAIC,mBAAA,CAAc;AAAA,MAC/B,MAAA;AAAA,MACA,OAAA;AAAA,MACA,aAAA;AAAA,MACA;AAAA,KACD,CAAA;AAED,IAAA,OAAO;AAAA,MACL,MAAA;AAAA,MACA;AAAA,KACF;AAAA,EACF,GAAG,CAAC,MAAA,EAAQ,SAAS,WAAA,EAAa,aAAA,EAAe,SAAS,CAAC,CAAA;AAE3D,EAAA,sCACG,cAAA,CAAe,QAAA,EAAf,EAAwB,KAAA,EAAO,cAC7B,QAAA,EACH,CAAA;AAEJ;AAEO,IAAM,oBAAoB,MAAM;AACrC,EAAA,MAAM,OAAA,GAAUC,iBAAW,cAAc,CAAA;AACzC,EAAA,IAAI,CAAC,OAAA,EAAS;AACZ,IAAA,MAAM,IAAI,MAAM,yDAAyD,CAAA;AAAA,EAC3E;AACA,EAAA,OAAO,OAAA;AACT,CAAA;;;ACpDO,IAAM,aAAa,MAAM;AAC9B,EAAA,OAAO,iBAAA,EAAkB;AAC3B;ACQO,IAAM,YAAsC,CAAC;AAAA,EAClD,IAAA;AAAA,EACA,IAAA;AAAA,EACA,WAAA;AAAA,EACA,OAAA;AAAA,EACA,YAAA;AAAA,EACA;AACF,CAAA,KAAM;AACJ,EAAA,MAAM,EAAE,MAAA,EAAQ,WAAA,EAAY,GAAI,UAAA,EAAW;AAC3C,EAAA,MAAM,CAAC,cAAA,EAAgB,iBAAiB,CAAA,GAAIC,cAAA,CAAiB,YAAY,IAAI,CAAA;AAC7E,EAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAIA,eAAkB,KAAK,CAAA;AAGzD,EAAA,MAAM,aAAa,IAAA,IAAQ,WAAA;AAG3B,EAAA,MAAM,YAAA,GAAeH,cAAQ,MAAM;AACjC,IAAA,MAAM,GAAA,GAA8B,EAAE,GAAG,OAAA,EAAQ;AACjD,IAAA,IAAI,YAAA,EAAc;AAChB,MAAA,GAAA,CAAI,YAAA,GAAe,YAAA;AAAA,IACrB;AACA,IAAA,OAAO,GAAA;AAAA,EACT,CAAA,EAAG,CAAC,OAAA,EAAS,YAAY,CAAC,CAAA;AAE1B,EAAAI,eAAA,CAAU,MAAM;AACd,IAAA,IAAI,CAAC,UAAA,EAAY;AAEf,MAAA,iBAAA,CAAkB,IAAI,CAAA;AACtB,MAAA;AAAA,IACF;AAEA,IAAA,YAAA,CAAa,IAAI,CAAA;AAEjB,IAAA,MAAA,CACG,SAAA,CAAU,MAAM,UAAA,EAAY,WAAA,EAAa,YAAY,CAAA,CACrD,IAAA,CAAK,CAAC,WAAA,KAAgB;AACrB,MAAA,iBAAA,CAAkB,WAAW,CAAA;AAAA,IAC/B,CAAC,CAAA,CACA,KAAA,CAAM,CAAC,KAAA,KAAU;AAChB,MAAA,OAAA,CAAQ,KAAA,CAAM,sBAAsB,KAAK,CAAA;AAEzC,MAAA,iBAAA,CAAkB,IAAI,CAAA;AAAA,IACxB,CAAC,CAAA,CACA,OAAA,CAAQ,MAAM;AACb,MAAA,YAAA,CAAa,KAAK,CAAA;AAAA,IACpB,CAAC,CAAA;AAAA,EACL,GAAG,CAAC,IAAA,EAAM,YAAY,WAAA,EAAa,YAAA,EAAc,MAAM,CAAC,CAAA;AAGxD,EAAA,IAAI,SAAA,EAAW;AACb,IAAA,uBAAOC,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,QAAA,IAAY,IAAA,EAAK,CAAA;AAAA,EAC7B;AAEA,EAAA,uBAAOD,cAAAA,CAAAC,mBAAA,EAAA,EAAG,QAAA,EAAA,cAAA,EAAe,CAAA;AAC3B","file":"index.cjs","sourcesContent":["import React, { createContext, useContext, useMemo } from 'react';\nimport { BelocalEngine } from '@belocal/js-sdk';\n\nexport type BelocalProviderProps = {\n apiKey: string;\n baseUrl?: string;\n defaultLang?: string;\n batchWindowMs?: number;\n timeoutMs?: number;\n children: React.ReactNode;\n};\n\ntype BelocalContextType = {\n engine: BelocalEngine;\n defaultLang?: string;\n};\n\nconst BelocalContext = createContext<BelocalContextType | null>(null);\n\nexport const BelocalProvider: React.FC<BelocalProviderProps> = ({\n apiKey,\n baseUrl,\n defaultLang,\n batchWindowMs,\n timeoutMs,\n children,\n}) => {\n const contextValue = useMemo(() => {\n const engine = new BelocalEngine({\n apiKey,\n baseUrl,\n batchWindowMs,\n timeoutMs,\n });\n\n return {\n engine,\n defaultLang,\n };\n }, [apiKey, baseUrl, defaultLang, batchWindowMs, timeoutMs]);\n\n return (\n <BelocalContext.Provider value={contextValue}>\n {children}\n </BelocalContext.Provider>\n );\n};\n\nexport const useBelocalContext = () => {\n const context = useContext(BelocalContext);\n if (!context) {\n throw new Error('useBelocalContext must be used within a BelocalProvider');\n }\n return context;\n};\n\n","import { useBelocalContext } from './BelocalProvider';\n\nexport const useBelocal = () => {\n return useBelocalContext();\n};\n\n","import React, { useState, useEffect, useMemo } from 'react';\nimport { useBelocal } from './useBelocal';\n\nexport type TranslateProps = {\n text: string;\n lang?: string;\n source_lang?: string;\n context?: Record<string, string>;\n user_context?: string;\n fallback?: string;\n};\n\nexport const Translate: React.FC<TranslateProps> = ({\n text,\n lang,\n source_lang,\n context,\n user_context,\n fallback,\n}) => {\n const { engine, defaultLang } = useBelocal();\n const [translatedText, setTranslatedText] = useState<string>(fallback || text);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n\n // Выбираем язык: props.lang имеет приоритет над defaultLang\n const targetLang = lang ?? defaultLang;\n\n // Формируем финальный контекст\n const finalContext = useMemo(() => {\n const ctx: Record<string, string> = { ...context };\n if (user_context) {\n ctx.user_context = user_context;\n }\n return ctx;\n }, [context, user_context]);\n\n useEffect(() => {\n if (!targetLang) {\n // Если язык не указан, просто возвращаем исходный текст\n setTranslatedText(text);\n return;\n }\n\n setIsLoading(true);\n \n engine\n .translate(text, targetLang, source_lang, finalContext)\n .then((translation) => {\n setTranslatedText(translation);\n })\n .catch((error) => {\n console.error('Translation error:', error);\n // При ошибке возвращаем исходный текст\n setTranslatedText(text);\n })\n .finally(() => {\n setIsLoading(false);\n });\n }, [text, targetLang, source_lang, finalContext, engine]);\n\n // Пока идёт запрос рендерим fallback или исходный text\n if (isLoading) {\n return <>{fallback || text}</>;\n }\n\n return <>{translatedText}</>;\n};\n\n"]}
1
+ {"version":3,"sources":["../src/BelocalProvider.tsx","../src/useBelocal.ts","../src/Translate.tsx"],"names":["createContext","useMemo","BelocalEngine","useContext","useState","useEffect","jsx","Fragment"],"mappings":"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","file":"index.cjs","sourcesContent":["import React, { createContext, useContext, useMemo } from 'react';\nimport { BelocalEngine } from '@belocal/js-sdk';\n\nexport type BelocalProviderProps = {\n apiKey: string;\n baseUrl?: string;\n defaultLang?: string;\n batchWindowMs?: number;\n timeoutMs?: number;\n children: React.ReactNode;\n};\n\ntype BelocalContextType = {\n engine: BelocalEngine;\n defaultLang?: string;\n};\n\nconst BelocalContext = createContext<BelocalContextType | null>(null);\n\nexport const BelocalProvider: React.FC<BelocalProviderProps> = ({\n apiKey,\n baseUrl,\n defaultLang,\n batchWindowMs,\n timeoutMs,\n children,\n}) => {\n const contextValue = useMemo(() => {\n const engine = new BelocalEngine({\n apiKey,\n baseUrl,\n batchWindowMs,\n timeoutMs,\n });\n\n return {\n engine,\n defaultLang,\n };\n }, [apiKey, baseUrl, defaultLang, batchWindowMs, timeoutMs]);\n\n return (\n <BelocalContext.Provider value={contextValue}>\n {children}\n </BelocalContext.Provider>\n );\n};\n\nexport const useBelocalContext = () => {\n const context = useContext(BelocalContext);\n if (!context) {\n throw new Error('useBelocalContext must be used within a BelocalProvider');\n }\n return context;\n};\n\n","import { useBelocalContext } from './BelocalProvider';\n\nexport const useBelocal = () => {\n return useBelocalContext();\n};\n\n","import React, { useState, useEffect, useMemo } from 'react';\nimport { useBelocal } from './useBelocal';\n\nconst translationCache = new Map<string, string>();\nconst pendingRequests = new Map<string, Promise<string>>();\n\nexport type TranslateProps = {\n text: string;\n lang?: string;\n source_lang?: string;\n context?: Record<string, string>;\n user_context?: string;\n fallback?: string;\n onSuccess?: (translation: string) => void;\n onError?: (error: Error) => void;\n dots?: boolean;\n};\n\nexport const Translate: React.FC<TranslateProps> = ({\n text,\n lang,\n source_lang,\n context,\n user_context,\n fallback,\n onSuccess,\n onError,\n dots,\n}) => {\n const { engine, defaultLang } = useBelocal();\n const [translatedText, setTranslatedText] = useState<string>(fallback || text);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [dotCount, setDotCount] = useState<number>(1);\n\n const targetLang = lang ?? defaultLang;\n\n const finalContext = useMemo(() => {\n const ctx: Record<string, string> = { ...context };\n if (user_context) {\n ctx.user_context = user_context;\n }\n return ctx;\n }, [context, user_context]);\n\n const cacheKey = useMemo(() => {\n return JSON.stringify({\n text,\n targetLang,\n source_lang,\n finalContext,\n });\n }, [text, targetLang, source_lang, finalContext]);\n\n const attachHandlers = (promise: Promise<string>) => {\n setIsLoading(true);\n promise\n .then((translation) => {\n setTranslatedText(translation);\n onSuccess?.(translation);\n })\n .catch((error) => {\n console.error('Translation error:', error);\n setTranslatedText(text);\n onError?.(error instanceof Error ? error : new Error(String(error)));\n })\n .finally(() => {\n setIsLoading(false);\n });\n };\n\n useEffect(() => {\n if (!targetLang) {\n setTranslatedText(text);\n return;\n }\n\n const cachedTranslation = translationCache.get(cacheKey);\n if (cachedTranslation) {\n setTranslatedText(cachedTranslation);\n return;\n }\n\n const pendingRequest = pendingRequests.get(cacheKey);\n if (pendingRequest) {\n attachHandlers(pendingRequest);\n return;\n }\n\n const translationPromise = engine\n .translate(text, targetLang, source_lang, finalContext)\n .then((translation) => {\n translationCache.set(cacheKey, translation);\n pendingRequests.delete(cacheKey);\n return translation;\n })\n .catch((error) => {\n pendingRequests.delete(cacheKey);\n throw error;\n });\n\n pendingRequests.set(cacheKey, translationPromise);\n \n attachHandlers(translationPromise);\n }, [text, targetLang, source_lang, finalContext, engine, cacheKey]);\n\n useEffect(() => {\n if (!isLoading || !dots) {\n if (!isLoading) {\n setDotCount(1);\n }\n return;\n }\n\n const interval = setInterval(() => {\n setDotCount((prev) => (prev >= 3 ? 1 : prev + 1));\n }, 500);\n\n return () => clearInterval(interval);\n }, [isLoading, dots]);\n\n if (isLoading && dots) {\n return <>{'.'.repeat(dotCount)}</>;\n }\n\n if (isLoading) {\n return <>{fallback || text}</>;\n }\n\n return <>{translatedText}</>;\n};\n\n"]}
package/dist/index.d.cts CHANGED
@@ -23,6 +23,9 @@ type TranslateProps = {
23
23
  context?: Record<string, string>;
24
24
  user_context?: string;
25
25
  fallback?: string;
26
+ onSuccess?: (translation: string) => void;
27
+ onError?: (error: Error) => void;
28
+ dots?: boolean;
26
29
  };
27
30
  declare const Translate: React.FC<TranslateProps>;
28
31
 
package/dist/index.d.ts CHANGED
@@ -23,6 +23,9 @@ type TranslateProps = {
23
23
  context?: Record<string, string>;
24
24
  user_context?: string;
25
25
  fallback?: string;
26
+ onSuccess?: (translation: string) => void;
27
+ onError?: (error: Error) => void;
28
+ dots?: boolean;
26
29
  };
27
30
  declare const Translate: React.FC<TranslateProps>;
28
31
 
package/dist/index.js CHANGED
@@ -38,17 +38,23 @@ var useBelocalContext = () => {
38
38
  var useBelocal = () => {
39
39
  return useBelocalContext();
40
40
  };
41
+ var translationCache = /* @__PURE__ */ new Map();
42
+ var pendingRequests = /* @__PURE__ */ new Map();
41
43
  var Translate = ({
42
44
  text,
43
45
  lang,
44
46
  source_lang,
45
47
  context,
46
48
  user_context,
47
- fallback
49
+ fallback,
50
+ onSuccess,
51
+ onError,
52
+ dots
48
53
  }) => {
49
54
  const { engine, defaultLang } = useBelocal();
50
55
  const [translatedText, setTranslatedText] = useState(fallback || text);
51
56
  const [isLoading, setIsLoading] = useState(false);
57
+ const [dotCount, setDotCount] = useState(1);
52
58
  const targetLang = lang ?? defaultLang;
53
59
  const finalContext = useMemo(() => {
54
60
  const ctx = { ...context };
@@ -57,21 +63,68 @@ var Translate = ({
57
63
  }
58
64
  return ctx;
59
65
  }, [context, user_context]);
60
- useEffect(() => {
61
- if (!targetLang) {
62
- setTranslatedText(text);
63
- return;
64
- }
66
+ const cacheKey = useMemo(() => {
67
+ return JSON.stringify({
68
+ text,
69
+ targetLang,
70
+ source_lang,
71
+ finalContext
72
+ });
73
+ }, [text, targetLang, source_lang, finalContext]);
74
+ const attachHandlers = (promise) => {
65
75
  setIsLoading(true);
66
- engine.translate(text, targetLang, source_lang, finalContext).then((translation) => {
76
+ promise.then((translation) => {
67
77
  setTranslatedText(translation);
78
+ onSuccess?.(translation);
68
79
  }).catch((error) => {
69
80
  console.error("Translation error:", error);
70
81
  setTranslatedText(text);
82
+ onError?.(error instanceof Error ? error : new Error(String(error)));
71
83
  }).finally(() => {
72
84
  setIsLoading(false);
73
85
  });
74
- }, [text, targetLang, source_lang, finalContext, engine]);
86
+ };
87
+ useEffect(() => {
88
+ if (!targetLang) {
89
+ setTranslatedText(text);
90
+ return;
91
+ }
92
+ const cachedTranslation = translationCache.get(cacheKey);
93
+ if (cachedTranslation) {
94
+ setTranslatedText(cachedTranslation);
95
+ return;
96
+ }
97
+ const pendingRequest = pendingRequests.get(cacheKey);
98
+ if (pendingRequest) {
99
+ attachHandlers(pendingRequest);
100
+ return;
101
+ }
102
+ const translationPromise = engine.translate(text, targetLang, source_lang, finalContext).then((translation) => {
103
+ translationCache.set(cacheKey, translation);
104
+ pendingRequests.delete(cacheKey);
105
+ return translation;
106
+ }).catch((error) => {
107
+ pendingRequests.delete(cacheKey);
108
+ throw error;
109
+ });
110
+ pendingRequests.set(cacheKey, translationPromise);
111
+ attachHandlers(translationPromise);
112
+ }, [text, targetLang, source_lang, finalContext, engine, cacheKey]);
113
+ useEffect(() => {
114
+ if (!isLoading || !dots) {
115
+ if (!isLoading) {
116
+ setDotCount(1);
117
+ }
118
+ return;
119
+ }
120
+ const interval = setInterval(() => {
121
+ setDotCount((prev) => prev >= 3 ? 1 : prev + 1);
122
+ }, 500);
123
+ return () => clearInterval(interval);
124
+ }, [isLoading, dots]);
125
+ if (isLoading && dots) {
126
+ return /* @__PURE__ */ jsx(Fragment, { children: ".".repeat(dotCount) });
127
+ }
75
128
  if (isLoading) {
76
129
  return /* @__PURE__ */ jsx(Fragment, { children: fallback || text });
77
130
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/BelocalProvider.tsx","../src/useBelocal.ts","../src/Translate.tsx"],"names":["useMemo","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import React, { createContext, useContext, useMemo } from 'react';\nimport { BelocalEngine } from '@belocal/js-sdk';\n\nexport type BelocalProviderProps = {\n apiKey: string;\n baseUrl?: string;\n defaultLang?: string;\n batchWindowMs?: number;\n timeoutMs?: number;\n children: React.ReactNode;\n};\n\ntype BelocalContextType = {\n engine: BelocalEngine;\n defaultLang?: string;\n};\n\nconst BelocalContext = createContext<BelocalContextType | null>(null);\n\nexport const BelocalProvider: React.FC<BelocalProviderProps> = ({\n apiKey,\n baseUrl,\n defaultLang,\n batchWindowMs,\n timeoutMs,\n children,\n}) => {\n const contextValue = useMemo(() => {\n const engine = new BelocalEngine({\n apiKey,\n baseUrl,\n batchWindowMs,\n timeoutMs,\n });\n\n return {\n engine,\n defaultLang,\n };\n }, [apiKey, baseUrl, defaultLang, batchWindowMs, timeoutMs]);\n\n return (\n <BelocalContext.Provider value={contextValue}>\n {children}\n </BelocalContext.Provider>\n );\n};\n\nexport const useBelocalContext = () => {\n const context = useContext(BelocalContext);\n if (!context) {\n throw new Error('useBelocalContext must be used within a BelocalProvider');\n }\n return context;\n};\n\n","import { useBelocalContext } from './BelocalProvider';\n\nexport const useBelocal = () => {\n return useBelocalContext();\n};\n\n","import React, { useState, useEffect, useMemo } from 'react';\nimport { useBelocal } from './useBelocal';\n\nexport type TranslateProps = {\n text: string;\n lang?: string;\n source_lang?: string;\n context?: Record<string, string>;\n user_context?: string;\n fallback?: string;\n};\n\nexport const Translate: React.FC<TranslateProps> = ({\n text,\n lang,\n source_lang,\n context,\n user_context,\n fallback,\n}) => {\n const { engine, defaultLang } = useBelocal();\n const [translatedText, setTranslatedText] = useState<string>(fallback || text);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n\n // Выбираем язык: props.lang имеет приоритет над defaultLang\n const targetLang = lang ?? defaultLang;\n\n // Формируем финальный контекст\n const finalContext = useMemo(() => {\n const ctx: Record<string, string> = { ...context };\n if (user_context) {\n ctx.user_context = user_context;\n }\n return ctx;\n }, [context, user_context]);\n\n useEffect(() => {\n if (!targetLang) {\n // Если язык не указан, просто возвращаем исходный текст\n setTranslatedText(text);\n return;\n }\n\n setIsLoading(true);\n \n engine\n .translate(text, targetLang, source_lang, finalContext)\n .then((translation) => {\n setTranslatedText(translation);\n })\n .catch((error) => {\n console.error('Translation error:', error);\n // При ошибке возвращаем исходный текст\n setTranslatedText(text);\n })\n .finally(() => {\n setIsLoading(false);\n });\n }, [text, targetLang, source_lang, finalContext, engine]);\n\n // Пока идёт запрос рендерим fallback или исходный text\n if (isLoading) {\n return <>{fallback || text}</>;\n }\n\n return <>{translatedText}</>;\n};\n\n"]}
1
+ {"version":3,"sources":["../src/BelocalProvider.tsx","../src/useBelocal.ts","../src/Translate.tsx"],"names":["useMemo","jsx"],"mappings":"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","file":"index.js","sourcesContent":["import React, { createContext, useContext, useMemo } from 'react';\nimport { BelocalEngine } from '@belocal/js-sdk';\n\nexport type BelocalProviderProps = {\n apiKey: string;\n baseUrl?: string;\n defaultLang?: string;\n batchWindowMs?: number;\n timeoutMs?: number;\n children: React.ReactNode;\n};\n\ntype BelocalContextType = {\n engine: BelocalEngine;\n defaultLang?: string;\n};\n\nconst BelocalContext = createContext<BelocalContextType | null>(null);\n\nexport const BelocalProvider: React.FC<BelocalProviderProps> = ({\n apiKey,\n baseUrl,\n defaultLang,\n batchWindowMs,\n timeoutMs,\n children,\n}) => {\n const contextValue = useMemo(() => {\n const engine = new BelocalEngine({\n apiKey,\n baseUrl,\n batchWindowMs,\n timeoutMs,\n });\n\n return {\n engine,\n defaultLang,\n };\n }, [apiKey, baseUrl, defaultLang, batchWindowMs, timeoutMs]);\n\n return (\n <BelocalContext.Provider value={contextValue}>\n {children}\n </BelocalContext.Provider>\n );\n};\n\nexport const useBelocalContext = () => {\n const context = useContext(BelocalContext);\n if (!context) {\n throw new Error('useBelocalContext must be used within a BelocalProvider');\n }\n return context;\n};\n\n","import { useBelocalContext } from './BelocalProvider';\n\nexport const useBelocal = () => {\n return useBelocalContext();\n};\n\n","import React, { useState, useEffect, useMemo } from 'react';\nimport { useBelocal } from './useBelocal';\n\nconst translationCache = new Map<string, string>();\nconst pendingRequests = new Map<string, Promise<string>>();\n\nexport type TranslateProps = {\n text: string;\n lang?: string;\n source_lang?: string;\n context?: Record<string, string>;\n user_context?: string;\n fallback?: string;\n onSuccess?: (translation: string) => void;\n onError?: (error: Error) => void;\n dots?: boolean;\n};\n\nexport const Translate: React.FC<TranslateProps> = ({\n text,\n lang,\n source_lang,\n context,\n user_context,\n fallback,\n onSuccess,\n onError,\n dots,\n}) => {\n const { engine, defaultLang } = useBelocal();\n const [translatedText, setTranslatedText] = useState<string>(fallback || text);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [dotCount, setDotCount] = useState<number>(1);\n\n const targetLang = lang ?? defaultLang;\n\n const finalContext = useMemo(() => {\n const ctx: Record<string, string> = { ...context };\n if (user_context) {\n ctx.user_context = user_context;\n }\n return ctx;\n }, [context, user_context]);\n\n const cacheKey = useMemo(() => {\n return JSON.stringify({\n text,\n targetLang,\n source_lang,\n finalContext,\n });\n }, [text, targetLang, source_lang, finalContext]);\n\n const attachHandlers = (promise: Promise<string>) => {\n setIsLoading(true);\n promise\n .then((translation) => {\n setTranslatedText(translation);\n onSuccess?.(translation);\n })\n .catch((error) => {\n console.error('Translation error:', error);\n setTranslatedText(text);\n onError?.(error instanceof Error ? error : new Error(String(error)));\n })\n .finally(() => {\n setIsLoading(false);\n });\n };\n\n useEffect(() => {\n if (!targetLang) {\n setTranslatedText(text);\n return;\n }\n\n const cachedTranslation = translationCache.get(cacheKey);\n if (cachedTranslation) {\n setTranslatedText(cachedTranslation);\n return;\n }\n\n const pendingRequest = pendingRequests.get(cacheKey);\n if (pendingRequest) {\n attachHandlers(pendingRequest);\n return;\n }\n\n const translationPromise = engine\n .translate(text, targetLang, source_lang, finalContext)\n .then((translation) => {\n translationCache.set(cacheKey, translation);\n pendingRequests.delete(cacheKey);\n return translation;\n })\n .catch((error) => {\n pendingRequests.delete(cacheKey);\n throw error;\n });\n\n pendingRequests.set(cacheKey, translationPromise);\n \n attachHandlers(translationPromise);\n }, [text, targetLang, source_lang, finalContext, engine, cacheKey]);\n\n useEffect(() => {\n if (!isLoading || !dots) {\n if (!isLoading) {\n setDotCount(1);\n }\n return;\n }\n\n const interval = setInterval(() => {\n setDotCount((prev) => (prev >= 3 ? 1 : prev + 1));\n }, 500);\n\n return () => clearInterval(interval);\n }, [isLoading, dots]);\n\n if (isLoading && dots) {\n return <>{'.'.repeat(dotCount)}</>;\n }\n\n if (isLoading) {\n return <>{fallback || text}</>;\n }\n\n return <>{translatedText}</>;\n};\n\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@belocal/react",
3
- "version": "0.5.0",
3
+ "version": "0.5.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.cjs",
6
6
  "module": "./dist/index.js",