@scoutello/i18n-magic 0.59.1 → 0.61.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 CHANGED
@@ -30,11 +30,12 @@ Stop context switching. Let AI handle your translations while you stay in your c
30
30
  ## CLI Commands
31
31
 
32
32
  ```bash
33
- npx @scoutello/i18n-magic scan # Find & add missing translations
34
- npx @scoutello/i18n-magic sync # Translate to all languages
35
- npx @scoutello/i18n-magic replace [key] # Update existing translation
36
- npx @scoutello/i18n-magic clean # Remove unused keys
37
- npx @scoutello/i18n-magic check-missing # CI/CD validation
33
+ npx @scoutello/i18n-magic scan # Find & add missing translations
34
+ npx @scoutello/i18n-magic sync # Translate to all languages
35
+ npx @scoutello/i18n-magic replace [key] # Update existing translation
36
+ npx @scoutello/i18n-magic remove-key [key] # Remove a specific key
37
+ npx @scoutello/i18n-magic clean # Remove unused keys
38
+ npx @scoutello/i18n-magic check-missing # CI/CD validation
38
39
  ```
39
40
 
40
41
  **`scan`** - Scans your codebase for missing keys, prompts you for values, auto-translates to all locales.
@@ -43,6 +44,8 @@ npx @scoutello/i18n-magic check-missing # CI/CD validation
43
44
 
44
45
  **`replace`** - Update an existing translation key across all locales. Detects which namespaces use the key automatically.
45
46
 
47
+ **`remove-key`** - Remove a specific translation key from all namespaces and locales. Useful when deprecating keys.
48
+
46
49
  **`clean`** - Removes unused translation keys from all locales. Great for keeping files lean.
47
50
 
48
51
  **`check-missing`** - Dry-run check. Exits with error code if translations are missing. Perfect for CI pipelines.
@@ -66,21 +69,24 @@ Result: Separate files per feature (`common.json`, `dashboard.json`, `mobile.jso
66
69
 
67
70
  **MCP (Model Context Protocol)** = API for AI agents.
68
71
 
69
- Install the i18n-magic MCP server in Cursor, and your AI gets 5 new tools:
72
+ Install the i18n-magic MCP server in Cursor, and your AI gets 6 tools:
70
73
 
71
74
  ### 1. `search_translations` - Prevent Duplicates
72
75
  Fuzzy search across all translations. AI searches before adding anything.
73
76
 
74
77
  ### 2. `add_translation_key` - Add New Keys
75
- Adds key to English (`en`) locale. Run `sync` afterward to translate to other languages.
78
+ Adds a single key. If API translation is configured, it auto-translates to other locales; otherwise run `sync`.
79
+
80
+ ### 3. `add_translation_keys` - Add Multiple Keys Fast
81
+ Batch add 2+ keys in one call with better performance than multiple single-key calls.
76
82
 
77
- ### 3. `get_translation_key` - Check What Exists
83
+ ### 4. `get_translation_key` - Check What Exists
78
84
  Retrieve current value for any key.
79
85
 
80
- ### 4. `update_translation_key` - Fix & Auto-Translate
86
+ ### 5. `update_translation_key` - Fix & Auto-Translate
81
87
  Update a key and **instantly translate to all languages**. No sync needed!
82
88
 
83
- ### 5. `list_untranslated_keys` - Batch Check
89
+ ### 6. `list_untranslated_keys` - Batch Check
84
90
  Show all missing keys across your codebase.
85
91
 
86
92
  ---
@@ -160,9 +166,9 @@ Done! Test by asking: *"Search for translations with 'password'"*
160
166
  **Result**: No duplicate keys, instant code.
161
167
 
162
168
  If nothing exists:
163
- - AI adds key to English: `add_translation_key`
164
- - You run: `npx @scoutello/i18n-magic sync`
165
- - Translated to all languages!
169
+ - AI adds key(s): `add_translation_key` or `add_translation_keys`
170
+ - If API translation is configured, other locales are updated automatically
171
+ - If not configured, run: `npx @scoutello/i18n-magic sync`
166
172
 
167
173
  ### Pattern 2: Updating Translations
168
174
 
@@ -329,7 +335,7 @@ ls -la i18n-magic.js
329
335
  npx @scoutello/i18n-magic sync
330
336
  ```
331
337
 
332
- `add_translation_key` only adds English. `sync` translates to other languages.
338
+ `add_translation_key`/`add_translation_keys` translate automatically when API translation is configured; otherwise run `sync`.
333
339
 
334
340
  ### MCP Tools Not Appearing
335
341
 
package/dist/cli.js CHANGED
@@ -4,6 +4,7 @@ import dotenv from "dotenv";
4
4
  import OpenAI from "openai";
5
5
  import { checkMissing } from "./commands/check-missing.js";
6
6
  import { removeUnusedKeys } from "./commands/clean.js";
7
+ import { removeKey } from "./commands/remove-key.js";
7
8
  import { replaceTranslation } from "./commands/replace.js";
8
9
  import { restoreFromNamespaces } from "./commands/restore-from-namespaces.js";
9
10
  import { translateMissing } from "./commands/scan.js";
@@ -47,18 +48,29 @@ const commands = [
47
48
  description: "Restore missing keys by searching for them in other namespace files across all locales.",
48
49
  action: restoreFromNamespaces,
49
50
  },
51
+ {
52
+ name: "remove-key",
53
+ description: "Remove a specific translation key from all namespaces and locales.",
54
+ action: removeKey,
55
+ },
50
56
  ];
51
57
  for (const command of commands) {
52
58
  const cmd = program.command(command.name).description(command.description);
53
- // Add key option to replace command
59
+ // Add key option to replace and remove-key commands
54
60
  if (command.name === "replace") {
55
61
  cmd
56
62
  .option("-k, --key <key>", "translation key to replace")
57
63
  .allowExcessArguments(true)
58
64
  .argument("[key]", "translation key to replace");
59
65
  }
66
+ if (command.name === "remove-key") {
67
+ cmd
68
+ .option("-k, --key <key>", "translation key to remove")
69
+ .allowExcessArguments(true)
70
+ .argument("[key]", "translation key to remove");
71
+ }
60
72
  cmd.action(async (arg, options) => {
61
- const res = dotenv.config({
73
+ dotenv.config({
62
74
  path: program.opts().env || ".env",
63
75
  });
64
76
  const config = await loadConfig({
@@ -81,8 +93,8 @@ for (const command of commands) {
81
93
  baseURL: "https://generativelanguage.googleapis.com/v1beta/openai/",
82
94
  }),
83
95
  });
84
- // For replace command, check for key in argument or option
85
- if (command.name === "replace") {
96
+ // For replace and remove-key commands, check for key in argument or option
97
+ if (command.name === "replace" || command.name === "remove-key") {
86
98
  // If key is provided as positional argument, use that first
87
99
  const keyToUse = typeof arg === "string" ? arg : options.key;
88
100
  command.action({ ...config, openai }, keyToUse);
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { Configuration } from "../lib/types.js";
2
+ export declare const removeKey: (config: Configuration, key?: string) => Promise<void>;
3
+ //# sourceMappingURL=remove-key.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-key.d.ts","sourceRoot":"","sources":["../../src/commands/remove-key.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAA;AAGpD,eAAO,MAAM,SAAS,GAAU,QAAQ,aAAa,EAAE,MAAM,MAAM,kBAsHlE,CAAA"}
@@ -0,0 +1,93 @@
1
+ import prompts from "prompts";
2
+ import { loadLocalesFile, writeLocalesFile } from "../lib/utils.js";
3
+ export const removeKey = async (config, key) => {
4
+ const { namespaces, locales, loadPath, savePath } = config;
5
+ if (!key) {
6
+ const response = await prompts({
7
+ type: "text",
8
+ name: "key",
9
+ message: "Enter the translation key to remove:",
10
+ validate: (value) => (value ? true : "Key cannot be empty"),
11
+ onState: (state) => {
12
+ if (state.aborted) {
13
+ process.nextTick(() => {
14
+ process.exit(0);
15
+ });
16
+ }
17
+ },
18
+ });
19
+ key = response.key;
20
+ }
21
+ if (!key) {
22
+ console.log("\n❌ Operation cancelled. No key was provided.");
23
+ return;
24
+ }
25
+ console.log(`\n🔍 Searching for key "${key}" across all namespaces and locales...`);
26
+ const foundIn = [];
27
+ for (const namespace of namespaces) {
28
+ for (const locale of locales) {
29
+ try {
30
+ const existingKeys = await loadLocalesFile(loadPath, locale, namespace, {
31
+ silent: true,
32
+ });
33
+ if (Object.hasOwn(existingKeys, key)) {
34
+ foundIn.push({ namespace, locale });
35
+ }
36
+ }
37
+ catch {
38
+ // Skip if file doesn't exist or can't be read
39
+ }
40
+ }
41
+ }
42
+ if (foundIn.length === 0) {
43
+ console.log(`\n❌ Key "${key}" not found in any namespace or locale.`);
44
+ return;
45
+ }
46
+ console.log(`\n⚠️ Key "${key}" found in ${foundIn.length} location(s):\n`);
47
+ const maxToShow = 20;
48
+ const itemsToShow = foundIn.slice(0, maxToShow);
49
+ for (const { namespace, locale } of itemsToShow) {
50
+ console.log(` • ${locale}:${namespace}`);
51
+ }
52
+ if (foundIn.length > maxToShow) {
53
+ console.log(` ... and ${foundIn.length - maxToShow} more`);
54
+ }
55
+ console.log("");
56
+ const { confirmed } = await prompts({
57
+ type: "confirm",
58
+ name: "confirmed",
59
+ message: `Do you want to remove "${key}" from ${foundIn.length} location(s)?`,
60
+ initial: false,
61
+ onState: (state) => {
62
+ if (state.aborted) {
63
+ process.nextTick(() => {
64
+ process.exit(0);
65
+ });
66
+ }
67
+ },
68
+ });
69
+ if (!confirmed) {
70
+ console.log("\n❌ Operation cancelled. No keys were removed.");
71
+ return;
72
+ }
73
+ console.log(`\n🗑️ Removing key "${key}"...`);
74
+ let removedCount = 0;
75
+ for (const { namespace, locale } of foundIn) {
76
+ try {
77
+ const existingKeys = await loadLocalesFile(loadPath, locale, namespace, {
78
+ silent: true,
79
+ });
80
+ if (Object.hasOwn(existingKeys, key)) {
81
+ delete existingKeys[key];
82
+ await writeLocalesFile(savePath, locale, namespace, existingKeys);
83
+ removedCount++;
84
+ console.log(` ✓ Removed from ${locale}:${namespace}`);
85
+ }
86
+ }
87
+ catch (error) {
88
+ console.error(` ✗ Failed to remove from ${locale}:${namespace}: ${error instanceof Error ? error.message : String(error)}`);
89
+ }
90
+ }
91
+ console.log(`\n✅ Successfully removed key "${key}" from ${removedCount} location(s)`);
92
+ };
93
+ //# sourceMappingURL=remove-key.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remove-key.js","sourceRoot":"","sources":["../../src/commands/remove-key.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../../src/lib/utils.ts"],"names":[],"mappings":"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"}
package/dist/lib/utils.js CHANGED
@@ -590,43 +590,86 @@ export const addTranslationKeys = async ({ keys, config, }) => {
590
590
  const scanTime = performance.now() - scanStartTime;
591
591
  log(`⏱️ Codebase scan completed in ${scanTime.toFixed(2)}ms`);
592
592
  // Step 2: Determine namespaces for each key
593
- const keyToNamespaces = new Map();
594
- for (const { key, language = "en" } of keys) {
595
- const affectedNamespaces = [];
596
- // Find entries for this specific key
597
- const keyEntries = keysWithNamespaces.filter((entry) => entry.key === key || entry.key === `${defaultNamespace}:${key}`);
598
- // Collect unique namespaces where this key is used
599
- const foundNamespaces = new Set();
600
- for (const entry of keyEntries) {
601
- for (const ns of entry.namespaces) {
602
- foundNamespaces.add(ns);
603
- }
593
+ // Resolution order:
594
+ // 1) Explicit namespace prefix (e.g. "dashboard:welcome")
595
+ // 2) Code usage scan matches
596
+ // 3) Existing key in default locale namespace files
597
+ // 4) Key prefix matching a namespace (e.g. "dashboard.title")
598
+ // 5) Default namespace fallback
599
+ const defaultLocaleKeysByNamespace = new Map();
600
+ await Promise.all(namespaces.map(async (namespace) => {
601
+ try {
602
+ const nsKeys = await loadLocalesFile(loadPath, defaultLocale, namespace, {
603
+ silent: true,
604
+ });
605
+ defaultLocaleKeysByNamespace.set(namespace, nsKeys);
606
+ }
607
+ catch {
608
+ defaultLocaleKeysByNamespace.set(namespace, {});
604
609
  }
605
- if (foundNamespaces.size > 0) {
606
- affectedNamespaces.push(...Array.from(foundNamespaces));
610
+ }));
611
+ const preparedKeys = keys.map(({ key, value, language = "en" }) => {
612
+ const splitKey = key.split(":");
613
+ const hasExplicitNamespace = splitKey.length > 1 && namespaces.includes(splitKey[0]);
614
+ const normalizedKey = hasExplicitNamespace ? splitKey.slice(1).join(":") : key;
615
+ const explicitNamespace = hasExplicitNamespace ? splitKey[0] : null;
616
+ const foundNamespaces = new Set();
617
+ if (explicitNamespace) {
618
+ foundNamespaces.add(explicitNamespace);
607
619
  }
608
620
  else {
609
- // If the key is not found in the codebase, use the default namespace
610
- affectedNamespaces.push(defaultNamespace);
621
+ for (const entry of keysWithNamespaces) {
622
+ for (const namespace of entry.namespaces) {
623
+ const pureKey = getPureKey(entry.key, namespace, namespace === defaultNamespace);
624
+ if (entry.key === normalizedKey || pureKey === normalizedKey) {
625
+ foundNamespaces.add(namespace);
626
+ }
627
+ }
628
+ }
629
+ if (foundNamespaces.size === 0) {
630
+ for (const namespace of namespaces) {
631
+ const namespaceKeys = defaultLocaleKeysByNamespace.get(namespace) || {};
632
+ if (Object.hasOwn(namespaceKeys, normalizedKey)) {
633
+ foundNamespaces.add(namespace);
634
+ }
635
+ }
636
+ }
637
+ if (foundNamespaces.size === 0) {
638
+ const keyPrefix = normalizedKey.split(".")[0];
639
+ if (namespaces.includes(keyPrefix)) {
640
+ foundNamespaces.add(keyPrefix);
641
+ }
642
+ }
611
643
  }
612
- keyToNamespaces.set(key, new Set(affectedNamespaces));
613
- }
644
+ if (foundNamespaces.size === 0) {
645
+ foundNamespaces.add(defaultNamespace);
646
+ }
647
+ return {
648
+ key: normalizedKey,
649
+ value,
650
+ language,
651
+ namespaces: foundNamespaces,
652
+ };
653
+ });
614
654
  // Step 3: Group keys by namespace and locale
615
655
  const namespaceLocaleToKeys = new Map();
616
- for (const { key, value, language = "en" } of keys) {
617
- const namespaces = keyToNamespaces.get(key) || new Set([defaultNamespace]);
618
- for (const namespace of namespaces) {
619
- const mapKey = `${namespace}:${language}`;
656
+ for (const keyEntry of preparedKeys) {
657
+ for (const namespace of keyEntry.namespaces) {
658
+ const mapKey = `${namespace}:${keyEntry.language}`;
620
659
  if (!namespaceLocaleToKeys.has(mapKey)) {
621
660
  namespaceLocaleToKeys.set(mapKey, []);
622
661
  }
623
- namespaceLocaleToKeys.get(mapKey).push({ key, value, language });
662
+ namespaceLocaleToKeys.get(mapKey).push({
663
+ key: keyEntry.key,
664
+ value: keyEntry.value,
665
+ language: keyEntry.language,
666
+ });
624
667
  }
625
668
  }
626
669
  // Step 4: Collect all unique namespaces that will be affected
627
670
  const affectedNamespaces = new Set();
628
- for (const namespaces of keyToNamespaces.values()) {
629
- for (const ns of namespaces) {
671
+ for (const keyEntry of preparedKeys) {
672
+ for (const ns of keyEntry.namespaces) {
630
673
  affectedNamespaces.add(ns);
631
674
  }
632
675
  }
@@ -634,25 +677,27 @@ export const addTranslationKeys = async ({ keys, config, }) => {
634
677
  // This ensures we preserve existing keys in all locales
635
678
  const fileIOStartTime = performance.now();
636
679
  const localeFiles = new Map();
680
+ const originalKeyCounts = new Map();
637
681
  const loadErrors = [];
638
- // Load files for all locales × all affected namespaces - SEQUENTIALLY to avoid race conditions
682
+ // Load files for all locales × all affected namespaces in parallel (read-only)
683
+ const localeLoadPromises = [];
639
684
  for (const namespace of affectedNamespaces) {
640
685
  for (const locale of config.locales) {
641
686
  const fileKey = `${locale}:${namespace}`;
642
- if (!localeFiles.has(fileKey)) {
643
- try {
644
- const existingKeys = await loadLocalesFile(loadPath, locale, namespace, { silent: true });
645
- localeFiles.set(fileKey, existingKeys);
646
- }
647
- catch (error) {
648
- // Don't silently ignore - track the error and DO NOT set empty object
649
- const errorMsg = error instanceof Error ? error.message : String(error);
650
- loadErrors.push({ fileKey, error: errorMsg });
651
- log(`⚠️ Failed to load ${fileKey}: ${errorMsg}`);
652
- }
653
- }
654
- }
655
- }
687
+ localeLoadPromises.push(loadLocalesFile(loadPath, locale, namespace, { silent: true })
688
+ .then((existingKeys) => {
689
+ localeFiles.set(fileKey, existingKeys);
690
+ originalKeyCounts.set(fileKey, Object.keys(existingKeys).length);
691
+ })
692
+ .catch((error) => {
693
+ // Don't silently ignore - track the error and DO NOT set empty object
694
+ const errorMsg = error instanceof Error ? error.message : String(error);
695
+ loadErrors.push({ fileKey, error: errorMsg });
696
+ log(`⚠️ Failed to load ${fileKey}: ${errorMsg}`);
697
+ }));
698
+ }
699
+ }
700
+ await Promise.all(localeLoadPromises);
656
701
  // If any files failed to load, abort to prevent data loss
657
702
  if (loadErrors.length > 0) {
658
703
  throw new Error(`Failed to load ${loadErrors.length} locale file(s). Aborting to prevent data loss.\n` +
@@ -677,14 +722,14 @@ export const addTranslationKeys = async ({ keys, config, }) => {
677
722
  if (openai) {
678
723
  // Group keys by input language
679
724
  const keysByLanguage = new Map();
680
- for (const { key, value, language = "en" } of keys) {
681
- if (!keysByLanguage.has(language)) {
682
- keysByLanguage.set(language, []);
725
+ for (const keyEntry of preparedKeys) {
726
+ if (!keysByLanguage.has(keyEntry.language)) {
727
+ keysByLanguage.set(keyEntry.language, []);
683
728
  }
684
- keysByLanguage.get(language).push({
685
- key,
686
- value,
687
- namespaces: keyToNamespaces.get(key) || new Set([defaultNamespace]),
729
+ keysByLanguage.get(keyEntry.language).push({
730
+ key: keyEntry.key,
731
+ value: keyEntry.value,
732
+ namespaces: keyEntry.namespaces,
688
733
  });
689
734
  }
690
735
  // Translate each language group to all other locales
@@ -741,22 +786,6 @@ export const addTranslationKeys = async ({ keys, config, }) => {
741
786
  // Step 8: Validate and write all files
742
787
  // Safety check: ensure we're not accidentally removing keys (only adding)
743
788
  const writeStartTime = performance.now();
744
- const originalKeyCounts = new Map();
745
- // Store original key counts for validation
746
- for (const namespace of affectedNamespaces) {
747
- for (const locale of config.locales) {
748
- const fileKey = `${locale}:${namespace}`;
749
- try {
750
- const original = await loadLocalesFile(loadPath, locale, namespace, {
751
- silent: true,
752
- });
753
- originalKeyCounts.set(fileKey, Object.keys(original).length);
754
- }
755
- catch {
756
- originalKeyCounts.set(fileKey, 0);
757
- }
758
- }
759
- }
760
789
  // Validate: new file should have at least as many keys as original
761
790
  for (const [fileKey, newKeys] of localeFiles) {
762
791
  const originalCount = originalKeyCounts.get(fileKey) || 0;
@@ -776,8 +805,8 @@ export const addTranslationKeys = async ({ keys, config, }) => {
776
805
  const totalTime = performance.now() - startTime;
777
806
  log(`✅ Batch operation completed in ${totalTime.toFixed(2)}ms (${(totalTime / keys.length).toFixed(2)}ms per key)`);
778
807
  // Build results
779
- const results = keys.map(({ key, value, language = "en" }) => {
780
- const namespaces = Array.from(keyToNamespaces.get(key) || new Set([defaultNamespace]));
808
+ const results = preparedKeys.map(({ key, value, language, namespaces }) => {
809
+ const resolvedNamespaces = Array.from(namespaces);
781
810
  const savedLocales = new Set([language]);
782
811
  if (openai) {
783
812
  config.locales.forEach((locale) => {
@@ -792,7 +821,7 @@ export const addTranslationKeys = async ({ keys, config, }) => {
792
821
  return {
793
822
  key,
794
823
  value,
795
- namespace: namespaces.join(", "),
824
+ namespace: resolvedNamespaces.join(", "),
796
825
  locale: Array.from(savedLocales).sort().join(", "),
797
826
  };
798
827
  });