@scoutello/i18n-magic 0.52.0 → 0.54.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 +236 -266
- package/dist/lib/utils.d.ts +5 -4
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +49 -37
- package/dist/lib/utils.js.map +1 -1
- package/dist/mcp-server.js +451 -8
- package/dist/mcp-server.js.map +1 -1
- package/package.json +1 -1
- package/src/lib/utils.ts +55 -38
- package/src/mcp-server.ts +540 -8
package/src/mcp-server.ts
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
ListToolsRequestSchema,
|
|
6
6
|
} from "@modelcontextprotocol/sdk/types.js"
|
|
7
7
|
import { z } from "zod"
|
|
8
|
-
import { addTranslationKey, getMissingKeys, loadConfig } from "./lib/utils.js"
|
|
8
|
+
import { addTranslationKey, getMissingKeys, loadConfig, loadLocalesFile } from "./lib/utils.js"
|
|
9
9
|
import type { Configuration } from "./lib/types.js"
|
|
10
10
|
import path from "path"
|
|
11
11
|
import fs from "fs"
|
|
@@ -67,7 +67,8 @@ function resolveProjectRoot(): string {
|
|
|
67
67
|
// Zod schema for the add_translation_key tool parameters
|
|
68
68
|
const AddTranslationKeySchema = z.object({
|
|
69
69
|
key: z.string().describe("The translation key to add (e.g., \"welcomeMessage\")"),
|
|
70
|
-
value: z.string().describe("The
|
|
70
|
+
value: z.string().describe("The text value for this translation key"),
|
|
71
|
+
language: z.string().optional().describe("The language code of the provided value (e.g., \"en\", \"de\", \"fr\"). Defaults to \"en\" (English) if not specified."),
|
|
71
72
|
})
|
|
72
73
|
|
|
73
74
|
// Zod schema for the list_untranslated_keys tool parameters
|
|
@@ -80,6 +81,35 @@ const ListUntranslatedKeysSchema = z.object({
|
|
|
80
81
|
),
|
|
81
82
|
})
|
|
82
83
|
|
|
84
|
+
// Zod schema for the get_translation_key tool parameters
|
|
85
|
+
const GetTranslationKeySchema = z.object({
|
|
86
|
+
key: z.string().describe("The translation key to retrieve (e.g., \"welcomeMessage\")"),
|
|
87
|
+
namespace: z
|
|
88
|
+
.string()
|
|
89
|
+
.optional()
|
|
90
|
+
.describe(
|
|
91
|
+
"Optional namespace to search in. If not provided, searches in default namespace first, then all namespaces.",
|
|
92
|
+
),
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
// Zod schema for the update_translation_key tool parameters
|
|
96
|
+
const UpdateTranslationKeySchema = z.object({
|
|
97
|
+
key: z.string().describe("The translation key to update (e.g., \"welcomeMessage\")"),
|
|
98
|
+
value: z.string().describe("The new text value for this translation key"),
|
|
99
|
+
language: z.string().optional().describe("The language code of the provided value (e.g., \"en\", \"de\", \"fr\"). Defaults to \"en\" (English) if not specified."),
|
|
100
|
+
namespace: z
|
|
101
|
+
.string()
|
|
102
|
+
.optional()
|
|
103
|
+
.describe(
|
|
104
|
+
"Optional namespace to update. If not provided, updates the key in all namespaces where it exists.",
|
|
105
|
+
),
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
// Zod schema for the search_translations tool parameters
|
|
109
|
+
const SearchTranslationsSchema = z.object({
|
|
110
|
+
query: z.string().describe("Search term to find in translation keys or values (fuzzy search)"),
|
|
111
|
+
})
|
|
112
|
+
|
|
83
113
|
class I18nMagicServer {
|
|
84
114
|
private server: Server
|
|
85
115
|
private config: Configuration | null = null
|
|
@@ -154,7 +184,7 @@ class I18nMagicServer {
|
|
|
154
184
|
tools: [
|
|
155
185
|
{
|
|
156
186
|
name: "add_translation_key",
|
|
157
|
-
description: "Add a new translation key with
|
|
187
|
+
description: "Add a new translation key with a text value. You can optionally specify the language of the value you're providing (defaults to English).",
|
|
158
188
|
inputSchema: {
|
|
159
189
|
type: "object",
|
|
160
190
|
properties: {
|
|
@@ -166,7 +196,12 @@ class I18nMagicServer {
|
|
|
166
196
|
value: {
|
|
167
197
|
type: "string",
|
|
168
198
|
description:
|
|
169
|
-
"The
|
|
199
|
+
"The text value for this translation key",
|
|
200
|
+
},
|
|
201
|
+
language: {
|
|
202
|
+
type: "string",
|
|
203
|
+
description:
|
|
204
|
+
"The language code of the provided value (e.g., \"en\" for English, \"de\" for German, \"fr\" for French). Defaults to \"en\" if not specified.",
|
|
170
205
|
},
|
|
171
206
|
},
|
|
172
207
|
required: ["key", "value"],
|
|
@@ -188,6 +223,74 @@ class I18nMagicServer {
|
|
|
188
223
|
required: [],
|
|
189
224
|
},
|
|
190
225
|
},
|
|
226
|
+
{
|
|
227
|
+
name: "get_translation_key",
|
|
228
|
+
description:
|
|
229
|
+
"Retrieve the English value for a specific translation key. This tool searches for the key in the locale files and always returns the English translation if it exists.",
|
|
230
|
+
inputSchema: {
|
|
231
|
+
type: "object",
|
|
232
|
+
properties: {
|
|
233
|
+
key: {
|
|
234
|
+
type: "string",
|
|
235
|
+
description:
|
|
236
|
+
"The translation key to retrieve (e.g., \"welcomeMessage\")",
|
|
237
|
+
},
|
|
238
|
+
namespace: {
|
|
239
|
+
type: "string",
|
|
240
|
+
description:
|
|
241
|
+
"Optional namespace to search in. If not provided, searches in default namespace first, then all namespaces.",
|
|
242
|
+
},
|
|
243
|
+
},
|
|
244
|
+
required: ["key"],
|
|
245
|
+
},
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
name: "update_translation_key",
|
|
249
|
+
description:
|
|
250
|
+
"Update an existing translation key with a new text value. You can optionally specify the language of the value you're providing (defaults to English). This will update the key across all locales (translating automatically to other languages) and all namespaces where the key exists. Use this when you need to fix typos, improve wording, or change the text of an existing translation. If you're not sure if a key exists, use get_translation_key or search_translations first.",
|
|
251
|
+
inputSchema: {
|
|
252
|
+
type: "object",
|
|
253
|
+
properties: {
|
|
254
|
+
key: {
|
|
255
|
+
type: "string",
|
|
256
|
+
description:
|
|
257
|
+
"The translation key to update (e.g., \"welcomeMessage\")",
|
|
258
|
+
},
|
|
259
|
+
value: {
|
|
260
|
+
type: "string",
|
|
261
|
+
description:
|
|
262
|
+
"The new text value for this translation key",
|
|
263
|
+
},
|
|
264
|
+
language: {
|
|
265
|
+
type: "string",
|
|
266
|
+
description:
|
|
267
|
+
"The language code of the provided value (e.g., \"en\" for English, \"de\" for German, \"fr\" for French). Defaults to \"en\" if not specified.",
|
|
268
|
+
},
|
|
269
|
+
namespace: {
|
|
270
|
+
type: "string",
|
|
271
|
+
description:
|
|
272
|
+
"Optional namespace to update. If not provided, updates the key in all namespaces where it exists.",
|
|
273
|
+
},
|
|
274
|
+
},
|
|
275
|
+
required: ["key", "value"],
|
|
276
|
+
},
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "search_translations",
|
|
280
|
+
description:
|
|
281
|
+
"Search for translations by keyword or phrase across ALL namespaces. This tool performs fuzzy search across both translation keys AND their English values, making it perfect for finding existing translations before adding new ones. Use this to: 1) Check if similar text already exists to avoid duplicates, 2) Find the key name when you only remember part of the text, 3) Discover related translations. Always returns the key name AND English value for each result.",
|
|
282
|
+
inputSchema: {
|
|
283
|
+
type: "object",
|
|
284
|
+
properties: {
|
|
285
|
+
query: {
|
|
286
|
+
type: "string",
|
|
287
|
+
description:
|
|
288
|
+
"Search term to find in translation keys or values (e.g., 'password', 'welcome', 'click'). Fuzzy search - doesn't need to be exact. Searches across all namespaces.",
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
required: ["query"],
|
|
292
|
+
},
|
|
293
|
+
},
|
|
191
294
|
],
|
|
192
295
|
}
|
|
193
296
|
})
|
|
@@ -220,6 +323,7 @@ class I18nMagicServer {
|
|
|
220
323
|
result = await addTranslationKey({
|
|
221
324
|
key: params.key,
|
|
222
325
|
value: params.value,
|
|
326
|
+
language: params.language || "en",
|
|
223
327
|
config,
|
|
224
328
|
})
|
|
225
329
|
} finally {
|
|
@@ -234,14 +338,15 @@ class I18nMagicServer {
|
|
|
234
338
|
text: JSON.stringify(
|
|
235
339
|
{
|
|
236
340
|
success: true,
|
|
237
|
-
message: `Successfully added translation key "${result.key}" to affected namespaces: ${result.namespace} (${result.locale})`,
|
|
341
|
+
message: `Successfully added translation key "${result.key}" to affected namespaces: ${result.namespace} (locales: ${result.locale})`,
|
|
238
342
|
key: result.key,
|
|
239
343
|
value: result.value,
|
|
344
|
+
providedLanguage: params.language || "en",
|
|
240
345
|
namespace: result.namespace,
|
|
241
|
-
|
|
346
|
+
locales: result.locale,
|
|
242
347
|
nextStep: result.locale.includes(',')
|
|
243
|
-
? "
|
|
244
|
-
: "
|
|
348
|
+
? "Key was automatically translated to multiple locales"
|
|
349
|
+
: "Run 'i18n-magic sync' to translate this key to other locales",
|
|
245
350
|
diagnostics: logMessages.join('\n'),
|
|
246
351
|
},
|
|
247
352
|
null,
|
|
@@ -396,6 +501,433 @@ class I18nMagicServer {
|
|
|
396
501
|
}
|
|
397
502
|
}
|
|
398
503
|
|
|
504
|
+
if (request.params.name === "get_translation_key") {
|
|
505
|
+
try {
|
|
506
|
+
// Validate parameters
|
|
507
|
+
const params = GetTranslationKeySchema.parse(
|
|
508
|
+
request.params.arguments,
|
|
509
|
+
)
|
|
510
|
+
|
|
511
|
+
// Ensure config is loaded
|
|
512
|
+
const config = await this.ensureConfig()
|
|
513
|
+
|
|
514
|
+
// Suppress console.log to prevent interference with MCP JSON protocol
|
|
515
|
+
const originalConsoleLog = console.log
|
|
516
|
+
console.log = () => {}
|
|
517
|
+
|
|
518
|
+
let foundValue: string | null = null
|
|
519
|
+
let foundNamespace: string | null = null
|
|
520
|
+
|
|
521
|
+
try {
|
|
522
|
+
// If namespace is specified, only check that namespace
|
|
523
|
+
if (params.namespace) {
|
|
524
|
+
const keys = await loadLocalesFile(config.loadPath, "en", params.namespace)
|
|
525
|
+
if (keys[params.key]) {
|
|
526
|
+
foundValue = keys[params.key]
|
|
527
|
+
foundNamespace = params.namespace
|
|
528
|
+
}
|
|
529
|
+
} else {
|
|
530
|
+
// Try default namespace first
|
|
531
|
+
try {
|
|
532
|
+
const keys = await loadLocalesFile(config.loadPath, "en", config.defaultNamespace)
|
|
533
|
+
if (keys[params.key]) {
|
|
534
|
+
foundValue = keys[params.key]
|
|
535
|
+
foundNamespace = config.defaultNamespace
|
|
536
|
+
}
|
|
537
|
+
} catch (error) {
|
|
538
|
+
// Default namespace file doesn't exist or has issues, continue to search other namespaces
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// If not found in default namespace, search all other namespaces
|
|
542
|
+
if (!foundValue) {
|
|
543
|
+
for (const namespace of config.namespaces) {
|
|
544
|
+
if (namespace === config.defaultNamespace) continue // Already checked
|
|
545
|
+
|
|
546
|
+
try {
|
|
547
|
+
const keys = await loadLocalesFile(config.loadPath, "en", namespace)
|
|
548
|
+
if (keys[params.key]) {
|
|
549
|
+
foundValue = keys[params.key]
|
|
550
|
+
foundNamespace = namespace
|
|
551
|
+
break
|
|
552
|
+
}
|
|
553
|
+
} catch (error) {
|
|
554
|
+
// Namespace file doesn't exist or has issues, continue
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
} finally {
|
|
560
|
+
// Restore console.log
|
|
561
|
+
console.log = originalConsoleLog
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
if (foundValue) {
|
|
565
|
+
return {
|
|
566
|
+
content: [
|
|
567
|
+
{
|
|
568
|
+
type: "text",
|
|
569
|
+
text: JSON.stringify(
|
|
570
|
+
{
|
|
571
|
+
success: true,
|
|
572
|
+
key: params.key,
|
|
573
|
+
value: foundValue,
|
|
574
|
+
namespace: foundNamespace,
|
|
575
|
+
locale: "en",
|
|
576
|
+
},
|
|
577
|
+
null,
|
|
578
|
+
2,
|
|
579
|
+
),
|
|
580
|
+
},
|
|
581
|
+
],
|
|
582
|
+
}
|
|
583
|
+
} else {
|
|
584
|
+
return {
|
|
585
|
+
content: [
|
|
586
|
+
{
|
|
587
|
+
type: "text",
|
|
588
|
+
text: JSON.stringify(
|
|
589
|
+
{
|
|
590
|
+
success: false,
|
|
591
|
+
error: `Translation key "${params.key}" not found in English locale${params.namespace ? ` (namespace: ${params.namespace})` : ""}`,
|
|
592
|
+
key: params.key,
|
|
593
|
+
searchedNamespace: params.namespace || "all namespaces",
|
|
594
|
+
suggestion: "Use list_untranslated_keys to see all missing keys or add_translation_key to add this key",
|
|
595
|
+
},
|
|
596
|
+
null,
|
|
597
|
+
2,
|
|
598
|
+
),
|
|
599
|
+
},
|
|
600
|
+
],
|
|
601
|
+
isError: false,
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
} catch (error) {
|
|
605
|
+
const errorMessage =
|
|
606
|
+
error instanceof Error ? error.message : "Unknown error occurred"
|
|
607
|
+
|
|
608
|
+
// Get more detailed error information
|
|
609
|
+
let errorDetails = errorMessage
|
|
610
|
+
if (error instanceof Error) {
|
|
611
|
+
const cause = (error as any).cause
|
|
612
|
+
if (cause instanceof Error) {
|
|
613
|
+
errorDetails = `${errorMessage}\nCause: ${cause.message}\nStack: ${cause.stack}`
|
|
614
|
+
} else if (cause) {
|
|
615
|
+
errorDetails = `${errorMessage}\nCause: ${JSON.stringify(cause)}`
|
|
616
|
+
}
|
|
617
|
+
if (error.stack) {
|
|
618
|
+
errorDetails = `${errorDetails}\nStack: ${error.stack}`
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// Log detailed error to stderr for debugging
|
|
623
|
+
console.error(`[i18n-magic MCP] Error getting translation key:`)
|
|
624
|
+
console.error(errorDetails)
|
|
625
|
+
|
|
626
|
+
return {
|
|
627
|
+
content: [
|
|
628
|
+
{
|
|
629
|
+
type: "text",
|
|
630
|
+
text: JSON.stringify(
|
|
631
|
+
{
|
|
632
|
+
success: false,
|
|
633
|
+
error: errorMessage,
|
|
634
|
+
details: errorDetails,
|
|
635
|
+
},
|
|
636
|
+
null,
|
|
637
|
+
2,
|
|
638
|
+
),
|
|
639
|
+
},
|
|
640
|
+
],
|
|
641
|
+
isError: true,
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
if (request.params.name === "update_translation_key") {
|
|
647
|
+
try {
|
|
648
|
+
// Validate parameters
|
|
649
|
+
const params = UpdateTranslationKeySchema.parse(
|
|
650
|
+
request.params.arguments,
|
|
651
|
+
)
|
|
652
|
+
|
|
653
|
+
// Ensure config is loaded
|
|
654
|
+
const config = await this.ensureConfig()
|
|
655
|
+
|
|
656
|
+
// Suppress console.log to prevent interference with MCP JSON protocol
|
|
657
|
+
const originalConsoleLog = console.log
|
|
658
|
+
const logMessages: string[] = []
|
|
659
|
+
console.log = (...args: any[]) => {
|
|
660
|
+
const message = args.map(arg =>
|
|
661
|
+
typeof arg === 'object' ? JSON.stringify(arg) : String(arg)
|
|
662
|
+
).join(' ')
|
|
663
|
+
logMessages.push(message)
|
|
664
|
+
console.error(`[i18n-magic] ${message}`)
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
try {
|
|
668
|
+
// Find which namespaces contain this key
|
|
669
|
+
const targetNamespaces: string[] = []
|
|
670
|
+
|
|
671
|
+
if (params.namespace) {
|
|
672
|
+
// Check if key exists in specified namespace
|
|
673
|
+
const keys = await loadLocalesFile(config.loadPath, "en", params.namespace)
|
|
674
|
+
if (keys[params.key]) {
|
|
675
|
+
targetNamespaces.push(params.namespace)
|
|
676
|
+
} else {
|
|
677
|
+
throw new Error(`Key "${params.key}" does not exist in namespace "${params.namespace}"`)
|
|
678
|
+
}
|
|
679
|
+
} else {
|
|
680
|
+
// Find all namespaces where this key exists
|
|
681
|
+
for (const namespace of config.namespaces) {
|
|
682
|
+
try {
|
|
683
|
+
const keys = await loadLocalesFile(config.loadPath, "en", namespace)
|
|
684
|
+
if (keys[params.key]) {
|
|
685
|
+
targetNamespaces.push(namespace)
|
|
686
|
+
}
|
|
687
|
+
} catch (error) {
|
|
688
|
+
// Namespace file doesn't exist, continue
|
|
689
|
+
}
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
if (targetNamespaces.length === 0) {
|
|
693
|
+
throw new Error(`Key "${params.key}" does not exist in any namespace. Use add_translation_key to create it.`)
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Build translation cache with new value
|
|
698
|
+
const inputLanguage = params.language || "en"
|
|
699
|
+
const translationCache: Record<string, string> = {
|
|
700
|
+
[inputLanguage]: params.value,
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// Translate to all other locales
|
|
704
|
+
const otherLocales = config.locales.filter((l) => l !== inputLanguage)
|
|
705
|
+
if (otherLocales.length > 0 && config.openai) {
|
|
706
|
+
const { translateKey } = await import("./lib/utils.js")
|
|
707
|
+
|
|
708
|
+
await Promise.all(
|
|
709
|
+
otherLocales.map(async (locale) => {
|
|
710
|
+
const translation = await translateKey({
|
|
711
|
+
context: config.context || "",
|
|
712
|
+
inputLanguage: inputLanguage,
|
|
713
|
+
outputLanguage: locale,
|
|
714
|
+
object: {
|
|
715
|
+
[params.key]: params.value,
|
|
716
|
+
},
|
|
717
|
+
openai: config.openai!,
|
|
718
|
+
model: config.model,
|
|
719
|
+
})
|
|
720
|
+
translationCache[locale] = translation[params.key]
|
|
721
|
+
})
|
|
722
|
+
)
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// Update the key in all relevant namespaces and locales
|
|
726
|
+
await Promise.all(
|
|
727
|
+
targetNamespaces.flatMap((namespace) =>
|
|
728
|
+
config.locales.map(async (locale) => {
|
|
729
|
+
const newValue = translationCache[locale] || params.value
|
|
730
|
+
const existingKeys = await loadLocalesFile(config.loadPath, locale, namespace)
|
|
731
|
+
existingKeys[params.key] = newValue
|
|
732
|
+
const { writeLocalesFile } = await import("./lib/utils.js")
|
|
733
|
+
await writeLocalesFile(config.savePath, locale, namespace, existingKeys)
|
|
734
|
+
})
|
|
735
|
+
)
|
|
736
|
+
)
|
|
737
|
+
|
|
738
|
+
return {
|
|
739
|
+
content: [
|
|
740
|
+
{
|
|
741
|
+
type: "text",
|
|
742
|
+
text: JSON.stringify(
|
|
743
|
+
{
|
|
744
|
+
success: true,
|
|
745
|
+
message: `Successfully updated translation key "${params.key}" in ${targetNamespaces.length} namespace(s) and ${config.locales.length} locale(s)`,
|
|
746
|
+
key: params.key,
|
|
747
|
+
newValue: params.value,
|
|
748
|
+
providedLanguage: params.language || "en",
|
|
749
|
+
namespaces: targetNamespaces,
|
|
750
|
+
locales: config.locales,
|
|
751
|
+
diagnostics: logMessages.join('\n'),
|
|
752
|
+
},
|
|
753
|
+
null,
|
|
754
|
+
2,
|
|
755
|
+
),
|
|
756
|
+
},
|
|
757
|
+
],
|
|
758
|
+
}
|
|
759
|
+
} finally {
|
|
760
|
+
// Restore console.log
|
|
761
|
+
console.log = originalConsoleLog
|
|
762
|
+
}
|
|
763
|
+
} catch (error) {
|
|
764
|
+
const errorMessage =
|
|
765
|
+
error instanceof Error ? error.message : "Unknown error occurred"
|
|
766
|
+
|
|
767
|
+
let errorDetails = errorMessage
|
|
768
|
+
if (error instanceof Error && error.stack) {
|
|
769
|
+
errorDetails = `${errorDetails}\nStack: ${error.stack}`
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
console.error(`[i18n-magic MCP] Error updating translation key:`)
|
|
773
|
+
console.error(errorDetails)
|
|
774
|
+
|
|
775
|
+
return {
|
|
776
|
+
content: [
|
|
777
|
+
{
|
|
778
|
+
type: "text",
|
|
779
|
+
text: JSON.stringify(
|
|
780
|
+
{
|
|
781
|
+
success: false,
|
|
782
|
+
error: errorMessage,
|
|
783
|
+
details: errorDetails,
|
|
784
|
+
},
|
|
785
|
+
null,
|
|
786
|
+
2,
|
|
787
|
+
),
|
|
788
|
+
},
|
|
789
|
+
],
|
|
790
|
+
isError: true,
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
if (request.params.name === "search_translations") {
|
|
796
|
+
try {
|
|
797
|
+
// Validate parameters
|
|
798
|
+
const params = SearchTranslationsSchema.parse(
|
|
799
|
+
request.params.arguments,
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
// Ensure config is loaded
|
|
803
|
+
const config = await this.ensureConfig()
|
|
804
|
+
|
|
805
|
+
// Suppress console.log
|
|
806
|
+
const originalConsoleLog = console.log
|
|
807
|
+
console.log = () => {}
|
|
808
|
+
|
|
809
|
+
try {
|
|
810
|
+
const searchQuery = params.query.toLowerCase()
|
|
811
|
+
const results: Array<{
|
|
812
|
+
key: string
|
|
813
|
+
value: string
|
|
814
|
+
namespace: string
|
|
815
|
+
matchType: "key" | "value" | "both"
|
|
816
|
+
}> = []
|
|
817
|
+
|
|
818
|
+
// Search through all namespaces
|
|
819
|
+
for (const namespace of config.namespaces) {
|
|
820
|
+
try {
|
|
821
|
+
const keys = await loadLocalesFile(config.loadPath, "en", namespace)
|
|
822
|
+
|
|
823
|
+
for (const [key, value] of Object.entries(keys)) {
|
|
824
|
+
const keyLower = key.toLowerCase()
|
|
825
|
+
const valueLower = value.toLowerCase()
|
|
826
|
+
|
|
827
|
+
// Fuzzy matching: check if search query is contained in key or value
|
|
828
|
+
const keyMatch = keyLower.includes(searchQuery)
|
|
829
|
+
const valueMatch = valueLower.includes(searchQuery)
|
|
830
|
+
|
|
831
|
+
if (keyMatch || valueMatch) {
|
|
832
|
+
results.push({
|
|
833
|
+
key,
|
|
834
|
+
value,
|
|
835
|
+
namespace,
|
|
836
|
+
matchType: keyMatch && valueMatch ? "both" : keyMatch ? "key" : "value",
|
|
837
|
+
})
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
} catch (error) {
|
|
841
|
+
// Namespace file doesn't exist or has issues, continue
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
|
|
845
|
+
// Sort results: exact matches first, then by match type, then alphabetically
|
|
846
|
+
results.sort((a, b) => {
|
|
847
|
+
// Prioritize exact key matches
|
|
848
|
+
const aExactKey = a.key.toLowerCase() === searchQuery
|
|
849
|
+
const bExactKey = b.key.toLowerCase() === searchQuery
|
|
850
|
+
if (aExactKey && !bExactKey) return -1
|
|
851
|
+
if (!aExactKey && bExactKey) return 1
|
|
852
|
+
|
|
853
|
+
// Prioritize exact value matches
|
|
854
|
+
const aExactValue = a.value.toLowerCase() === searchQuery
|
|
855
|
+
const bExactValue = b.value.toLowerCase() === searchQuery
|
|
856
|
+
if (aExactValue && !bExactValue) return -1
|
|
857
|
+
if (!aExactValue && bExactValue) return 1
|
|
858
|
+
|
|
859
|
+
// Then by match type (both > key > value)
|
|
860
|
+
const matchOrder = { both: 0, key: 1, value: 2 }
|
|
861
|
+
const matchCompare = matchOrder[a.matchType] - matchOrder[b.matchType]
|
|
862
|
+
if (matchCompare !== 0) return matchCompare
|
|
863
|
+
|
|
864
|
+
// Finally alphabetically by key
|
|
865
|
+
return a.key.localeCompare(b.key)
|
|
866
|
+
})
|
|
867
|
+
|
|
868
|
+
// Limit results to prevent overwhelming output
|
|
869
|
+
const maxResults = 50
|
|
870
|
+
const limitedResults = results.slice(0, maxResults)
|
|
871
|
+
const hasMore = results.length > maxResults
|
|
872
|
+
|
|
873
|
+
return {
|
|
874
|
+
content: [
|
|
875
|
+
{
|
|
876
|
+
type: "text",
|
|
877
|
+
text: JSON.stringify(
|
|
878
|
+
{
|
|
879
|
+
success: true,
|
|
880
|
+
message: results.length === 0
|
|
881
|
+
? `No translations found matching "${params.query}"`
|
|
882
|
+
: `Found ${results.length} translation${results.length === 1 ? "" : "s"} matching "${params.query}"${hasMore ? ` (showing first ${maxResults})` : ""}`,
|
|
883
|
+
query: params.query,
|
|
884
|
+
totalResults: results.length,
|
|
885
|
+
results: limitedResults,
|
|
886
|
+
hasMore,
|
|
887
|
+
tip: "Each result shows the translation key, English value, namespace, and what matched (key, value, or both). Use these keys directly in your code or use get_translation_key for more details.",
|
|
888
|
+
},
|
|
889
|
+
null,
|
|
890
|
+
2,
|
|
891
|
+
),
|
|
892
|
+
},
|
|
893
|
+
],
|
|
894
|
+
}
|
|
895
|
+
} finally {
|
|
896
|
+
// Restore console.log
|
|
897
|
+
console.log = originalConsoleLog
|
|
898
|
+
}
|
|
899
|
+
} catch (error) {
|
|
900
|
+
const errorMessage =
|
|
901
|
+
error instanceof Error ? error.message : "Unknown error occurred"
|
|
902
|
+
|
|
903
|
+
let errorDetails = errorMessage
|
|
904
|
+
if (error instanceof Error && error.stack) {
|
|
905
|
+
errorDetails = `${errorDetails}\nStack: ${error.stack}`
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
console.error(`[i18n-magic MCP] Error searching translations:`)
|
|
909
|
+
console.error(errorDetails)
|
|
910
|
+
|
|
911
|
+
return {
|
|
912
|
+
content: [
|
|
913
|
+
{
|
|
914
|
+
type: "text",
|
|
915
|
+
text: JSON.stringify(
|
|
916
|
+
{
|
|
917
|
+
success: false,
|
|
918
|
+
error: errorMessage,
|
|
919
|
+
details: errorDetails,
|
|
920
|
+
},
|
|
921
|
+
null,
|
|
922
|
+
2,
|
|
923
|
+
),
|
|
924
|
+
},
|
|
925
|
+
],
|
|
926
|
+
isError: true,
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
|
|
399
931
|
throw new Error(`Unknown tool: ${request.params.name}`)
|
|
400
932
|
})
|
|
401
933
|
}
|