@scoutello/i18n-magic 0.65.0 → 0.67.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.
Files changed (59) hide show
  1. package/README.md +16 -10
  2. package/dist/cli.js +20 -15
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/check-missing.d.ts +11 -1
  5. package/dist/commands/check-missing.d.ts.map +1 -1
  6. package/dist/commands/check-missing.js +56 -96
  7. package/dist/commands/check-missing.js.map +1 -1
  8. package/dist/commands/replace.d.ts +6 -0
  9. package/dist/commands/replace.d.ts.map +1 -1
  10. package/dist/commands/replace.js +48 -29
  11. package/dist/commands/replace.js.map +1 -1
  12. package/dist/commands/scan.d.ts.map +1 -1
  13. package/dist/commands/scan.js +114 -80
  14. package/dist/commands/scan.js.map +1 -1
  15. package/dist/commands/sync-locales.d.ts.map +1 -1
  16. package/dist/commands/sync-locales.js +21 -28
  17. package/dist/commands/sync-locales.js.map +1 -1
  18. package/dist/index.d.ts +2 -1
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +1 -1
  21. package/dist/index.js.map +1 -1
  22. package/dist/lib/locale-storage.d.ts +7 -0
  23. package/dist/lib/locale-storage.d.ts.map +1 -0
  24. package/dist/lib/locale-storage.js +49 -0
  25. package/dist/lib/locale-storage.js.map +1 -0
  26. package/dist/lib/mcp-write-schemas.d.ts +56 -0
  27. package/dist/lib/mcp-write-schemas.d.ts.map +1 -0
  28. package/dist/lib/mcp-write-schemas.js +36 -0
  29. package/dist/lib/mcp-write-schemas.js.map +1 -0
  30. package/dist/lib/translation-response.d.ts +8 -0
  31. package/dist/lib/translation-response.d.ts.map +1 -0
  32. package/dist/lib/translation-response.js +43 -0
  33. package/dist/lib/translation-response.js.map +1 -0
  34. package/dist/lib/types.d.ts +1 -1
  35. package/dist/lib/types.d.ts.map +1 -1
  36. package/dist/lib/update-translation-key.d.ts +16 -0
  37. package/dist/lib/update-translation-key.d.ts.map +1 -0
  38. package/dist/lib/update-translation-key.js +67 -0
  39. package/dist/lib/update-translation-key.js.map +1 -0
  40. package/dist/lib/utils.d.ts +2 -0
  41. package/dist/lib/utils.d.ts.map +1 -1
  42. package/dist/lib/utils.js +69 -51
  43. package/dist/lib/utils.js.map +1 -1
  44. package/dist/mcp-server.js +30 -117
  45. package/dist/mcp-server.js.map +1 -1
  46. package/package.json +5 -9
  47. package/src/cli.ts +26 -18
  48. package/src/commands/check-missing.ts +83 -137
  49. package/src/commands/replace.ts +81 -43
  50. package/src/commands/scan.ts +130 -108
  51. package/src/commands/sync-locales.ts +41 -63
  52. package/src/index.ts +6 -1
  53. package/src/lib/locale-storage.ts +76 -0
  54. package/src/lib/mcp-write-schemas.ts +46 -0
  55. package/src/lib/translation-response.ts +59 -0
  56. package/src/lib/types.ts +1 -1
  57. package/src/lib/update-translation-key.ts +119 -0
  58. package/src/lib/utils.ts +92 -76
  59. package/src/mcp-server.ts +38 -167
package/src/mcp-server.ts CHANGED
@@ -9,7 +9,14 @@ import OpenAI from "openai"
9
9
  import path from "path"
10
10
  import { fileURLToPath } from "url"
11
11
  import { z } from "zod"
12
+ import {
13
+ AddTranslationKeySchema,
14
+ AddTranslationKeysSchema,
15
+ MCP_WRITE_REQUIRED_FIELDS,
16
+ UpdateTranslationKeySchema,
17
+ } from "./lib/mcp-write-schemas.js"
12
18
  import type { Configuration } from "./lib/types.js"
19
+ import { updateTranslationKeyOperation } from "./lib/update-translation-key.js"
13
20
  import {
14
21
  addTranslationKey,
15
22
  addTranslationKeys,
@@ -150,46 +157,6 @@ function resolveProjectRoot(): string {
150
157
  return cwd
151
158
  }
152
159
 
153
- // Zod schema for the add_translation_key tool parameters
154
- const AddTranslationKeySchema = z.object({
155
- key: z
156
- .string()
157
- .describe(
158
- 'The translation key to add (e.g., "welcomeMessage").',
159
- ),
160
- value: z.string().describe("The text value for this translation key"),
161
- language: z
162
- .string()
163
- .optional()
164
- .describe(
165
- 'The language code of the provided value (e.g., "en", "de", "fr"). Defaults to "en" (English) if not specified.',
166
- ),
167
- })
168
-
169
- // Zod schema for the add_translation_keys (batch) tool parameters
170
- const AddTranslationKeysSchema = z.object({
171
- keys: z
172
- .array(
173
- z.object({
174
- key: z
175
- .string()
176
- .describe(
177
- 'The translation key to add (e.g., "welcomeMessage").',
178
- ),
179
- value: z.string().describe("The text value for this translation key"),
180
- language: z
181
- .string()
182
- .optional()
183
- .describe(
184
- 'The language code of the provided value (e.g., "en", "de", "fr"). Defaults to "en" (English) if not specified.',
185
- ),
186
- }),
187
- )
188
- .describe(
189
- "Array of translation keys to add in batch. Use this for adding multiple keys at once for better performance.",
190
- ),
191
- })
192
-
193
160
  // Zod schema for the list_untranslated_keys tool parameters
194
161
  const ListUntranslatedKeysSchema = z.object({})
195
162
 
@@ -200,20 +167,6 @@ const GetTranslationKeySchema = z.object({
200
167
  .describe('The translation key to retrieve (e.g., "welcomeMessage")'),
201
168
  })
202
169
 
203
- // Zod schema for the update_translation_key tool parameters
204
- const UpdateTranslationKeySchema = z.object({
205
- key: z
206
- .string()
207
- .describe('The translation key to update (e.g., "welcomeMessage")'),
208
- value: z.string().describe("The new text value for this translation key"),
209
- language: z
210
- .string()
211
- .optional()
212
- .describe(
213
- 'The language code of the provided value (e.g., "en", "de", "fr"). Defaults to "en" (English) if not specified.',
214
- ),
215
- })
216
-
217
170
  // Zod schema for the search_translations tool parameters
218
171
  const SearchTranslationsSchema = z.object({
219
172
  query: z
@@ -299,7 +252,7 @@ class I18nMagicServer {
299
252
  {
300
253
  name: "add_translation_key",
301
254
  description:
302
- "Add a new translation key with a text value. The destination translation file is resolved automatically from project configuration and code usage. You can optionally specify the language of the value you're providing (defaults to English). For adding multiple keys at once, use add_translation_keys instead for better performance. NOTE: This tool can only ADD keys, it will NEVER remove any existing keys.",
255
+ "Add a new translation key with a text value and its explicit source language. The destination translation file is resolved automatically from project configuration and code usage. For adding multiple keys at once, use add_translation_keys instead for better performance. NOTE: This tool can only ADD keys, it will NEVER remove any existing keys.",
303
256
  inputSchema: {
304
257
  type: "object",
305
258
  properties: {
@@ -315,10 +268,10 @@ class I18nMagicServer {
315
268
  language: {
316
269
  type: "string",
317
270
  description:
318
- 'The language code of the provided value (e.g., "en" for English, "de" for German, "fr" for French). Defaults to "en" if not specified.',
271
+ "Required configured language code of the provided value (for example, en or de).",
319
272
  },
320
273
  },
321
- required: ["key", "value"],
274
+ required: [...MCP_WRITE_REQUIRED_FIELDS],
322
275
  },
323
276
  },
324
277
  {
@@ -346,10 +299,10 @@ class I18nMagicServer {
346
299
  language: {
347
300
  type: "string",
348
301
  description:
349
- 'The language code of the provided value (e.g., "en" for English, "de" for German, "fr" for French). Defaults to "en" if not specified.',
302
+ "Required configured language code of the provided value (for example, en or de).",
350
303
  },
351
304
  },
352
- required: ["key", "value"],
305
+ required: [...MCP_WRITE_REQUIRED_FIELDS],
353
306
  },
354
307
  },
355
308
  },
@@ -385,7 +338,7 @@ class I18nMagicServer {
385
338
  {
386
339
  name: "update_translation_key",
387
340
  description:
388
- "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) everywhere it 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. NOTE: This tool can only UPDATE existing keys, it will NEVER remove any keys.",
341
+ "Update an existing translation key with a new text value and its explicit source language. With a provider, every locale is translated before writes begin. Without a provider, only the supplied locale is updated and other locales are returned as pending. NOTE: This tool can only UPDATE existing keys, it will NEVER remove any keys.",
389
342
  inputSchema: {
390
343
  type: "object",
391
344
  properties: {
@@ -401,10 +354,10 @@ class I18nMagicServer {
401
354
  language: {
402
355
  type: "string",
403
356
  description:
404
- 'The language code of the provided value (e.g., "en" for English, "de" for German, "fr" for French). Defaults to "en" if not specified.',
357
+ "Required configured language code of the provided value (for example, en or de).",
405
358
  },
406
359
  },
407
- required: ["key", "value"],
360
+ required: [...MCP_WRITE_REQUIRED_FIELDS],
408
361
  },
409
362
  },
410
363
  {
@@ -448,7 +401,7 @@ class I18nMagicServer {
448
401
  return await addTranslationKey({
449
402
  key: params.key,
450
403
  value: params.value,
451
- language: params.language || "en",
404
+ language: params.language,
452
405
  config,
453
406
  })
454
407
  })
@@ -467,7 +420,7 @@ class I18nMagicServer {
467
420
  message: `Successfully added translation key "${result.key}"`,
468
421
  key: result.key,
469
422
  value: result.value,
470
- providedLanguage: params.language || "en",
423
+ providedLanguage: result.providedLanguage,
471
424
  locales: result.locale,
472
425
  nextStep: result.locale.includes(",")
473
426
  ? "Key was automatically translated to multiple locales"
@@ -564,7 +517,7 @@ class I18nMagicServer {
564
517
  keys: params.keys.map((k) => ({
565
518
  key: k.key,
566
519
  value: k.value,
567
- language: k.language || "en",
520
+ language: k.language,
568
521
  })),
569
522
  config,
570
523
  })
@@ -691,7 +644,7 @@ class I18nMagicServer {
691
644
  nextSteps:
692
645
  missingKeys.length > 0
693
646
  ? [
694
- "Use add_translation_key to add these keys with English values",
647
+ "Use add_translation_key with each value and its explicit source language (for example, language: \"en\")",
695
648
  "Or run 'i18n-magic scan' to add them interactively",
696
649
  ]
697
650
  : [],
@@ -892,102 +845,14 @@ class I18nMagicServer {
892
845
  console.log = () => {}
893
846
 
894
847
  try {
895
- // Use mutex to prevent concurrent writes
896
- await translationMutex.withLock(async () => {
897
- // Find which namespaces contain this key
898
- const targetNamespaces: string[] = []
899
-
900
- // Find all namespaces where this key exists
901
- for (const namespace of config.namespaces) {
902
- try {
903
- const keys = await loadLocalesFile(
904
- config.loadPath,
905
- "en",
906
- namespace,
907
- )
908
- if (Object.hasOwn(keys, params.key)) {
909
- targetNamespaces.push(namespace)
910
- }
911
- } catch (error) {
912
- // Namespace file doesn't exist, continue
913
- }
914
- }
915
-
916
- if (targetNamespaces.length === 0) {
917
- throw new Error(
918
- `Key "${params.key}" does not exist in translation files. Use add_translation_key to create it.`,
919
- )
920
- }
921
-
922
- // Build translation cache with new value
923
- const inputLanguage = params.language || "en"
924
- const translationCache: Record<string, string> = {
925
- [inputLanguage]: params.value,
926
- }
927
-
928
- // Translate to all other locales
929
- const otherLocales = config.locales.filter(
930
- (l) => l !== inputLanguage,
931
- )
932
- if (otherLocales.length > 0 && config.openai) {
933
- const { translateKey } = await import("./lib/utils.js")
934
-
935
- await Promise.all(
936
- otherLocales.map(async (locale) => {
937
- const translation = await translateKey({
938
- context: config.context || "",
939
- inputLanguage: inputLanguage,
940
- outputLanguage: locale,
941
- object: {
942
- [params.key]: params.value,
943
- },
944
- openai: config.openai!,
945
- model: config.model,
946
- })
947
- translationCache[locale] = translation[params.key]
948
- }),
949
- )
950
- }
951
-
952
- // Update the key in all relevant namespaces and locales
953
- // Process sequentially to avoid race conditions
954
- const { writeLocalesFile } = await import("./lib/utils.js")
955
-
956
- for (const namespace of targetNamespaces) {
957
- for (const locale of config.locales) {
958
- const newValue = translationCache[locale] || params.value
959
-
960
- // Load existing keys
961
- const existingKeys = await loadLocalesFile(
962
- config.loadPath,
963
- locale,
964
- namespace,
965
- )
966
-
967
- const originalKeyCount = Object.keys(existingKeys).length
968
-
969
- // Update the specific key
970
- existingKeys[params.key] = newValue
971
-
972
- const newKeyCount = Object.keys(existingKeys).length
973
-
974
- // Safety check: we should only be updating, not removing
975
- // Key count should stay same (update) or increase by 1 (add)
976
- if (newKeyCount < originalKeyCount) {
977
- throw new Error(
978
- `Safety check failed: Updating locale "${locale}" would reduce keys from ${originalKeyCount} to ${newKeyCount}. Aborting.`,
979
- )
980
- }
981
-
982
- await writeLocalesFile(
983
- config.savePath,
984
- locale,
985
- namespace,
986
- existingKeys,
987
- )
988
- }
989
- }
990
- })
848
+ const result = await translationMutex.withLock(async () =>
849
+ updateTranslationKeyOperation({
850
+ config,
851
+ key: params.key,
852
+ value: params.value,
853
+ language: params.language,
854
+ }),
855
+ )
991
856
 
992
857
  return {
993
858
  content: [
@@ -996,11 +861,17 @@ class I18nMagicServer {
996
861
  text: JSON.stringify(
997
862
  {
998
863
  success: true,
999
- message: `Successfully updated translation key "${params.key}" in ${config.locales.length} locale(s)`,
1000
- key: params.key,
1001
- newValue: params.value,
1002
- providedLanguage: params.language || "en",
1003
- locales: config.locales,
864
+ message: `Successfully updated translation key "${result.key}" in ${result.updatedLocales.length} locale(s)`,
865
+ key: result.key,
866
+ newValue: result.value,
867
+ providedLanguage: result.sourceLocale,
868
+ updatedLocales: result.updatedLocales,
869
+ pendingLocales: result.pendingLocales,
870
+ locales: result.updatedLocales,
871
+ nextStep:
872
+ result.pendingLocales.length > 0
873
+ ? "Run 'i18n-magic sync' with a configured provider to translate pending locales."
874
+ : "All configured locales were updated.",
1004
875
  },
1005
876
  null,
1006
877
  2,