@agentscope-ai/i18n 0.1.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.
@@ -0,0 +1,572 @@
1
+ const { callDashScope } = require('../services/dashscope');
2
+ const { callDashScopeMTBatch } = require('../services/dashscopeMT');
3
+
4
+ // 生成带长度限制的key
5
+ const generateKeyWithLengthLimit = (translatedText, originalKey) => {
6
+ const MAX_KEY_LENGTH = 256;
7
+
8
+ // 将翻译结果转换为适合的key格式
9
+ let cleanKey = translatedText
10
+ .toLowerCase()
11
+ .replace(/[^a-z0-9\s]/g, '') // 移除特殊字符
12
+ .trim()
13
+ .split(/\s+/)
14
+ .filter((word) => word.length > 0)
15
+ .map((word, index) => (index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)))
16
+ .join('');
17
+
18
+ // 替换原key的下标部分,保留前缀和文件路径
19
+ const keyParts = originalKey.split('.');
20
+ let newKey;
21
+
22
+ if (keyParts.length > 0) {
23
+ // 替换最后一部分(下标)为翻译后的key
24
+ keyParts[keyParts.length - 1] = cleanKey;
25
+ newKey = keyParts.join('.');
26
+ } else {
27
+ // 如果key格式不符合预期,直接使用翻译结果
28
+ newKey = cleanKey;
29
+ }
30
+
31
+ // 检查长度是否超过限制
32
+ if (newKey.length <= MAX_KEY_LENGTH) {
33
+ return { newKey, isShortened: false };
34
+ }
35
+
36
+ // 长度超过限制,开始缩短处理
37
+ console.log(` [警告] key长度超过${MAX_KEY_LENGTH}字符 (${newKey.length}字符),开始缩短处理`);
38
+
39
+ // 方法1:cleanKey转化时只取翻译文本的后七个单词
40
+ const words = translatedText
41
+ .toLowerCase()
42
+ .replace(/[^a-z0-9\s]/g, '')
43
+ .trim()
44
+ .split(/\s+/)
45
+ .filter((word) => word.length > 0);
46
+
47
+ const lastSevenWords = words.slice(-7); // 取后七个单词
48
+ const shortenedCleanKey = lastSevenWords
49
+ .map((word, index) => (index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)))
50
+ .join('');
51
+
52
+ // 重新构建key
53
+ const originalKeyParts = originalKey.split('.');
54
+ if (originalKeyParts.length > 0) {
55
+ originalKeyParts[originalKeyParts.length - 1] = shortenedCleanKey;
56
+ newKey = originalKeyParts.join('.');
57
+ } else {
58
+ newKey = shortenedCleanKey;
59
+ }
60
+
61
+ // 如果还是超过长度,使用方法2:缩短originalKey路径
62
+ if (newKey.length > MAX_KEY_LENGTH) {
63
+ console.log(` [警告] 方法1后仍超过长度限制,使用方法2缩短路径`);
64
+
65
+ const parts = originalKey.split('.');
66
+ if (parts.length > 7) {
67
+ // 只保留第一个索引和最后六个索引
68
+ const shortenedParts = [parts[0], ...parts.slice(-6)];
69
+ shortenedParts[shortenedParts.length - 1] = shortenedCleanKey;
70
+ newKey = shortenedParts.join('.');
71
+ } else {
72
+ // 如果原key部分数量不足7个,保持原有逻辑
73
+ parts[parts.length - 1] = shortenedCleanKey;
74
+ newKey = parts.join('.');
75
+ }
76
+ }
77
+
78
+ // 如果仍然超过长度,进行最终截断
79
+ if (newKey.length > MAX_KEY_LENGTH) {
80
+ console.log(` [警告] 缩短后仍超过长度限制,进行截断处理`);
81
+ newKey = newKey.substring(0, MAX_KEY_LENGTH);
82
+
83
+ // 确保截断后不以'.'结尾
84
+ if (newKey.endsWith('.')) {
85
+ newKey = newKey.substring(0, newKey.length - 1);
86
+ }
87
+ }
88
+
89
+ return { newKey, isShortened: true };
90
+ };
91
+ const fs = require('fs-extra');
92
+ const path = require('path');
93
+
94
+ // 日志工具函数
95
+ const logTranslationError = (error, context) => {
96
+ const logDir = path.join(process.cwd(), 'logs');
97
+ const logFile = path.join(logDir, 'i18n-translation-errors.log');
98
+
99
+ // 确保日志目录存在
100
+ fs.ensureDirSync(logDir);
101
+
102
+ const timestamp = new Date().toISOString();
103
+ const logEntry = {
104
+ timestamp,
105
+ error: {
106
+ message: error.message,
107
+ stack: error.stack,
108
+ ...(error.response && {
109
+ response: {
110
+ status: error.response.status,
111
+ statusText: error.response.statusText,
112
+ data: error.response.data,
113
+ headers: error.response.headers,
114
+ },
115
+ }),
116
+ },
117
+ context,
118
+ };
119
+
120
+ // 追加到日志文件
121
+ const logLine = JSON.stringify(logEntry, null, 2) + '\n' + '-'.repeat(80) + '\n';
122
+ fs.appendFileSync(logFile, logLine);
123
+
124
+ console.error(` [日志] 翻译错误已记录到: ${logFile}`);
125
+ };
126
+
127
+ // 统一的批处理翻译函数,支持重试机制
128
+ const batchTranslateWithRetry = async (content, tokenName, appId, apiKey, retryCount = 3) => {
129
+ for (let i = 0; i < retryCount; i++) {
130
+ try {
131
+ const translatedText = await callDashScope(
132
+ JSON.stringify(content, null, 2),
133
+ tokenName,
134
+ appId,
135
+ apiKey,
136
+ );
137
+ const translatedJson = extractJsonContent(translatedText);
138
+ if (translatedJson) {
139
+ return translatedJson;
140
+ }
141
+ throw new Error('翻译结果解析失败');
142
+ } catch (error) {
143
+ // 记录翻译失败信息
144
+ const context = {
145
+ attempt: i + 1,
146
+ totalAttempts: retryCount,
147
+ tokenName,
148
+ appId,
149
+ contentSize: JSON.stringify(content).length,
150
+ timestamp: new Date().toISOString(),
151
+ };
152
+
153
+ logTranslationError(error, context);
154
+
155
+ if (i < retryCount - 1) {
156
+ console.log(` [重试] 第${i + 1}次翻译失败,10秒后重试...`);
157
+ console.error(` [错误] ${error.message}`);
158
+ await new Promise((resolve) => setTimeout(resolve, 10000));
159
+ } else {
160
+ throw error;
161
+ }
162
+ }
163
+ }
164
+ };
165
+
166
+ // 分批处理key翻译,每批100个
167
+ const batchKeyTranslate = async (i18n, dashScope) => {
168
+ const entries = Object.entries(i18n);
169
+ const batchSize = dashScope.batchSize || 100;
170
+ const batches = [];
171
+
172
+ // 将内容分成每批100个
173
+ for (let i = 0; i < entries.length; i += batchSize) {
174
+ batches.push(entries.slice(i, i + batchSize));
175
+ }
176
+
177
+ console.log(` [进度] 共${batches.length}批需要翻译`);
178
+ const keyMap = {};
179
+ const zhCN = {};
180
+
181
+ // 逐批处理
182
+ for (let i = 0; i < batches.length; i++) {
183
+ const batch = batches[i];
184
+ const batchContent = {
185
+ type: 'i18n',
186
+ content: Object.fromEntries(batch),
187
+ };
188
+
189
+ console.log(` [进度] 正在翻译第${i + 1}/${batches.length}批...`);
190
+ const translatedJson = await batchTranslateWithRetry(
191
+ batchContent,
192
+ 'i18n',
193
+ dashScope.keyTranslateAppId,
194
+ dashScope.apiKey,
195
+ );
196
+
197
+ if (translatedJson.keyMap && translatedJson['zh-cn']) {
198
+ Object.assign(keyMap, translatedJson.keyMap);
199
+ Object.assign(zhCN, translatedJson['zh-cn']);
200
+ }
201
+ // 每批之间暂停1秒,避免请求过快
202
+ if (i < batches.length - 1) {
203
+ await new Promise((resolve) => setTimeout(resolve, 1000));
204
+ }
205
+ }
206
+
207
+ return { keyMap, zhCN };
208
+ };
209
+
210
+ // 分批处理value翻译,每批100个,支持多语言
211
+ const batchTranslate = async (i18n, dashScope) => {
212
+ const entries = Object.entries(i18n);
213
+ const batchSize = dashScope.batchSize || 100;
214
+ const batches = [];
215
+
216
+ // 将内容分成每批100个
217
+ for (let i = 0; i < entries.length; i += batchSize) {
218
+ batches.push(entries.slice(i, i + batchSize));
219
+ }
220
+
221
+ console.log(` [进度] 共${batches.length}批需要翻译`);
222
+
223
+ // 支持多语言翻译,为每种语言创建翻译结果对象
224
+ const translatedResults = {};
225
+
226
+ // 检查valueTranslateAppId配置
227
+ if (typeof dashScope.valueTranslateAppId === 'string') {
228
+ // 兼容旧版本配置,只翻译成英文
229
+ console.log(` [进度] 检测到单语言配置,翻译为英文...`);
230
+ const translatedI18n = {};
231
+
232
+ for (let i = 0; i < batches.length; i++) {
233
+ const batch = batches[i];
234
+ const batchContent = Object.fromEntries(batch);
235
+
236
+ console.log(` [进度] 正在翻译第${i + 1}/${batches.length}批...`);
237
+ const translatedJson = await batchTranslateWithRetry(
238
+ batchContent,
239
+ 'i18n',
240
+ dashScope.valueTranslateAppId,
241
+ dashScope.apiKey,
242
+ );
243
+
244
+ if (translatedJson) {
245
+ Object.assign(translatedI18n, translatedJson);
246
+ }
247
+
248
+ // 每批之间暂停1秒,避免请求过快
249
+ if (i < batches.length - 1) {
250
+ await new Promise((resolve) => setTimeout(resolve, 1000));
251
+ }
252
+ }
253
+
254
+ translatedResults['en-us'] = translatedI18n;
255
+ } else if (typeof dashScope.valueTranslateAppId === 'object') {
256
+ // 新版本配置,支持多语言翻译
257
+ const languages = Object.keys(dashScope.valueTranslateAppId);
258
+ console.log(` [进度] 检测到多语言配置,将翻译为: ${languages.join(', ')}`);
259
+
260
+ // 为每种语言进行翻译
261
+ for (const language of languages) {
262
+ const appId = dashScope.valueTranslateAppId[language];
263
+ console.log(` [进度] 开始翻译为 ${language}...`);
264
+
265
+ const translatedI18n = {};
266
+
267
+ for (let i = 0; i < batches.length; i++) {
268
+ const batch = batches[i];
269
+ const batchContent = Object.fromEntries(batch);
270
+
271
+ console.log(` [进度] ${language}: 正在翻译第${i + 1}/${batches.length}批...`);
272
+ const translatedJson = await batchTranslateWithRetry(
273
+ batchContent,
274
+ 'i18n',
275
+ appId,
276
+ dashScope.apiKey,
277
+ );
278
+
279
+ if (translatedJson) {
280
+ Object.assign(translatedI18n, translatedJson);
281
+ }
282
+
283
+ // 每批之间暂停1秒,避免请求过快
284
+ if (i < batches.length - 1) {
285
+ await new Promise((resolve) => setTimeout(resolve, 1000));
286
+ }
287
+ }
288
+
289
+ translatedResults[language] = translatedI18n;
290
+ }
291
+ } else {
292
+ throw new Error('valueTranslateAppId 配置无效,必须是字符串或对象');
293
+ }
294
+
295
+ return translatedResults;
296
+ };
297
+
298
+ const extractJsonContent = (text, retryCount = 3) => {
299
+ if (typeof text !== 'string') {
300
+ const error = new Error('传入的文本不是字符串类型');
301
+ const context = {
302
+ type: 'type_error',
303
+ actualType: typeof text,
304
+ content: text,
305
+ timestamp: new Date().toISOString(),
306
+ };
307
+ logTranslationError(error, context);
308
+ console.error(' # 错误:传入的文本不是字符串类型');
309
+ console.error(' # 实际类型:', typeof text);
310
+ console.error(' # 内容:', text);
311
+ return null;
312
+ }
313
+
314
+ // 清理文本中可能导致解析失败的字符
315
+ const cleanText = text
316
+ .replace(/[\u0000-\u001F\u007F-\u009F]/g, '') // 移除控制字符
317
+ .replace(/\n\s*\n/g, '\n') // 移除多余的空行
318
+ .trim(); // 移除首尾空白
319
+
320
+ try {
321
+ // 首先尝试直接解析为JSON
322
+ return JSON.parse(cleanText);
323
+ } catch (e) {
324
+ try {
325
+ // 如果直接解析失败,尝试提取```json```中的内容
326
+ const regex = /```json\n([\s\S]*?)```/;
327
+ const match = cleanText.match(regex);
328
+ if (match && match[1]) {
329
+ return JSON.parse(match[1].trim());
330
+ }
331
+ } catch (innerError) {
332
+ // 记录JSON解析失败
333
+ const error = new Error(`JSON解析失败: ${innerError.message}`);
334
+ const context = {
335
+ type: 'json_parse_error',
336
+ originalText: text,
337
+ cleanText: cleanText,
338
+ innerError: innerError.message,
339
+ timestamp: new Date().toISOString(),
340
+ };
341
+ logTranslationError(error, context);
342
+
343
+ console.error(' # JSON解析错误,准备重试翻译');
344
+ console.error(' # 原始文本:', text);
345
+ console.error(' # 错误信息:', innerError.message);
346
+ }
347
+ return null;
348
+ }
349
+ };
350
+
351
+ // 单条翻译key(使用MT方式)
352
+ const singleKeyTranslate = async (i18n, dashScope) => {
353
+ console.log(` [进度] 使用MT方式翻译key,共${Object.keys(i18n).length}条`);
354
+
355
+ const keyMap = {};
356
+ const zhCN = {};
357
+ const entries = Object.entries(i18n);
358
+ const concurrency = dashScope.mtConcurrency || 1; // 默认并发数为1
359
+ const delay = dashScope.mtDelay || 100; // 默认延迟100ms
360
+ const mtBatchSize = dashScope.mtBatchSize || 5; // 默认每次调用翻译5条
361
+
362
+ console.log(
363
+ ` [配置] 并发数: ${concurrency}, 请求间隔: ${delay}ms, 每次调用翻译条数: ${mtBatchSize}`,
364
+ );
365
+
366
+ // 批量翻译key
367
+ const translateKeyBatch = async (batch, startIndex) => {
368
+ try {
369
+ const prompts = batch.map(([, chineseValue]) => chineseValue);
370
+ console.log(
371
+ ` [进度] 翻译key批次 ${startIndex + 1}-${startIndex + batch.length}/${entries.length}`,
372
+ );
373
+
374
+ // 调用批量MT翻译
375
+ const translatedTexts = await callDashScopeMTBatch(
376
+ prompts,
377
+ 'en-us',
378
+ dashScope.apiKey,
379
+ 3,
380
+ 'key',
381
+ );
382
+
383
+ const results = [];
384
+ for (let i = 0; i < batch.length; i++) {
385
+ const [originalKey, chineseValue] = batch[i];
386
+ const translatedText = translatedTexts[i];
387
+
388
+ if (translatedText && translatedText.trim()) {
389
+ // 生成新key,并处理长度限制
390
+ const { newKey, isShortened } = generateKeyWithLengthLimit(translatedText, originalKey);
391
+
392
+ if (isShortened) {
393
+ console.log(` [长度限制] ${originalKey} -> ${newKey} (已缩短)`);
394
+ } else {
395
+ console.log(` [完成] ${originalKey} -> ${newKey}`);
396
+ }
397
+ results.push({ originalKey, newKey, chineseValue });
398
+ } else {
399
+ console.warn(` [警告] key翻译失败,保持原key: ${originalKey}`);
400
+ results.push({ originalKey, newKey: originalKey, chineseValue });
401
+ }
402
+ }
403
+
404
+ return results;
405
+ } catch (error) {
406
+ console.error(` [错误] 翻译key批次失败:`, error.message);
407
+ // 翻译失败时保持原key
408
+ return batch.map(([originalKey, chineseValue]) => ({
409
+ originalKey,
410
+ newKey: originalKey,
411
+ chineseValue,
412
+ }));
413
+ }
414
+ };
415
+
416
+ // 分组处理:先按mtBatchSize分组,再按并发数并行处理
417
+ for (let i = 0; i < entries.length; i += mtBatchSize * concurrency) {
418
+ const superBatch = entries.slice(i, i + mtBatchSize * concurrency);
419
+ const batches = [];
420
+
421
+ // 将superBatch分成多个mtBatchSize大小的批次
422
+ for (let j = 0; j < superBatch.length; j += mtBatchSize) {
423
+ batches.push(superBatch.slice(j, j + mtBatchSize));
424
+ }
425
+
426
+ // 并发处理这些批次
427
+ const promises = batches.map((batch, batchIndex) =>
428
+ translateKeyBatch(batch, i + batchIndex * mtBatchSize),
429
+ );
430
+
431
+ const batchResults = await Promise.all(promises);
432
+
433
+ // 收集结果
434
+ batchResults.forEach((results) => {
435
+ results.forEach(({ originalKey, newKey, chineseValue }) => {
436
+ keyMap[originalKey] = newKey;
437
+ zhCN[newKey] = chineseValue;
438
+ });
439
+ });
440
+
441
+ // 避免请求过快,超级批次间暂停
442
+ if (i + mtBatchSize * concurrency < entries.length) {
443
+ await new Promise((resolve) => setTimeout(resolve, delay));
444
+ }
445
+ }
446
+
447
+ return { keyMap, zhCN };
448
+ };
449
+
450
+ // 单条翻译value(使用MT方式)
451
+ const singleTranslate = async (i18n, dashScope) => {
452
+ console.log(` [进度] 使用MT方式翻译value,共${Object.keys(i18n).length}条`);
453
+
454
+ // 支持多语言翻译,为每种语言创建翻译结果对象
455
+ const translatedResults = {};
456
+ const concurrency = dashScope.mtConcurrency || 1; // 默认并发数为1
457
+ const delay = dashScope.mtDelay || 100; // 默认延迟100ms
458
+ const mtBatchSize = dashScope.mtBatchSize || 5; // 默认每次调用翻译5条
459
+
460
+ console.log(
461
+ ` [配置] 并发数: ${concurrency}, 请求间隔: ${delay}ms, 每次调用翻译条数: ${mtBatchSize}`,
462
+ );
463
+
464
+ // 检查valueTranslateAppId配置,如果没有配置就使用默认的英文翻译
465
+ let languages = ['en-us']; // 默认翻译为英文
466
+
467
+ if (dashScope.valueTranslateAppId) {
468
+ if (typeof dashScope.valueTranslateAppId === 'string') {
469
+ // 兼容旧版本配置,只翻译成英文
470
+ console.log(` [进度] 检测到单语言配置,使用MT翻译为英文...`);
471
+ languages = ['en-us'];
472
+ } else if (typeof dashScope.valueTranslateAppId === 'object') {
473
+ // 新版本配置,支持多语言翻译
474
+ languages = Object.keys(dashScope.valueTranslateAppId);
475
+ console.log(` [进度] 检测到多语言配置,使用MT翻译为: ${languages.join(', ')}`);
476
+ }
477
+ } else {
478
+ console.log(` [进度] 未配置valueTranslateAppId,默认使用MT翻译为英文...`);
479
+ }
480
+
481
+ // 批量翻译value函数
482
+ const translateValueBatch = async (batch, language, startIndex, total) => {
483
+ try {
484
+ const prompts = batch.map(([, chineseValue]) => chineseValue);
485
+ console.log(
486
+ ` [进度] ${language}: 翻译批次 ${startIndex + 1}-${startIndex + batch.length}/${total}`,
487
+ );
488
+
489
+ // 调用批量MT翻译
490
+ const translatedValues = await callDashScopeMTBatch(
491
+ prompts,
492
+ language,
493
+ dashScope.apiKey,
494
+ 3,
495
+ 'value',
496
+ );
497
+
498
+ const results = [];
499
+ for (let i = 0; i < batch.length; i++) {
500
+ const [key, chineseValue] = batch[i];
501
+ const translatedValue = translatedValues[i];
502
+
503
+ if (translatedValue && translatedValue.trim()) {
504
+ console.log(` [完成] ${chineseValue} -> ${translatedValue.trim()}`);
505
+ results.push({ key, value: translatedValue.trim() });
506
+ } else {
507
+ console.warn(` [警告] value翻译失败,保持原文: ${chineseValue}`);
508
+ results.push({ key, value: chineseValue });
509
+ }
510
+ }
511
+
512
+ return results;
513
+ } catch (error) {
514
+ console.error(` [错误] 翻译value批次失败:`, error.message);
515
+ // 翻译失败时保持原文
516
+ return batch.map(([key, chineseValue]) => ({
517
+ key,
518
+ value: chineseValue,
519
+ }));
520
+ }
521
+ };
522
+
523
+ // 为每种语言进行翻译
524
+ for (const language of languages) {
525
+ console.log(` [进度] 开始MT翻译为 ${language}...`);
526
+ const translatedI18n = {};
527
+ const entries = Object.entries(i18n);
528
+
529
+ // 分组处理:先按mtBatchSize分组,再按并发数并行处理
530
+ for (let i = 0; i < entries.length; i += mtBatchSize * concurrency) {
531
+ const superBatch = entries.slice(i, i + mtBatchSize * concurrency);
532
+ const batches = [];
533
+
534
+ // 将superBatch分成多个mtBatchSize大小的批次
535
+ for (let j = 0; j < superBatch.length; j += mtBatchSize) {
536
+ batches.push(superBatch.slice(j, j + mtBatchSize));
537
+ }
538
+
539
+ // 并发处理这些批次
540
+ const promises = batches.map((batch, batchIndex) =>
541
+ translateValueBatch(batch, language, i + batchIndex * mtBatchSize, entries.length),
542
+ );
543
+
544
+ const batchResults = await Promise.all(promises);
545
+
546
+ // 收集结果
547
+ batchResults.forEach((results) => {
548
+ results.forEach(({ key, value }) => {
549
+ translatedI18n[key] = value;
550
+ });
551
+ });
552
+
553
+ // 避免请求过快,超级批次间暂停
554
+ if (i + mtBatchSize * concurrency < entries.length) {
555
+ await new Promise((resolve) => setTimeout(resolve, delay));
556
+ }
557
+ }
558
+
559
+ translatedResults[language] = translatedI18n;
560
+ }
561
+
562
+ return translatedResults;
563
+ };
564
+
565
+ module.exports = {
566
+ batchTranslateWithRetry,
567
+ batchKeyTranslate,
568
+ batchTranslate,
569
+ extractJsonContent,
570
+ singleKeyTranslate,
571
+ singleTranslate,
572
+ };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@agentscope-ai/i18n",
3
+ "version": "0.1.2",
4
+ "description": "A tool for translating Chinese content in frontend code repositories",
5
+ "keywords": [
6
+ "i18n",
7
+ "translation",
8
+ "chinese",
9
+ "frontend"
10
+ ],
11
+ "bin": {
12
+ "i18n": "./lib/cli.js"
13
+ },
14
+ "files": [
15
+ "lib/*",
16
+ "README.md"
17
+ ],
18
+ "scripts": {
19
+ "lint": "eslint .",
20
+ "format": "prettier --write \"lib/**/*.{ts,tsx}\""
21
+ },
22
+ "lint-staged": {
23
+ "**/*.{js,jsx,html,css,json}": [
24
+ "npx prettier --write"
25
+ ]
26
+ },
27
+ "author": [],
28
+ "license": "ISC",
29
+ "dependencies": {
30
+ "@babel/generator": "^7.23.6",
31
+ "@babel/parser": "^7.23.6",
32
+ "@babel/traverse": "^7.23.6",
33
+ "@babel/types": "^7.23.6",
34
+ "axios": "^1.6.2",
35
+ "commander": "^11.1.0",
36
+ "dotenv": "^16.3.1",
37
+ "find": "^0.3.0",
38
+ "fs-extra": "^11.1.1",
39
+ "inquirer": "^12.9.3",
40
+ "lodash-es": "^4.17.21",
41
+ "node-fetch": "^3.3.2",
42
+ "xlsx": "^0.18.5"
43
+ },
44
+ "devDependencies": {
45
+ "@babel/core": "^7.23.6",
46
+ "@babel/preset-env": "^7.23.6",
47
+ "@babel/preset-react": "^7.23.6",
48
+ "@babel/preset-typescript": "^7.23.6",
49
+ "@types/jest": "^29.5.10",
50
+ "@types/node": "^20.10.4",
51
+ "@types/react": "^19.1.6",
52
+ "babel-plugin-module-resolver": "^5.0.0",
53
+ "eslint": "^8.55.0",
54
+ "jest": "^29.7.0",
55
+ "prettier": "^3.1.0",
56
+ "react": "^19.1.0",
57
+ "typescript": "^5.3.3"
58
+ },
59
+ "engines": {
60
+ "node": ">=14.0.0"
61
+ },
62
+ "publishConfig": {
63
+ "registry": "https://registry.npmjs.org/",
64
+ "access": "public"
65
+ }
66
+ }