locai 2.3.0 → 2.5.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.
@@ -0,0 +1,164 @@
1
+ import { Project, Node } from "ts-morph";
2
+ import { log } from "../utils/logger.js";
3
+ import path from "path";
4
+ export class SourceCodeAnalyzer {
5
+ project;
6
+ keyUsageMap = new Map();
7
+ rootDir;
8
+ initPromise = null;
9
+ constructor(rootDir = process.cwd()) {
10
+ this.rootDir = rootDir;
11
+ // Initialize with default settings, but don't add files yet for performance
12
+ this.project = new Project({
13
+ skipAddingFilesFromTsConfig: true,
14
+ compilerOptions: {
15
+ allowJs: true,
16
+ jsx: 1, // Preserve
17
+ },
18
+ });
19
+ }
20
+ initialize(globPatterns = [
21
+ "src/**/*.{ts,tsx}",
22
+ "app/**/*.{ts,tsx}",
23
+ "components/**/*.{ts,tsx}",
24
+ ]) {
25
+ if (this.initPromise)
26
+ return this.initPromise;
27
+ this.initPromise = (async () => {
28
+ await new Promise((resolve) => setTimeout(resolve, 0)); // Force async execution for race condition safety
29
+ try {
30
+ // Add source files
31
+ log(`Analyzing source code in ${this.rootDir}...`);
32
+ // Exclude node_modules explicitly just in case
33
+ this.project.addSourceFilesAtPaths(globPatterns);
34
+ const sourceFiles = this.project.getSourceFiles();
35
+ log(`Found ${sourceFiles.length} source files for context analysis.`);
36
+ for (const sourceFile of sourceFiles) {
37
+ this.processFile(sourceFile);
38
+ }
39
+ log(`Source analysis complete. Context extracted for ${this.keyUsageMap.size} keys.`);
40
+ }
41
+ catch (error) {
42
+ console.warn(`Error during source analysis: ${error.message}`);
43
+ // Don't throw to prevent blocking the whole app, just log
44
+ }
45
+ })();
46
+ return this.initPromise;
47
+ }
48
+ async ensureInitialized() {
49
+ if (this.initPromise) {
50
+ await this.initPromise;
51
+ }
52
+ }
53
+ processFile(sourceFile) {
54
+ sourceFile.forEachDescendant((node) => {
55
+ if (Node.isCallExpression(node)) {
56
+ this.processCallExpression(node, sourceFile.getFilePath());
57
+ }
58
+ });
59
+ }
60
+ processCallExpression(node, filePath) {
61
+ // Match t('key') or useTranslations().t('key') pattern
62
+ // This is a heuristic match
63
+ const expression = node.getExpression();
64
+ // Check if expression is accessible
65
+ if (!expression)
66
+ return;
67
+ const expressionText = expression.getText();
68
+ // Simple check for 't' function calls
69
+ if (expressionText.endsWith(".t") || expressionText === "t") {
70
+ const args = node.getArguments();
71
+ if (args.length > 0 && Node.isStringLiteral(args[0])) {
72
+ const key = args[0].getLiteralText();
73
+ this.extractContext(key, node, filePath);
74
+ }
75
+ }
76
+ }
77
+ extractContext(key, node, fullFilePath) {
78
+ const relativePath = path.relative(this.rootDir, fullFilePath);
79
+ const line = node.getStartLineNumber();
80
+ // Extract component name
81
+ let componentName = undefined;
82
+ // Use getAncestors() to find the parent safely
83
+ const componentNode = node
84
+ .getAncestors()
85
+ .find((n) => Node.isFunctionDeclaration(n) ||
86
+ Node.isClassDeclaration(n) ||
87
+ Node.isVariableDeclaration(n));
88
+ if (componentNode) {
89
+ if (Node.isFunctionDeclaration(componentNode) ||
90
+ Node.isClassDeclaration(componentNode)) {
91
+ componentName = componentNode.getName();
92
+ }
93
+ else if (Node.isVariableDeclaration(componentNode)) {
94
+ componentName = componentNode.getName();
95
+ }
96
+ }
97
+ // Extract comments
98
+ // Look for leading comments on the statement
99
+ const statement = node
100
+ .getAncestors()
101
+ .find((n) => Node.isExpressionStatement(n) ||
102
+ Node.isVariableStatement(n) ||
103
+ Node.isJsxElement(n) ||
104
+ Node.isJsxExpression(n));
105
+ const comments = [];
106
+ const targetNode = statement || node;
107
+ const ranges = targetNode.getLeadingCommentRanges();
108
+ for (const range of ranges) {
109
+ comments.push(range
110
+ .getText()
111
+ .replace(/^\/\/\s*/, "")
112
+ .replace(/^\/\*\s*/, "")
113
+ .replace(/\s*\*\/$/, ""));
114
+ }
115
+ // Get props (simple heuristic for JSX)
116
+ const props = {};
117
+ const jsxElement = node
118
+ .getAncestors()
119
+ .find((n) => Node.isJsxOpeningElement(n) || Node.isJsxSelfClosingElement(n));
120
+ if (jsxElement &&
121
+ (Node.isJsxOpeningElement(jsxElement) || Node.isJsxSelfClosingElement(jsxElement))) {
122
+ jsxElement.getAttributes().forEach((attr) => {
123
+ if (Node.isJsxAttribute(attr)) {
124
+ // Safe name extraction
125
+ const nameNode = attr.getNameNode();
126
+ const name = nameNode ? nameNode.getText() : "unknown";
127
+ const initializer = attr.getInitializer();
128
+ let value = initializer ? initializer.getText() : "true";
129
+ // Strip quotes if string literal
130
+ if ((value.startsWith('"') && value.endsWith('"')) ||
131
+ (value.startsWith("'") && value.endsWith("'"))) {
132
+ value = value.substring(1, value.length - 1);
133
+ }
134
+ props[name] = value;
135
+ }
136
+ });
137
+ }
138
+ // Code Snippet (surrounding lines)
139
+ // Check for node.getParent() to avoid error if node is root (unlikely for call expression)
140
+ const parent = node.getParent();
141
+ const snippet = parent ? parent.getText() : node.getText();
142
+ const context = {
143
+ filePath: relativePath,
144
+ line,
145
+ component: componentName,
146
+ usageSnippet: snippet.length > 200 ? snippet.substring(0, 200) + "..." : snippet,
147
+ comments: comments.length > 0 ? comments : undefined,
148
+ props: Object.keys(props).length > 0 ? props : undefined,
149
+ };
150
+ if (!this.keyUsageMap.has(key)) {
151
+ this.keyUsageMap.set(key, []);
152
+ }
153
+ this.keyUsageMap.get(key)?.push(context);
154
+ }
155
+ getContext(key) {
156
+ // Return duplicate functionality: prefer the first usage found or merge?
157
+ // For now return the first usage that has comments, or just the first usage.
158
+ const usages = this.keyUsageMap.get(key);
159
+ if (!usages || usages.length === 0)
160
+ return undefined;
161
+ // Prioritize usage with comments
162
+ return usages.find((u) => u.comments && u.comments.length > 0) || usages[0];
163
+ }
164
+ }
@@ -1,60 +1,12 @@
1
1
  import Orchestrator from "../core/orchestrator.js";
2
- import { OrchestratorOptions } from "../core/orchestrator.js";
3
2
  import StateManager from "../utils/state-manager.js";
4
- export interface TranslationOptions extends OrchestratorOptions {
5
- source: string;
6
- targets?: string[];
7
- localesDir?: string;
8
- debug?: boolean;
9
- forceUpdate?: boolean;
10
- syncOptions?: {
11
- enabled?: boolean;
12
- removeDeletedKeys?: boolean;
13
- };
14
- concurrencyLimit?: number;
15
- saveReviewQueue?: boolean;
16
- minConfidence?: number;
17
- fileExtensions?: string[];
18
- }
19
- export interface GlobalStats {
20
- total: number;
21
- success: number;
22
- failed: number;
23
- skipped: number;
24
- languages: Record<string, any>;
25
- byCategory: Record<string, number>;
26
- details: Record<string, {
27
- totalConfidence: number;
28
- samples: number;
29
- }>;
30
- error?: {
31
- message: string;
32
- time: string;
33
- stack?: string;
34
- };
35
- orchestrators: any[];
36
- endTime?: string;
37
- totalDuration?: number;
38
- totalTime: number;
39
- startTime: string;
40
- }
41
- interface LanguageProcessResult {
42
- status: any;
43
- savedMessage?: string;
44
- error?: string;
45
- }
3
+ import { TranslationOptions, GlobalStats, LanguageProcessResult } from "../types/index.js";
46
4
  /**
47
5
  * Service for handling file translation operations.
48
6
  * Orchestrates the translation process including state management,
49
7
  * verification, and statistics collection.
50
8
  */
51
9
  declare class TranslationService {
52
- /**
53
- * Check if text only contains placeholders and whitespace.
54
- * @param {string} text - Text to check.
55
- * @returns {boolean} - True if text only contains placeholders.
56
- */
57
- isPlaceholderOnlyText(text: string): boolean;
58
10
  /**
59
11
  * Main translator function to process source file and create translations.
60
12
  * @param {string} file - Path to source file.
@@ -8,26 +8,14 @@ import InputValidator from "../utils/input-validator.js";
8
8
  import gracefulShutdown from "../utils/graceful-shutdown.js";
9
9
  import uiManager from "../utils/ui-manager.js";
10
10
  import statisticsManager from "../utils/statistics-manager.js";
11
+ import { LanguageProcessor } from "./language-processor.js";
12
+ import { SourceCodeAnalyzer } from "./source-analyzer.js";
11
13
  /**
12
14
  * Service for handling file translation operations.
13
15
  * Orchestrates the translation process including state management,
14
16
  * verification, and statistics collection.
15
17
  */
16
18
  class TranslationService {
17
- /**
18
- * Check if text only contains placeholders and whitespace.
19
- * @param {string} text - Text to check.
20
- * @returns {boolean} - True if text only contains placeholders.
21
- */
22
- isPlaceholderOnlyText(text) {
23
- if (!text || typeof text !== "string") {
24
- return false;
25
- }
26
- // Remove all placeholders from the text
27
- const placeholderRegex = /\{[^}]+\}|\$\{[^}]+\}|%[sd]/g;
28
- const textWithoutPlaceholders = text.replace(placeholderRegex, "").trim();
29
- return textWithoutPlaceholders.length === 0;
30
- }
31
19
  /**
32
20
  * Main translator function to process source file and create translations.
33
21
  * @param {string} file - Path to source file.
@@ -164,6 +152,10 @@ class TranslationService {
164
152
  const languageConcurrency = options.concurrencyLimit || 3;
165
153
  const targetLanguages = [...(options.targets || [])];
166
154
  await uiManager.log(`Processing ${targetLanguages.length} languages with concurrency of ${languageConcurrency}`);
155
+ // Initialize shared SourceCodeAnalyzer
156
+ console.log("Initializing Locai Engine...");
157
+ const sharedAnalyzer = new SourceCodeAnalyzer();
158
+ sharedAnalyzer.initialize();
167
159
  // Create shared orchestrators array to collect review queues
168
160
  const orchestrators = [];
169
161
  for (let i = 0; i < targetLanguages.length; i += languageConcurrency) {
@@ -180,6 +172,7 @@ class TranslationService {
180
172
  ...options,
181
173
  concurrencyLimit: 1,
182
174
  progressOptions,
175
+ sourceCodeAnalyzer: sharedAnalyzer,
183
176
  });
184
177
  orchestrators.push(orchestrator);
185
178
  return this.processLanguage(targetLang, resolvedFile, flattenedSource, orchestrator, { ...options, progressOptions }, globalStats, comparison);
@@ -201,207 +194,7 @@ class TranslationService {
201
194
  * @returns {Promise<LanguageProcessResult>} - Status and messages for this language.
202
195
  */
203
196
  async processLanguage(targetLang, sourceFile, flattenedSource, orchestrator, options, globalStats, comparison) {
204
- const langStartTime = Date.now();
205
- try {
206
- if (!targetLang || typeof targetLang !== "string") {
207
- throw new Error("Invalid target language provided");
208
- }
209
- if (!sourceFile || typeof sourceFile !== "string") {
210
- throw new Error("Invalid source file path provided");
211
- }
212
- if (!flattenedSource || typeof flattenedSource !== "object") {
213
- throw new Error("Invalid flattened source data provided");
214
- }
215
- if (!orchestrator || typeof orchestrator.processTranslations !== "function") {
216
- throw new Error("Invalid orchestrator instance provided");
217
- }
218
- if (!globalStats || typeof globalStats !== "object") {
219
- throw new Error("Invalid global stats object provided");
220
- }
221
- const safeTargetLang = InputValidator.validateLanguageCode(targetLang, "target language");
222
- await uiManager.log(`Starting translations for ${safeTargetLang}`);
223
- let finalStatus = null;
224
- let savedMessage = undefined;
225
- statisticsManager.initLanguageStats(safeTargetLang);
226
- const sourceDir = path.dirname(sourceFile);
227
- const safeTargetFilename = `${safeTargetLang}.json`;
228
- const targetPath = InputValidator.createSafeFilePath(sourceDir, safeTargetFilename);
229
- let targetContent = {};
230
- try {
231
- targetContent = await FileManager.readJSON(targetPath);
232
- if (!targetContent || typeof targetContent !== "object") {
233
- console.warn(`Invalid content in ${targetPath}, using empty object`);
234
- targetContent = {};
235
- }
236
- }
237
- catch (err) {
238
- if (err.code === "ENOENT") {
239
- await uiManager.log(`\ud83c\udd95 Creating new translation file for ${safeTargetLang}`);
240
- }
241
- else {
242
- if (options.debug) {
243
- console.warn(`Error reading ${targetPath}: ${err.message}, using empty object`);
244
- }
245
- }
246
- targetContent = {};
247
- }
248
- let flattenedTarget = {};
249
- try {
250
- flattenedTarget = ObjectTransformer.flatten(targetContent);
251
- if (!flattenedTarget || typeof flattenedTarget !== "object") {
252
- flattenedTarget = {};
253
- }
254
- }
255
- catch (err) {
256
- console.warn(`Error flattening target content: ${err.message}, using empty object`);
257
- flattenedTarget = {};
258
- }
259
- const missingKeys = [];
260
- let hasPlaceholderOnlyChanges = false;
261
- for (const [key, sourceText] of Object.entries(flattenedSource)) {
262
- try {
263
- InputValidator.validateKey(key, "translation key");
264
- InputValidator.validateText(sourceText, "source text");
265
- }
266
- catch (keyError) {
267
- await uiManager.log(`\u26a0\ufe0f Skipping invalid key/text: ${keyError.message}`);
268
- globalStats.languages[safeTargetLang].failed++;
269
- globalStats.failed++;
270
- continue;
271
- }
272
- globalStats.languages[safeTargetLang].processed++;
273
- if (this.isPlaceholderOnlyText(sourceText)) {
274
- const existingValue = flattenedTarget[key];
275
- if (existingValue !== sourceText) {
276
- flattenedTarget[key] = sourceText;
277
- hasPlaceholderOnlyChanges = true;
278
- if (options.debug) {
279
- await uiManager.log(` Copying placeholder-only text: "${sourceText}"`);
280
- }
281
- }
282
- globalStats.languages[safeTargetLang].skipped++;
283
- globalStats.skipped++;
284
- continue;
285
- }
286
- const isNewKey = comparison.newKeys.includes(key);
287
- const isModifiedKey = comparison.modifiedKeys.includes(key);
288
- const keyExistsInTarget = key in flattenedTarget;
289
- if (keyExistsInTarget && !options.forceUpdate && !isModifiedKey) {
290
- globalStats.languages[safeTargetLang].skipped++;
291
- globalStats.skipped++;
292
- continue;
293
- }
294
- missingKeys.push({
295
- key,
296
- text: sourceText,
297
- targetLang: safeTargetLang,
298
- existingTranslation: flattenedTarget[key],
299
- isModified: isModifiedKey,
300
- isNew: isNewKey,
301
- });
302
- }
303
- if (missingKeys.length === 0 && !hasPlaceholderOnlyChanges) {
304
- await uiManager.log(`All translations exist for ${safeTargetLang}`);
305
- globalStats.languages[safeTargetLang].timeMs = Date.now() - langStartTime;
306
- return { status: { completed: 0, total: 0, language: safeTargetLang } };
307
- }
308
- if (missingKeys.length > 0) {
309
- await uiManager.log(`Found ${missingKeys.length} missing translations for ${safeTargetLang}`);
310
- }
311
- let results = [];
312
- if (missingKeys.length > 0) {
313
- try {
314
- results = await orchestrator.processTranslations(missingKeys);
315
- if (!Array.isArray(results)) {
316
- console.warn("Invalid results from orchestrator, using empty array");
317
- results = [];
318
- }
319
- }
320
- catch (err) {
321
- console.error(`Error processing translations for ${safeTargetLang}: ${err.message}`);
322
- results = [];
323
- if (globalStats.languages[safeTargetLang]) {
324
- globalStats.languages[safeTargetLang].failed += missingKeys.length;
325
- globalStats.failed += missingKeys.length;
326
- }
327
- }
328
- }
329
- else {
330
- if (hasPlaceholderOnlyChanges) {
331
- await uiManager.log(`Found placeholder-only changes for ${safeTargetLang}`);
332
- }
333
- }
334
- try {
335
- if (orchestrator?.progress &&
336
- typeof orchestrator.progress.getStatus === "function") {
337
- finalStatus = orchestrator.progress.getStatus();
338
- }
339
- }
340
- catch (err) {
341
- console.warn(`Error getting orchestrator status: ${err.message}`);
342
- }
343
- let validResults = [];
344
- try {
345
- validResults = results.filter((result) => result && result.success === true);
346
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
347
- validResults = validResults.filter((result) => result.key &&
348
- typeof result.key === "string" &&
349
- result.translated &&
350
- typeof result.translated === "string");
351
- }
352
- catch (err) {
353
- console.warn(`Error filtering results: ${err.message}`);
354
- validResults = [];
355
- }
356
- if (validResults.length > 0) {
357
- validResults.forEach(({ key, translated }) => {
358
- if (translated) {
359
- flattenedTarget[key] = translated;
360
- }
361
- });
362
- }
363
- const hasChangesToSave = validResults.length > 0 || hasPlaceholderOnlyChanges;
364
- if (hasChangesToSave) {
365
- const unflattened = ObjectTransformer.unflatten(flattenedTarget);
366
- await FileManager.writeJSON(targetPath, unflattened);
367
- globalStats.total += validResults.length;
368
- globalStats.success += validResults.length;
369
- globalStats.failed += results.length - validResults.length;
370
- globalStats.languages[safeTargetLang].added += validResults.length;
371
- globalStats.languages[safeTargetLang].failed +=
372
- results.length - validResults.length;
373
- validResults.forEach((result) => {
374
- if (result.context) {
375
- const category = result.context.category || "general";
376
- globalStats.byCategory[category] =
377
- (globalStats.byCategory[category] || 0) + 1;
378
- if (!globalStats.details[category]) {
379
- globalStats.details[category] = {
380
- totalConfidence: 0,
381
- samples: 0,
382
- };
383
- }
384
- globalStats.details[category].totalConfidence +=
385
- result.context.confidence || 0;
386
- globalStats.details[category].samples++;
387
- }
388
- });
389
- savedMessage = `Translations saved: ${safeTargetLang}.json`;
390
- }
391
- globalStats.languages[safeTargetLang].timeMs = Date.now() - langStartTime;
392
- return { status: finalStatus, savedMessage };
393
- }
394
- catch (error) {
395
- const safeError = error.message.includes("outside working directory")
396
- ? "Invalid file path detected"
397
- : error.message;
398
- await uiManager.log(`\n\u274c Error processing ${targetLang}: ${safeError}`);
399
- if (globalStats.languages[targetLang]) {
400
- globalStats.languages[targetLang].error = safeError;
401
- globalStats.languages[targetLang].timeMs = Date.now() - langStartTime;
402
- }
403
- return { status: null, error: safeError };
404
- }
197
+ return await LanguageProcessor.processLanguage(targetLang, sourceFile, flattenedSource, orchestrator, options, globalStats, comparison);
405
198
  }
406
199
  /**
407
200
  * Finalize translation process and save state.
@@ -415,6 +208,7 @@ class TranslationService {
415
208
  async finalizeTranslation(stateManager, projectRoot, currentState, globalStats, startTime, options) {
416
209
  globalStats.endTime = new Date().toISOString();
417
210
  globalStats.totalDuration = (Date.now() - startTime) / 1000;
211
+ globalStats.totalTime = globalStats.totalDuration;
418
212
  await uiManager.displayGlobalSummary(globalStats, (options.targets || []).length);
419
213
  // Save review queue if confidence scoring was enabled
420
214
  if (options.saveReviewQueue || options.minConfidence !== undefined) {
@@ -0,0 +1,13 @@
1
+ export interface CodeContext {
2
+ filePath: string;
3
+ line: number;
4
+ component?: string;
5
+ usageSnippet?: string;
6
+ comments?: string[];
7
+ props?: Record<string, string>;
8
+ }
9
+ export interface SourceAnalyzerConfig {
10
+ enabled?: boolean;
11
+ include?: string[];
12
+ exclude?: string[];
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,58 @@
1
+ import { OrchestratorOptions } from "../core/orchestrator.js";
2
+ import { ContextConfig } from "../core/context-processor.js";
3
+ export interface TranslationOptions extends Omit<OrchestratorOptions, "context"> {
4
+ source: string;
5
+ targets?: string[];
6
+ localesDir?: string;
7
+ debug?: boolean;
8
+ forceUpdate?: boolean;
9
+ syncOptions?: {
10
+ enabled?: boolean;
11
+ removeDeletedKeys?: boolean;
12
+ };
13
+ concurrencyLimit?: number;
14
+ saveReviewQueue?: boolean;
15
+ minConfidence?: number;
16
+ fileExtensions?: string[];
17
+ progressOptions?: any;
18
+ qualityChecks?: any;
19
+ styleGuide?: any;
20
+ context?: ContextConfig;
21
+ lengthControl?: any;
22
+ }
23
+ export interface GlobalStats {
24
+ total: number;
25
+ success: number;
26
+ failed: number;
27
+ skipped: number;
28
+ languages: Record<string, any>;
29
+ byCategory: Record<string, number>;
30
+ details: Record<string, {
31
+ totalConfidence: number;
32
+ samples: number;
33
+ }>;
34
+ error?: {
35
+ message: string;
36
+ time: string;
37
+ stack?: string;
38
+ };
39
+ orchestrators: any[];
40
+ endTime?: string;
41
+ totalDuration?: number;
42
+ totalTime: number;
43
+ startTime: string;
44
+ }
45
+ export interface MissingKey {
46
+ key: string;
47
+ text: string;
48
+ targetLang: string;
49
+ existingTranslation?: string;
50
+ isModified: boolean;
51
+ isNew: boolean;
52
+ issueDetails?: any;
53
+ }
54
+ export interface LanguageProcessResult {
55
+ status: any;
56
+ savedMessage?: string;
57
+ error?: string;
58
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -74,10 +74,10 @@ export class UIManager {
74
74
  if (details && details.samples > 0) {
75
75
  const avgConfidence = details.totalConfidence / details.samples;
76
76
  const confidenceStr = `${(avgConfidence * 100).toFixed(1)}%`;
77
- await this.log(` ${category}: ${count} items (${confidenceStr} avg confidence)`);
77
+ await this.log(`${category}: ${count} items (${confidenceStr} avg confidence)`);
78
78
  }
79
79
  else {
80
- await this.log(` ${category}: ${count} items`);
80
+ await this.log(`${category}: ${count} items`);
81
81
  }
82
82
  }
83
83
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "locai",
3
- "version": "2.3.0",
3
+ "version": "2.5.0",
4
4
  "type": "module",
5
5
  "main": "dist/core/orchestrator.js",
6
6
  "types": "dist/core/orchestrator.d.ts",
@@ -16,7 +16,7 @@
16
16
  "start": "node dist/core/orchestrator.js",
17
17
  "dev": "tsx src/core/orchestrator.js",
18
18
  "build": "tsc",
19
- "type-check": "tsc --noEmit",
19
+ "typecheck": "tsc --noEmit",
20
20
  "lint": "eslint .",
21
21
  "lint:fix": "eslint . --fix",
22
22
  "format": "prettier --write \"**/*.{js,json,md,jsx,ts,tsx,css}\"",
@@ -27,18 +27,26 @@
27
27
  "prepublishOnly": "pnpm build"
28
28
  },
29
29
  "keywords": [
30
- "translation",
31
- "localization",
32
30
  "i18n",
33
- "nextjs",
34
- "ai-translation",
35
- "cli",
31
+ "localization",
32
+ "translation",
33
+ "l10n",
34
+ "internationalization",
36
35
  "automation",
36
+ "cli",
37
37
  "llm",
38
+ "ai",
39
+ "artificial-intelligence",
38
40
  "openai",
41
+ "anthropic",
42
+ "claude",
39
43
  "gemini",
40
44
  "deepseek",
41
- "developer-tools"
45
+ "xai",
46
+ "vector-database",
47
+ "semantic-search",
48
+ "developer-tools",
49
+ "typescript"
42
50
  ],
43
51
  "author": {
44
52
  "name": "Ahmet Enes Dur",
@@ -64,8 +72,8 @@
64
72
  "./translator": "./dist/commands/translator.js"
65
73
  },
66
74
  "dependencies": {
67
- "axios": "1.13.1",
68
- "c12": "^3.3.2",
75
+ "axios": "1.13.2",
76
+ "c12": "^3.3.3",
69
77
  "commander": "^14.0.2",
70
78
  "dot-properties": "^1.1.0",
71
79
  "dotenv": "^17.2.3",
@@ -73,25 +81,26 @@
73
81
  "gettext-parser": "^8.0.0",
74
82
  "jiti": "^2.6.1",
75
83
  "js-yaml": "^4.1.1",
76
- "lru-cache": "^11.2.2",
84
+ "lru-cache": "^11.2.4",
77
85
  "p-limit": "^7.2.0",
78
86
  "prompts": "^2.4.2",
87
+ "ts-morph": "^27.0.2",
79
88
  "vectra": "^0.11.1"
80
89
  },
81
90
  "devDependencies": {
82
- "@eslint/js": "^9.39.1",
91
+ "@eslint/js": "^9.39.2",
83
92
  "@types/js-yaml": "^4.0.9",
84
- "@types/node": "^25.0.1",
93
+ "@types/node": "^25.0.3",
85
94
  "@types/prompts": "^2.4.9",
86
- "@vitest/coverage-v8": "^4.0.15",
87
- "eslint": "^9.39.1",
95
+ "@vitest/coverage-v8": "^4.0.16",
96
+ "eslint": "^9.39.2",
88
97
  "eslint-config-prettier": "^10.1.8",
89
98
  "globals": "^16.5.0",
90
- "prettier": "^3.6.2",
99
+ "prettier": "^3.7.4",
91
100
  "std-mocks": "^2.0.0",
92
101
  "tsx": "^4.21.0",
93
102
  "typescript": "^5.9.3",
94
- "vitest": "^4.0.15"
103
+ "vitest": "^4.0.16"
95
104
  },
96
105
  "files": [
97
106
  "dist/"