@i18n-agent/mcp-client 1.8.20 → 1.8.22

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 CHANGED
@@ -346,7 +346,7 @@ Translate text content with cultural adaptation and context awareness.
346
346
  - `targetLanguage` (string): Target language code
347
347
  - `targetAudience` (string): Target audience context
348
348
  - `industry` (string): Industry context
349
- - `namespace` (string, **required**): Unique namespace identifier for backend tracking and project organization
349
+ - `namespace` (string, optional): Optional namespace identifier for backend tracking and project organization
350
350
  - `sourceLanguage` (string, optional): Source language (auto-detected if not provided)
351
351
  - `region` (string, optional): Specific region for localization
352
352
 
package/mcp-client.js CHANGED
@@ -18,6 +18,7 @@ import {
18
18
  import axios from 'axios';
19
19
  import fs from 'fs';
20
20
  import path from 'path';
21
+ import { detectNamespaceFromPath, generateNamespaceSuggestions, getNamespaceSuggestionText } from './namespace-detector.js';
21
22
 
22
23
  const server = new Server(
23
24
  {
@@ -115,10 +116,10 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
115
116
  },
116
117
  namespace: {
117
118
  type: 'string',
118
- description: 'Unique namespace identifier for backend tracking and project organization (required for production use)',
119
+ description: 'Optional namespace identifier for backend tracking and project organization (recommended for file-based workflows)',
119
120
  },
120
121
  },
121
- required: ['texts', 'targetLanguages', 'namespace'],
122
+ required: ['texts', 'targetLanguages'],
122
123
  },
123
124
  },
124
125
  {
@@ -450,9 +451,7 @@ async function handleTranslateText(args) {
450
451
  throw new Error('texts must be a non-empty array');
451
452
  }
452
453
 
453
- if (!namespace) {
454
- throw new Error('namespace is required for translation tracking and project organization');
455
- }
454
+ // Namespace is optional for text translation, but recommended for organizational tracking
456
455
 
457
456
  // Normalize targetLanguages - accept both string and array
458
457
  let targetLanguages = rawTargetLanguages;
@@ -769,8 +768,26 @@ async function handleTranslateFile(args) {
769
768
  throw new Error('Either filePath or fileContent must be provided');
770
769
  }
771
770
 
772
- if (!namespace) {
773
- throw new Error('namespace is required for translation tracking and project organization');
771
+ // Auto-detect namespace if not provided and filePath is available
772
+ let finalNamespace = namespace;
773
+ let detectionInfo = null;
774
+
775
+ if (!namespace && filePath) {
776
+ const detection = detectNamespaceFromPath(filePath);
777
+ if (detection.suggestion && detection.confidence > 0.5) {
778
+ finalNamespace = detection.suggestion;
779
+ detectionInfo = detection;
780
+ console.error(`šŸŽÆ [MCP CLIENT] Auto-detected namespace: "${finalNamespace}" (confidence: ${Math.round(detection.confidence * 100)}%, source: ${detection.source})`);
781
+ }
782
+ }
783
+
784
+ if (!finalNamespace) {
785
+ // Provide helpful suggestions when namespace is missing
786
+ const suggestionText = filePath
787
+ ? getNamespaceSuggestionText(filePath, path.basename(filePath))
788
+ : getNamespaceSuggestionText(null, null);
789
+
790
+ throw new Error(`namespace is required for translation tracking and project organization.\n\n${suggestionText}`);
774
791
  }
775
792
 
776
793
  // Normalize targetLanguages - accept both string and array
@@ -815,7 +832,7 @@ async function handleTranslateFile(args) {
815
832
  industry,
816
833
  preserveKeys,
817
834
  outputFormat,
818
- namespace
835
+ namespace: finalNamespace
819
836
  };
820
837
 
821
838
  // Add optional parameters only if defined
@@ -0,0 +1,362 @@
1
+ /**
2
+ * Namespace Auto-Detection Utility for MCP Client
3
+ *
4
+ * Analyzes file paths and suggests namespace names based on project structure.
5
+ * Follows the same validation rules as the service-mcp namespace validator.
6
+ */
7
+
8
+ /**
9
+ * Auto-detect namespace from file path
10
+ */
11
+ export function detectNamespaceFromPath(filePath, options = {}) {
12
+ const { includeAlternatives = true, maxAlternatives = 3 } = options;
13
+
14
+ if (!filePath || typeof filePath !== 'string') {
15
+ return {
16
+ suggestion: null,
17
+ confidence: 0,
18
+ source: 'none',
19
+ reasoning: 'No file path provided',
20
+ alternatives: []
21
+ };
22
+ }
23
+
24
+ // Normalize path separators
25
+ const normalizedPath = filePath.replace(/\\/g, '/');
26
+ const pathSegments = normalizedPath.split('/').filter(segment => segment.length > 0);
27
+
28
+ // Pattern detection strategies (ordered by confidence)
29
+ const detectionStrategies = [
30
+ detectServicePattern,
31
+ detectGitRepoPattern,
32
+ detectProjectFolderPattern,
33
+ detectDirectoryPattern
34
+ ];
35
+
36
+ let bestResult = null;
37
+ const allSuggestions = [];
38
+
39
+ for (const strategy of detectionStrategies) {
40
+ const result = strategy(pathSegments, normalizedPath);
41
+ if (result.suggestion && isValidNamespace(result.suggestion)) {
42
+ if (!bestResult || result.confidence > bestResult.confidence) {
43
+ bestResult = result;
44
+ }
45
+ if (includeAlternatives && !allSuggestions.includes(result.suggestion)) {
46
+ allSuggestions.push(result.suggestion);
47
+ }
48
+ }
49
+ }
50
+
51
+ if (!bestResult) {
52
+ return {
53
+ suggestion: null,
54
+ confidence: 0,
55
+ source: 'none',
56
+ reasoning: 'Could not detect a valid namespace from the file path',
57
+ alternatives: []
58
+ };
59
+ }
60
+
61
+ // Generate alternatives
62
+ const alternatives = includeAlternatives
63
+ ? allSuggestions
64
+ .filter(s => s !== bestResult.suggestion)
65
+ .slice(0, maxAlternatives)
66
+ : [];
67
+
68
+ return {
69
+ ...bestResult,
70
+ alternatives
71
+ };
72
+ }
73
+
74
+ /**
75
+ * Detect service-* pattern (highest confidence)
76
+ * Examples:
77
+ * - /path/to/service-platform/file.json → "service-platform"
78
+ * - /path/to/service-auth/config.yaml → "service-auth"
79
+ */
80
+ function detectServicePattern(pathSegments) {
81
+ for (let i = 0; i < pathSegments.length; i++) {
82
+ const segment = pathSegments[i];
83
+ if (segment.startsWith('service-') && segment.length > 8) {
84
+ const serviceName = segment.toLowerCase();
85
+ return {
86
+ suggestion: serviceName,
87
+ confidence: 0.9,
88
+ source: 'service-name',
89
+ reasoning: `Detected service name "${serviceName}" in path`,
90
+ alternatives: []
91
+ };
92
+ }
93
+ }
94
+
95
+ return { suggestion: null, confidence: 0, source: 'none', reasoning: '', alternatives: [] };
96
+ }
97
+
98
+ /**
99
+ * Detect git repository pattern (high confidence)
100
+ * Examples:
101
+ * - /path/to/i18n-agent/service-platform/file.json → "service-platform"
102
+ * - /path/to/my-project/src/file.ts → "my-project"
103
+ */
104
+ function detectGitRepoPattern(pathSegments) {
105
+ // Look for common project root indicators
106
+ const projectRootIndicators = [
107
+ 'package.json', '.git', 'node_modules', 'src', 'lib', 'app',
108
+ 'components', 'pages', 'public', 'assets', 'docs', 'tests'
109
+ ];
110
+
111
+ // Find potential project root by looking for these indicators
112
+ for (let i = pathSegments.length - 1; i >= 0; i--) {
113
+ const segment = pathSegments[i];
114
+
115
+ // If this looks like a project folder, use it
116
+ if (segment.includes('-') || segment.includes('_')) {
117
+ // Check if next segments contain project indicators
118
+ const remainingPath = pathSegments.slice(i + 1);
119
+ const hasProjectIndicators = remainingPath.some(seg =>
120
+ projectRootIndicators.includes(seg) ||
121
+ seg === 'src' ||
122
+ seg === 'lib' ||
123
+ seg.includes('component') ||
124
+ seg.includes('page')
125
+ );
126
+
127
+ if (hasProjectIndicators) {
128
+ const suggestion = normalizeNamespace(segment);
129
+ if (suggestion) {
130
+ return {
131
+ suggestion,
132
+ confidence: 0.7,
133
+ source: 'git-repo',
134
+ reasoning: `Detected project root "${segment}" based on directory structure`,
135
+ alternatives: []
136
+ };
137
+ }
138
+ }
139
+ }
140
+ }
141
+
142
+ return { suggestion: null, confidence: 0, source: 'none', reasoning: '', alternatives: [] };
143
+ }
144
+
145
+ /**
146
+ * Detect project folder pattern (medium confidence)
147
+ * Examples:
148
+ * - /Users/user/Documents/my-awesome-project/file.js → "my-awesome-project"
149
+ * - /workspace/client-docs/locales/en.json → "client-docs"
150
+ */
151
+ function detectProjectFolderPattern(pathSegments) {
152
+ // Look for segments that contain hyphens or underscores (common in project names)
153
+ const projectLikeSegments = pathSegments.filter(segment =>
154
+ (segment.includes('-') || segment.includes('_')) &&
155
+ segment.length >= 3 &&
156
+ !segment.startsWith('.') &&
157
+ segment !== 'node_modules'
158
+ );
159
+
160
+ if (projectLikeSegments.length > 0) {
161
+ // Use the last project-like segment (closest to the file)
162
+ const lastProjectSegment = projectLikeSegments[projectLikeSegments.length - 1];
163
+ const suggestion = normalizeNamespace(lastProjectSegment);
164
+
165
+ if (suggestion) {
166
+ return {
167
+ suggestion,
168
+ confidence: 0.5,
169
+ source: 'project-folder',
170
+ reasoning: `Detected project-like folder "${lastProjectSegment}" in path`,
171
+ alternatives: []
172
+ };
173
+ }
174
+ }
175
+
176
+ return { suggestion: null, confidence: 0, source: 'none', reasoning: '', alternatives: [] };
177
+ }
178
+
179
+ /**
180
+ * Detect directory pattern (low confidence fallback)
181
+ * Uses the immediate parent directory if it looks reasonable
182
+ */
183
+ function detectDirectoryPattern(pathSegments) {
184
+ if (pathSegments.length < 2) {
185
+ return { suggestion: null, confidence: 0, source: 'none', reasoning: '', alternatives: [] };
186
+ }
187
+
188
+ // Use the parent directory of the file
189
+ const parentDir = pathSegments[pathSegments.length - 2];
190
+ const suggestion = normalizeNamespace(parentDir);
191
+
192
+ if (suggestion && suggestion.length >= 3) {
193
+ return {
194
+ suggestion,
195
+ confidence: 0.3,
196
+ source: 'directory-name',
197
+ reasoning: `Using parent directory "${parentDir}" as fallback`,
198
+ alternatives: []
199
+ };
200
+ }
201
+
202
+ return { suggestion: null, confidence: 0, source: 'none', reasoning: '', alternatives: [] };
203
+ }
204
+
205
+ /**
206
+ * Normalize a potential namespace to follow validation rules
207
+ */
208
+ function normalizeNamespace(input) {
209
+ if (!input || typeof input !== 'string') return null;
210
+
211
+ // Convert to lowercase and trim
212
+ let normalized = input.toLowerCase().trim();
213
+
214
+ // Replace invalid characters with hyphens
215
+ normalized = normalized.replace(/[^a-z0-9_-]/g, '-');
216
+
217
+ // Remove multiple consecutive hyphens/underscores
218
+ normalized = normalized.replace(/[-_]{2,}/g, '-');
219
+
220
+ // Remove leading/trailing hyphens or underscores
221
+ normalized = normalized.replace(/^[-_]+|[-_]+$/g, '');
222
+
223
+ // Check length constraints
224
+ if (normalized.length < 3 || normalized.length > 50) {
225
+ return null;
226
+ }
227
+
228
+ // Check if it's a reserved keyword
229
+ const reservedKeywords = [
230
+ 'admin', 'api', 'www', 'system', 'root', 'default',
231
+ 'translations', 'upload', 'download', 'temp', 'cache',
232
+ 'public', 'private', 'test', 'staging', 'production'
233
+ ];
234
+
235
+ if (reservedKeywords.includes(normalized)) {
236
+ return null;
237
+ }
238
+
239
+ return normalized;
240
+ }
241
+
242
+ /**
243
+ * Quick validation using the same rules as service-mcp
244
+ */
245
+ function isValidNamespace(namespace) {
246
+ if (!namespace || typeof namespace !== 'string') return false;
247
+
248
+ // Basic validation
249
+ const pattern = /^[a-z0-9_-]+$/;
250
+ return (
251
+ namespace.length >= 3 &&
252
+ namespace.length <= 50 &&
253
+ pattern.test(namespace) &&
254
+ !['admin', 'api', 'www', 'system', 'root', 'default'].includes(namespace)
255
+ );
256
+ }
257
+
258
+ /**
259
+ * Generate additional namespace suggestions based on file context
260
+ */
261
+ export function generateNamespaceSuggestions(fileName, fileContent) {
262
+ const suggestions = [];
263
+
264
+ if (fileName) {
265
+ // Extract potential namespace from filename
266
+ const baseName = fileName.replace(/\.(json|yaml|yml|ts|js|md)$/i, '');
267
+ const normalized = normalizeNamespace(baseName);
268
+ if (normalized) {
269
+ suggestions.push(normalized);
270
+ }
271
+
272
+ // Look for common patterns in filename
273
+ if (fileName.includes('i18n') || fileName.includes('locale') || fileName.includes('lang')) {
274
+ suggestions.push('localization', 'translations', 'i18n-project');
275
+ }
276
+
277
+ if (fileName.includes('api') || fileName.includes('endpoint')) {
278
+ suggestions.push('api-docs', 'api-project');
279
+ }
280
+
281
+ if (fileName.includes('component') || fileName.includes('ui')) {
282
+ suggestions.push('components', 'ui-library');
283
+ }
284
+ }
285
+
286
+ if (fileContent) {
287
+ // Analyze content for clues (basic implementation)
288
+ const content = fileContent.toLowerCase();
289
+
290
+ if (content.includes('api') || content.includes('endpoint')) {
291
+ suggestions.push('api-docs');
292
+ }
293
+
294
+ if (content.includes('component') || content.includes('react') || content.includes('vue')) {
295
+ suggestions.push('components');
296
+ }
297
+
298
+ if (content.includes('translation') || content.includes('locale')) {
299
+ suggestions.push('translations');
300
+ }
301
+ }
302
+
303
+ // Remove duplicates and filter valid ones
304
+ return [...new Set(suggestions)]
305
+ .filter(s => isValidNamespace(s))
306
+ .slice(0, 5);
307
+ }
308
+
309
+ /**
310
+ * Get helpful namespace suggestions text for user guidance
311
+ */
312
+ export function getNamespaceSuggestionText(filePath, fileName) {
313
+ if (!filePath && !fileName) {
314
+ return `šŸ’” Namespace Suggestions:
315
+ • Use descriptive project names: "my-website", "mobile-app"
316
+ • Include service names: "service-auth", "api-gateway"
317
+ • Use organization prefixes: "company-docs", "team-frontend"
318
+ • Avoid generic names: "app", "project", "files"`;
319
+ }
320
+
321
+ const detection = detectNamespaceFromPath(filePath || fileName);
322
+
323
+ if (detection.suggestion) {
324
+ let text = `šŸŽÆ Auto-detected suggestion: "${detection.suggestion}"`;
325
+ text += `\nšŸ“ Source: ${getSourceDescription(detection.source)}`;
326
+ text += `\nšŸ’Ŗ Confidence: ${Math.round(detection.confidence * 100)}%`;
327
+
328
+ if (detection.alternatives.length > 0) {
329
+ text += `\nšŸ”„ Alternatives: ${detection.alternatives.join(', ')}`;
330
+ }
331
+
332
+ return text;
333
+ }
334
+
335
+ return `šŸ’” Namespace Suggestions for "${fileName || filePath}":
336
+ • Extract from path: "${getPathBasedSuggestion(filePath || fileName)}"
337
+ • Use project context: "docs", "website", "api"
338
+ • Include team/org: "frontend-team", "backend-api"
339
+ • Keep it descriptive: "user-dashboard", "admin-panel"`;
340
+ }
341
+
342
+ function getSourceDescription(source) {
343
+ const descriptions = {
344
+ 'service-name': 'Service name pattern detected',
345
+ 'git-repo': 'Git repository structure analyzed',
346
+ 'project-folder': 'Project folder pattern found',
347
+ 'directory-name': 'Parent directory used as fallback'
348
+ };
349
+ return descriptions[source] || 'Unknown source';
350
+ }
351
+
352
+ function getPathBasedSuggestion(path) {
353
+ if (!path) return 'my-project';
354
+
355
+ const segments = path.replace(/\\/g, '/').split('/').filter(s => s.length > 0);
356
+ for (let i = segments.length - 1; i >= 0; i--) {
357
+ const normalized = normalizeNamespace(segments[i]);
358
+ if (normalized) return normalized;
359
+ }
360
+
361
+ return 'my-project';
362
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-agent/mcp-client",
3
- "version": "1.8.20",
3
+ "version": "1.8.22",
4
4
  "description": "šŸŒ i18n-agent MCP Client - 48 languages, AI-powered translation for Claude, Claude Code, Cursor, VS Code, Codex. Get API key at https://app.i18nagent.ai",
5
5
  "main": "mcp-client.js",
6
6
  "bin": {
@@ -42,6 +42,7 @@
42
42
  },
43
43
  "files": [
44
44
  "mcp-client.js",
45
+ "namespace-detector.js",
45
46
  "install.js",
46
47
  "README.md",
47
48
  "LICENSE",