@scoutello/i18n-magic 0.60.0 → 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/src/lib/utils.ts CHANGED
@@ -872,34 +872,78 @@ export const addTranslationKeys = async ({
872
872
  log(`⏱️ Codebase scan completed in ${scanTime.toFixed(2)}ms`)
873
873
 
874
874
  // Step 2: Determine namespaces for each key
875
- const keyToNamespaces = new Map<string, Set<string>>()
875
+ // Resolution order:
876
+ // 1) Explicit namespace prefix (e.g. "dashboard:welcome")
877
+ // 2) Code usage scan matches
878
+ // 3) Existing key in default locale namespace files
879
+ // 4) Key prefix matching a namespace (e.g. "dashboard.title")
880
+ // 5) Default namespace fallback
881
+ const defaultLocaleKeysByNamespace = new Map<string, Record<string, string>>()
882
+ await Promise.all(
883
+ namespaces.map(async (namespace) => {
884
+ try {
885
+ const nsKeys = await loadLocalesFile(loadPath, defaultLocale, namespace, {
886
+ silent: true,
887
+ })
888
+ defaultLocaleKeysByNamespace.set(namespace, nsKeys)
889
+ } catch {
890
+ defaultLocaleKeysByNamespace.set(namespace, {})
891
+ }
892
+ }),
893
+ )
876
894
 
877
- for (const { key, language = "en" } of keys) {
878
- const affectedNamespaces: string[] = []
895
+ const preparedKeys = keys.map(({ key, value, language = "en" }) => {
896
+ const splitKey = key.split(":")
897
+ const hasExplicitNamespace =
898
+ splitKey.length > 1 && namespaces.includes(splitKey[0])
899
+ const normalizedKey = hasExplicitNamespace ? splitKey.slice(1).join(":") : key
900
+ const explicitNamespace = hasExplicitNamespace ? splitKey[0] : null
901
+ const foundNamespaces = new Set<string>()
879
902
 
880
- // Find entries for this specific key
881
- const keyEntries = keysWithNamespaces.filter(
882
- (entry) =>
883
- entry.key === key || entry.key === `${defaultNamespace}:${key}`,
884
- )
903
+ if (explicitNamespace) {
904
+ foundNamespaces.add(explicitNamespace)
905
+ } else {
906
+ for (const entry of keysWithNamespaces) {
907
+ for (const namespace of entry.namespaces) {
908
+ const pureKey = getPureKey(
909
+ entry.key,
910
+ namespace,
911
+ namespace === defaultNamespace,
912
+ )
913
+ if (entry.key === normalizedKey || pureKey === normalizedKey) {
914
+ foundNamespaces.add(namespace)
915
+ }
916
+ }
917
+ }
885
918
 
886
- // Collect unique namespaces where this key is used
887
- const foundNamespaces = new Set<string>()
888
- for (const entry of keyEntries) {
889
- for (const ns of entry.namespaces) {
890
- foundNamespaces.add(ns)
919
+ if (foundNamespaces.size === 0) {
920
+ for (const namespace of namespaces) {
921
+ const namespaceKeys = defaultLocaleKeysByNamespace.get(namespace) || {}
922
+ if (Object.hasOwn(namespaceKeys, normalizedKey)) {
923
+ foundNamespaces.add(namespace)
924
+ }
925
+ }
926
+ }
927
+
928
+ if (foundNamespaces.size === 0) {
929
+ const keyPrefix = normalizedKey.split(".")[0]
930
+ if (namespaces.includes(keyPrefix)) {
931
+ foundNamespaces.add(keyPrefix)
932
+ }
891
933
  }
892
934
  }
893
935
 
894
- if (foundNamespaces.size > 0) {
895
- affectedNamespaces.push(...Array.from(foundNamespaces))
896
- } else {
897
- // If the key is not found in the codebase, use the default namespace
898
- affectedNamespaces.push(defaultNamespace)
936
+ if (foundNamespaces.size === 0) {
937
+ foundNamespaces.add(defaultNamespace)
899
938
  }
900
939
 
901
- keyToNamespaces.set(key, new Set(affectedNamespaces))
902
- }
940
+ return {
941
+ key: normalizedKey,
942
+ value,
943
+ language,
944
+ namespaces: foundNamespaces,
945
+ }
946
+ })
903
947
 
904
948
  // Step 3: Group keys by namespace and locale
905
949
  const namespaceLocaleToKeys = new Map<
@@ -907,21 +951,24 @@ export const addTranslationKeys = async ({
907
951
  Array<{ key: string; value: string; language: string }>
908
952
  >()
909
953
 
910
- for (const { key, value, language = "en" } of keys) {
911
- const namespaces = keyToNamespaces.get(key) || new Set([defaultNamespace])
912
- for (const namespace of namespaces) {
913
- const mapKey = `${namespace}:${language}`
954
+ for (const keyEntry of preparedKeys) {
955
+ for (const namespace of keyEntry.namespaces) {
956
+ const mapKey = `${namespace}:${keyEntry.language}`
914
957
  if (!namespaceLocaleToKeys.has(mapKey)) {
915
958
  namespaceLocaleToKeys.set(mapKey, [])
916
959
  }
917
- namespaceLocaleToKeys.get(mapKey)!.push({ key, value, language })
960
+ namespaceLocaleToKeys.get(mapKey)!.push({
961
+ key: keyEntry.key,
962
+ value: keyEntry.value,
963
+ language: keyEntry.language,
964
+ })
918
965
  }
919
966
  }
920
967
 
921
968
  // Step 4: Collect all unique namespaces that will be affected
922
969
  const affectedNamespaces = new Set<string>()
923
- for (const namespaces of keyToNamespaces.values()) {
924
- for (const ns of namespaces) {
970
+ for (const keyEntry of preparedKeys) {
971
+ for (const ns of keyEntry.namespaces) {
925
972
  affectedNamespaces.add(ns)
926
973
  }
927
974
  }
@@ -930,32 +977,31 @@ export const addTranslationKeys = async ({
930
977
  // This ensures we preserve existing keys in all locales
931
978
  const fileIOStartTime = performance.now()
932
979
  const localeFiles = new Map<string, Record<string, string>>()
980
+ const originalKeyCounts = new Map<string, number>()
933
981
  const loadErrors: Array<{ fileKey: string; error: string }> = []
934
982
 
935
- // Load files for all locales × all affected namespaces - SEQUENTIALLY to avoid race conditions
983
+ // Load files for all locales × all affected namespaces in parallel (read-only)
984
+ const localeLoadPromises: Promise<void>[] = []
936
985
  for (const namespace of affectedNamespaces) {
937
986
  for (const locale of config.locales) {
938
987
  const fileKey = `${locale}:${namespace}`
939
-
940
- if (!localeFiles.has(fileKey)) {
941
- try {
942
- const existingKeys = await loadLocalesFile(
943
- loadPath,
944
- locale,
945
- namespace,
946
- { silent: true },
947
- )
948
- localeFiles.set(fileKey, existingKeys)
949
- } catch (error) {
950
- // Don't silently ignore - track the error and DO NOT set empty object
951
- const errorMsg =
952
- error instanceof Error ? error.message : String(error)
953
- loadErrors.push({ fileKey, error: errorMsg })
954
- log(`⚠️ Failed to load ${fileKey}: ${errorMsg}`)
955
- }
956
- }
988
+ localeLoadPromises.push(
989
+ loadLocalesFile(loadPath, locale, namespace, { silent: true })
990
+ .then((existingKeys) => {
991
+ localeFiles.set(fileKey, existingKeys)
992
+ originalKeyCounts.set(fileKey, Object.keys(existingKeys).length)
993
+ })
994
+ .catch((error) => {
995
+ // Don't silently ignore - track the error and DO NOT set empty object
996
+ const errorMsg =
997
+ error instanceof Error ? error.message : String(error)
998
+ loadErrors.push({ fileKey, error: errorMsg })
999
+ log(`⚠️ Failed to load ${fileKey}: ${errorMsg}`)
1000
+ }),
1001
+ )
957
1002
  }
958
1003
  }
1004
+ await Promise.all(localeLoadPromises)
959
1005
 
960
1006
  // If any files failed to load, abort to prevent data loss
961
1007
  if (loadErrors.length > 0) {
@@ -992,14 +1038,14 @@ export const addTranslationKeys = async ({
992
1038
  Array<{ key: string; value: string; namespaces: Set<string> }>
993
1039
  >()
994
1040
 
995
- for (const { key, value, language = "en" } of keys) {
996
- if (!keysByLanguage.has(language)) {
997
- keysByLanguage.set(language, [])
1041
+ for (const keyEntry of preparedKeys) {
1042
+ if (!keysByLanguage.has(keyEntry.language)) {
1043
+ keysByLanguage.set(keyEntry.language, [])
998
1044
  }
999
- keysByLanguage.get(language)!.push({
1000
- key,
1001
- value,
1002
- namespaces: keyToNamespaces.get(key) || new Set([defaultNamespace]),
1045
+ keysByLanguage.get(keyEntry.language)!.push({
1046
+ key: keyEntry.key,
1047
+ value: keyEntry.value,
1048
+ namespaces: keyEntry.namespaces,
1003
1049
  })
1004
1050
  }
1005
1051
 
@@ -1078,22 +1124,6 @@ export const addTranslationKeys = async ({
1078
1124
  // Step 8: Validate and write all files
1079
1125
  // Safety check: ensure we're not accidentally removing keys (only adding)
1080
1126
  const writeStartTime = performance.now()
1081
- const originalKeyCounts = new Map<string, number>()
1082
-
1083
- // Store original key counts for validation
1084
- for (const namespace of affectedNamespaces) {
1085
- for (const locale of config.locales) {
1086
- const fileKey = `${locale}:${namespace}`
1087
- try {
1088
- const original = await loadLocalesFile(loadPath, locale, namespace, {
1089
- silent: true,
1090
- })
1091
- originalKeyCounts.set(fileKey, Object.keys(original).length)
1092
- } catch {
1093
- originalKeyCounts.set(fileKey, 0)
1094
- }
1095
- }
1096
- }
1097
1127
 
1098
1128
  // Validate: new file should have at least as many keys as original
1099
1129
  for (const [fileKey, newKeys] of localeFiles) {
@@ -1123,10 +1153,8 @@ export const addTranslationKeys = async ({
1123
1153
  )
1124
1154
 
1125
1155
  // Build results
1126
- const results = keys.map(({ key, value, language = "en" }) => {
1127
- const namespaces = Array.from(
1128
- keyToNamespaces.get(key) || new Set([defaultNamespace]),
1129
- )
1156
+ const results = preparedKeys.map(({ key, value, language, namespaces }) => {
1157
+ const resolvedNamespaces = Array.from(namespaces)
1130
1158
  const savedLocales = new Set<string>([language])
1131
1159
 
1132
1160
  if (openai) {
@@ -1143,7 +1171,7 @@ export const addTranslationKeys = async ({
1143
1171
  return {
1144
1172
  key,
1145
1173
  value,
1146
- namespace: namespaces.join(", "),
1174
+ namespace: resolvedNamespaces.join(", "),
1147
1175
  locale: Array.from(savedLocales).sort().join(", "),
1148
1176
  }
1149
1177
  })
package/src/mcp-server.ts CHANGED
@@ -120,7 +120,9 @@ function resolveProjectRoot(): string {
120
120
  const AddTranslationKeySchema = z.object({
121
121
  key: z
122
122
  .string()
123
- .describe('The translation key to add (e.g., "welcomeMessage")'),
123
+ .describe(
124
+ 'The translation key to add (e.g., "welcomeMessage"). Namespace is auto-detected; optionally use "namespace:key" to force a namespace.',
125
+ ),
124
126
  value: z.string().describe("The text value for this translation key"),
125
127
  language: z
126
128
  .string()
@@ -137,7 +139,9 @@ const AddTranslationKeysSchema = z.object({
137
139
  z.object({
138
140
  key: z
139
141
  .string()
140
- .describe('The translation key to add (e.g., "welcomeMessage")'),
142
+ .describe(
143
+ 'The translation key to add (e.g., "welcomeMessage"). Namespace is auto-detected; optionally use "namespace:key" to force a namespace.',
144
+ ),
141
145
  value: z.string().describe("The text value for this translation key"),
142
146
  language: z
143
147
  .string()
@@ -153,26 +157,13 @@ const AddTranslationKeysSchema = z.object({
153
157
  })
154
158
 
155
159
  // Zod schema for the list_untranslated_keys tool parameters
156
- const ListUntranslatedKeysSchema = z.object({
157
- namespace: z
158
- .string()
159
- .optional()
160
- .describe(
161
- "Optional namespace to check. If not provided, checks all namespaces.",
162
- ),
163
- })
160
+ const ListUntranslatedKeysSchema = z.object({})
164
161
 
165
162
  // Zod schema for the get_translation_key tool parameters
166
163
  const GetTranslationKeySchema = z.object({
167
164
  key: z
168
165
  .string()
169
166
  .describe('The translation key to retrieve (e.g., "welcomeMessage")'),
170
- namespace: z
171
- .string()
172
- .optional()
173
- .describe(
174
- "Optional namespace to search in. If not provided, searches in default namespace first, then all namespaces.",
175
- ),
176
167
  })
177
168
 
178
169
  // Zod schema for the update_translation_key tool parameters
@@ -187,12 +178,6 @@ const UpdateTranslationKeySchema = z.object({
187
178
  .describe(
188
179
  'The language code of the provided value (e.g., "en", "de", "fr"). Defaults to "en" (English) if not specified.',
189
180
  ),
190
- namespace: z
191
- .string()
192
- .optional()
193
- .describe(
194
- "Optional namespace to update. If not provided, updates the key in all namespaces where it exists.",
195
- ),
196
181
  })
197
182
 
198
183
  // Zod schema for the search_translations tool parameters
@@ -280,14 +265,14 @@ class I18nMagicServer {
280
265
  {
281
266
  name: "add_translation_key",
282
267
  description:
283
- "Add a new translation key with a text value. 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.",
268
+ "Add a new translation key with a text value. Namespace is resolved automatically from code usage and existing locale files. Optionally, force a namespace by prefixing the key as namespace:key. 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.",
284
269
  inputSchema: {
285
270
  type: "object",
286
271
  properties: {
287
272
  key: {
288
273
  type: "string",
289
274
  description:
290
- 'The translation key to add (e.g., "welcomeMessage")',
275
+ 'The translation key to add (e.g., "welcomeMessage"). Namespace is auto-detected; optionally use "namespace:key" to force a namespace.',
291
276
  },
292
277
  value: {
293
278
  type: "string",
@@ -305,7 +290,7 @@ class I18nMagicServer {
305
290
  {
306
291
  name: "add_translation_keys",
307
292
  description:
308
- "Add multiple translation keys in batch. This is optimized for performance - when adding 2 or more keys, prefer this over multiple add_translation_key calls. It performs a single codebase scan, batches file I/O operations, and batches translations for much better performance. NOTE: This tool can only ADD keys, it will NEVER remove any existing keys.",
293
+ "Add multiple translation keys in batch. Namespaces are resolved automatically per key from code usage and existing locale files; optionally force per-key namespace with namespace:key. This is optimized for performance - when adding 2 or more keys, prefer this over multiple add_translation_key calls. It performs a single codebase scan, batches file I/O operations, and batches translations for much better performance. NOTE: This tool can only ADD keys, it will NEVER remove any existing keys.",
309
294
  inputSchema: {
310
295
  type: "object",
311
296
  properties: {
@@ -318,7 +303,7 @@ class I18nMagicServer {
318
303
  key: {
319
304
  type: "string",
320
305
  description:
321
- 'The translation key to add (e.g., "welcomeMessage")',
306
+ 'The translation key to add (e.g., "welcomeMessage"). Namespace is auto-detected; optionally use "namespace:key" to force a namespace.',
322
307
  },
323
308
  value: {
324
309
  type: "string",
@@ -340,16 +325,10 @@ class I18nMagicServer {
340
325
  {
341
326
  name: "list_untranslated_keys",
342
327
  description:
343
- "List all translation keys that are used in the codebase but are not yet defined in the locale files. This helps identify missing translations that need to be added. The tool scans the codebase for translation keys and compares them against existing locale files. NOTE: This is a read-only tool that does not modify any files.",
328
+ "List all translation keys that are used in the codebase but are not yet defined in the locale files. This helps identify missing translations that need to be added. The tool scans the codebase for translation keys and compares them against existing locale files across all namespaces automatically. NOTE: This is a read-only tool that does not modify any files.",
344
329
  inputSchema: {
345
330
  type: "object",
346
- properties: {
347
- namespace: {
348
- type: "string",
349
- description:
350
- "Optional namespace to check. If not provided, checks all namespaces.",
351
- },
352
- },
331
+ properties: {},
353
332
  required: [],
354
333
  },
355
334
  },
@@ -365,11 +344,6 @@ class I18nMagicServer {
365
344
  description:
366
345
  'The translation key to retrieve (e.g., "welcomeMessage")',
367
346
  },
368
- namespace: {
369
- type: "string",
370
- description:
371
- "Optional namespace to search in. If not provided, searches in default namespace first, then all namespaces.",
372
- },
373
347
  },
374
348
  required: ["key"],
375
349
  },
@@ -395,11 +369,6 @@ class I18nMagicServer {
395
369
  description:
396
370
  'The language code of the provided value (e.g., "en" for English, "de" for German, "fr" for French). Defaults to "en" if not specified.',
397
371
  },
398
- namespace: {
399
- type: "string",
400
- description:
401
- "Optional namespace to update. If not provided, updates the key in all namespaces where it exists.",
402
- },
403
372
  },
404
373
  required: ["key", "value"],
405
374
  },
@@ -669,9 +638,7 @@ class I18nMagicServer {
669
638
  if (request.params.name === "list_untranslated_keys") {
670
639
  try {
671
640
  // Validate parameters
672
- const params = ListUntranslatedKeysSchema.parse(
673
- request.params.arguments,
674
- )
641
+ ListUntranslatedKeysSchema.parse(request.params.arguments)
675
642
 
676
643
  // Ensure config is loaded
677
644
  const config = await this.ensureConfig()
@@ -689,17 +656,9 @@ class I18nMagicServer {
689
656
  console.log = originalConsoleLog
690
657
  }
691
658
 
692
- // Filter by namespace if specified
693
- let filteredKeys = missingKeys
694
- if (params.namespace) {
695
- filteredKeys = missingKeys.filter((item) =>
696
- item.namespaces.includes(params.namespace!),
697
- )
698
- }
699
-
700
659
  // Extract just the keys (sorted and unique)
701
660
  const uniqueKeys = Array.from(
702
- new Set(filteredKeys.map((item) => item.key)),
661
+ new Set(missingKeys.map((item) => item.key)),
703
662
  ).sort()
704
663
 
705
664
  return {
@@ -710,12 +669,12 @@ class I18nMagicServer {
710
669
  {
711
670
  success: true,
712
671
  message:
713
- filteredKeys.length === 0
672
+ missingKeys.length === 0
714
673
  ? "No missing translation keys found! All keys used in the codebase are defined."
715
- : `Found ${filteredKeys.length} missing translation key${filteredKeys.length === 1 ? "" : "s"}`,
674
+ : `Found ${missingKeys.length} missing translation key${missingKeys.length === 1 ? "" : "s"}`,
716
675
  missingKeys: uniqueKeys,
717
676
  nextSteps:
718
- filteredKeys.length > 0
677
+ missingKeys.length > 0
719
678
  ? [
720
679
  "Use add_translation_key to add these keys with English values",
721
680
  "Or run 'i18n-magic scan' to add them interactively",
@@ -786,52 +745,39 @@ class I18nMagicServer {
786
745
  let foundNamespace: string | null = null
787
746
 
788
747
  try {
789
- // If namespace is specified, only check that namespace
790
- if (params.namespace) {
748
+ // Try default namespace first
749
+ try {
791
750
  const keys = await loadLocalesFile(
792
751
  config.loadPath,
793
752
  "en",
794
- params.namespace,
753
+ config.defaultNamespace,
795
754
  )
796
- if (keys[params.key]) {
755
+ if (Object.hasOwn(keys, params.key)) {
797
756
  foundValue = keys[params.key]
798
- foundNamespace = params.namespace
799
- }
800
- } else {
801
- // Try default namespace first
802
- try {
803
- const keys = await loadLocalesFile(
804
- config.loadPath,
805
- "en",
806
- config.defaultNamespace,
807
- )
808
- if (keys[params.key]) {
809
- foundValue = keys[params.key]
810
- foundNamespace = config.defaultNamespace
811
- }
812
- } catch (error) {
813
- // Default namespace file doesn't exist or has issues, continue to search other namespaces
757
+ foundNamespace = config.defaultNamespace
814
758
  }
759
+ } catch (error) {
760
+ // Default namespace file doesn't exist or has issues, continue to search other namespaces
761
+ }
815
762
 
816
- // If not found in default namespace, search all other namespaces
817
- if (!foundValue) {
818
- for (const namespace of config.namespaces) {
819
- if (namespace === config.defaultNamespace) continue // Already checked
763
+ // If not found in default namespace, search all other namespaces
764
+ if (foundValue === null) {
765
+ for (const namespace of config.namespaces) {
766
+ if (namespace === config.defaultNamespace) continue // Already checked
820
767
 
821
- try {
822
- const keys = await loadLocalesFile(
823
- config.loadPath,
824
- "en",
825
- namespace,
826
- )
827
- if (keys[params.key]) {
828
- foundValue = keys[params.key]
829
- foundNamespace = namespace
830
- break
831
- }
832
- } catch (error) {
833
- // Namespace file doesn't exist or has issues, continue
768
+ try {
769
+ const keys = await loadLocalesFile(
770
+ config.loadPath,
771
+ "en",
772
+ namespace,
773
+ )
774
+ if (Object.hasOwn(keys, params.key)) {
775
+ foundValue = keys[params.key]
776
+ foundNamespace = namespace
777
+ break
834
778
  }
779
+ } catch (error) {
780
+ // Namespace file doesn't exist or has issues, continue
835
781
  }
836
782
  }
837
783
  }
@@ -840,7 +786,7 @@ class I18nMagicServer {
840
786
  console.log = originalConsoleLog
841
787
  }
842
788
 
843
- if (foundValue) {
789
+ if (foundValue !== null) {
844
790
  return {
845
791
  content: [
846
792
  {
@@ -867,9 +813,9 @@ class I18nMagicServer {
867
813
  text: JSON.stringify(
868
814
  {
869
815
  success: false,
870
- error: `Translation key "${params.key}" not found in English locale${params.namespace ? ` (namespace: ${params.namespace})` : ""}`,
816
+ error: `Translation key "${params.key}" not found in English locale`,
871
817
  key: params.key,
872
- searchedNamespace: params.namespace || "all namespaces",
818
+ searchedNamespace: "all namespaces",
873
819
  suggestion:
874
820
  "Use list_untranslated_keys to see all missing keys or add_translation_key to add this key",
875
821
  },
@@ -952,42 +898,26 @@ class I18nMagicServer {
952
898
  // Find which namespaces contain this key
953
899
  const targetNamespaces: string[] = []
954
900
 
955
- if (params.namespace) {
956
- // Check if key exists in specified namespace
957
- const keys = await loadLocalesFile(
958
- config.loadPath,
959
- "en",
960
- params.namespace,
961
- )
962
- if (keys[params.key]) {
963
- targetNamespaces.push(params.namespace)
964
- } else {
965
- throw new Error(
966
- `Key "${params.key}" does not exist in namespace "${params.namespace}"`,
901
+ // Find all namespaces where this key exists
902
+ for (const namespace of config.namespaces) {
903
+ try {
904
+ const keys = await loadLocalesFile(
905
+ config.loadPath,
906
+ "en",
907
+ namespace,
967
908
  )
968
- }
969
- } else {
970
- // Find all namespaces where this key exists
971
- for (const namespace of config.namespaces) {
972
- try {
973
- const keys = await loadLocalesFile(
974
- config.loadPath,
975
- "en",
976
- namespace,
977
- )
978
- if (keys[params.key]) {
979
- targetNamespaces.push(namespace)
980
- }
981
- } catch (error) {
982
- // Namespace file doesn't exist, continue
909
+ if (Object.hasOwn(keys, params.key)) {
910
+ targetNamespaces.push(namespace)
983
911
  }
912
+ } catch (error) {
913
+ // Namespace file doesn't exist, continue
984
914
  }
915
+ }
985
916
 
986
- if (targetNamespaces.length === 0) {
987
- throw new Error(
988
- `Key "${params.key}" does not exist in any namespace. Use add_translation_key to create it.`,
989
- )
990
- }
917
+ if (targetNamespaces.length === 0) {
918
+ throw new Error(
919
+ `Key "${params.key}" does not exist in any namespace. Use add_translation_key to create it.`,
920
+ )
991
921
  }
992
922
 
993
923
  // Build translation cache with new value