@scoutello/i18n-magic 0.51.0 → 0.53.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/dist/commands/clean.d.ts.map +1 -1
- package/dist/commands/clean.js +10 -10
- package/dist/commands/clean.js.map +1 -1
- package/dist/commands/replace.d.ts.map +1 -1
- package/dist/commands/replace.js +38 -30
- package/dist/commands/replace.js.map +1 -1
- package/dist/commands/scan.d.ts.map +1 -1
- package/dist/commands/scan.js +22 -18
- package/dist/commands/scan.js.map +1 -1
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +34 -68
- package/dist/lib/utils.js.map +1 -1
- package/dist/mcp-server.js +430 -1
- package/dist/mcp-server.js.map +1 -1
- package/package.json +6 -4
- package/src/commands/clean.ts +33 -30
- package/src/commands/replace.ts +46 -25
- package/src/commands/scan.ts +38 -28
- package/src/lib/utils.ts +39 -76
- package/src/mcp-server.ts +517 -1
package/src/commands/replace.ts
CHANGED
|
@@ -55,8 +55,14 @@ export const replaceTranslation = async (
|
|
|
55
55
|
{ namespace: string; value: string }[]
|
|
56
56
|
> = {}
|
|
57
57
|
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
const namespaceKeysResults = await Promise.all(
|
|
59
|
+
namespaces.map(async (namespace) => ({
|
|
60
|
+
namespace,
|
|
61
|
+
keys: await loadLocalesFile(loadPath, defaultLocale, namespace),
|
|
62
|
+
}))
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
for (const { namespace, keys } of namespaceKeysResults) {
|
|
60
66
|
for (const [keyName, value] of Object.entries(keys)) {
|
|
61
67
|
if (!allAvailableKeys[keyName]) {
|
|
62
68
|
allAvailableKeys[keyName] = []
|
|
@@ -105,24 +111,32 @@ export const replaceTranslation = async (
|
|
|
105
111
|
}
|
|
106
112
|
|
|
107
113
|
// Show current translations across namespaces
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
114
|
+
const currentTranslations = await Promise.all(
|
|
115
|
+
targetNamespaces.map(async (namespace) => {
|
|
116
|
+
const keys = await loadLocalesFile(loadPath, defaultLocale, namespace)
|
|
117
|
+
return { namespace, value: keys[keyToReplace] }
|
|
118
|
+
})
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
for (const { namespace, value } of currentTranslations) {
|
|
122
|
+
if (value) {
|
|
111
123
|
console.log(
|
|
112
|
-
`Current translation in ${defaultLocale} (${namespace}): "${
|
|
124
|
+
`Current translation in ${defaultLocale} (${namespace}): "${value}"`,
|
|
113
125
|
)
|
|
114
126
|
}
|
|
115
127
|
}
|
|
116
128
|
|
|
117
129
|
const newTranslation = await getTextInput("Enter the new translation: ")
|
|
118
130
|
|
|
119
|
-
//
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
// Batch translate for all non-default locales first
|
|
132
|
+
const translationCache: Record<string, string> = {
|
|
133
|
+
[defaultLocale]: newTranslation,
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nonDefaultLocales = locales.filter((l) => l !== defaultLocale)
|
|
137
|
+
if (nonDefaultLocales.length > 0) {
|
|
138
|
+
await Promise.all(
|
|
139
|
+
nonDefaultLocales.map(async (locale) => {
|
|
126
140
|
const translation = await translateKey({
|
|
127
141
|
context,
|
|
128
142
|
inputLanguage: defaultLocale,
|
|
@@ -133,17 +147,24 @@ export const replaceTranslation = async (
|
|
|
133
147
|
openai,
|
|
134
148
|
model: config.model,
|
|
135
149
|
})
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const existingKeys = await loadLocalesFile(loadPath, locale, namespace)
|
|
141
|
-
existingKeys[keyToReplace] = newValue
|
|
142
|
-
await writeLocalesFile(savePath, locale, namespace, existingKeys)
|
|
143
|
-
|
|
144
|
-
console.log(
|
|
145
|
-
`Updated "${keyToReplace}" in ${locale} (${namespace}): "${newValue}"`,
|
|
146
|
-
)
|
|
147
|
-
}
|
|
150
|
+
translationCache[locale] = translation[keyToReplace]
|
|
151
|
+
})
|
|
152
|
+
)
|
|
148
153
|
}
|
|
154
|
+
|
|
155
|
+
// Update the key in all relevant namespaces and locales in parallel
|
|
156
|
+
await Promise.all(
|
|
157
|
+
targetNamespaces.flatMap((namespace) =>
|
|
158
|
+
locales.map(async (locale) => {
|
|
159
|
+
const newValue = translationCache[locale]
|
|
160
|
+
const existingKeys = await loadLocalesFile(loadPath, locale, namespace)
|
|
161
|
+
existingKeys[keyToReplace] = newValue
|
|
162
|
+
await writeLocalesFile(savePath, locale, namespace, existingKeys)
|
|
163
|
+
|
|
164
|
+
console.log(
|
|
165
|
+
`Updated "${keyToReplace}" in ${locale} (${namespace}): "${newValue}"`,
|
|
166
|
+
)
|
|
167
|
+
})
|
|
168
|
+
)
|
|
169
|
+
)
|
|
149
170
|
}
|
package/src/commands/scan.ts
CHANGED
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
checkAllKeysExist,
|
|
4
4
|
getMissingKeys,
|
|
5
5
|
getTextInput,
|
|
6
|
-
findExistingTranslation,
|
|
7
6
|
findExistingTranslations,
|
|
8
7
|
loadLocalesFile,
|
|
9
8
|
translateKey,
|
|
@@ -93,40 +92,51 @@ export const translateMissing = async (config: Configuration) => {
|
|
|
93
92
|
|
|
94
93
|
const allLocales = disableTranslationDuringScan ? [defaultLocale] : locales
|
|
95
94
|
|
|
96
|
-
for
|
|
97
|
-
|
|
95
|
+
// Batch translate for all non-default locales in parallel
|
|
96
|
+
const translationCache: Record<string, Record<string, string>> = {
|
|
97
|
+
[defaultLocale]: newKeysObject,
|
|
98
|
+
}
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
100
|
+
const nonDefaultLocales = allLocales.filter((l) => l !== defaultLocale)
|
|
101
|
+
if (nonDefaultLocales.length > 0) {
|
|
102
|
+
await Promise.all(
|
|
103
|
+
nonDefaultLocales.map(async (locale) => {
|
|
104
|
+
const translatedValues = await translateKey({
|
|
105
|
+
inputLanguage: defaultLocale,
|
|
106
|
+
outputLanguage: locale,
|
|
107
|
+
context,
|
|
108
|
+
object: newKeysObject,
|
|
109
|
+
openai,
|
|
110
|
+
model: config.model,
|
|
111
|
+
})
|
|
112
|
+
translationCache[locale] = translatedValues
|
|
109
113
|
})
|
|
110
|
-
|
|
114
|
+
)
|
|
115
|
+
}
|
|
111
116
|
|
|
112
|
-
|
|
113
|
-
|
|
117
|
+
// Process all locale/namespace combinations in parallel
|
|
118
|
+
await Promise.all(
|
|
119
|
+
allLocales.flatMap((locale) =>
|
|
120
|
+
namespaces.map(async (namespace) => {
|
|
121
|
+
const existingKeys = await loadLocalesFile(loadPath, locale, namespace)
|
|
114
122
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
123
|
+
const relevantKeys = newKeysWithDefaultLocale.filter((key) =>
|
|
124
|
+
key.namespaces?.includes(namespace),
|
|
125
|
+
)
|
|
118
126
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
127
|
+
if (relevantKeys.length === 0) {
|
|
128
|
+
return
|
|
129
|
+
}
|
|
122
130
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
131
|
+
const translatedValues = translationCache[locale]
|
|
132
|
+
for (const key of relevantKeys) {
|
|
133
|
+
existingKeys[key.key] = translatedValues[key.key]
|
|
134
|
+
}
|
|
126
135
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
136
|
+
await writeLocalesFile(savePath, locale, namespace, existingKeys)
|
|
137
|
+
})
|
|
138
|
+
)
|
|
139
|
+
)
|
|
130
140
|
|
|
131
141
|
await checkAllKeysExist(config)
|
|
132
142
|
|
package/src/lib/utils.ts
CHANGED
|
@@ -703,45 +703,24 @@ export const addTranslationKey = async ({
|
|
|
703
703
|
}) => {
|
|
704
704
|
const { loadPath, savePath, defaultNamespace, namespaces, globPatterns, defaultLocale, openai, context, model } = config
|
|
705
705
|
|
|
706
|
-
// Use console.error for logging when called from MCP server (console.log is suppressed)
|
|
707
|
-
const log = console.log
|
|
708
|
-
|
|
709
|
-
// Validate that config has required fields
|
|
710
|
-
if (!loadPath || !savePath) {
|
|
711
|
-
throw new Error("Config must have loadPath and savePath defined")
|
|
712
|
-
}
|
|
713
|
-
|
|
714
|
-
if (!namespaces || namespaces.length === 0) {
|
|
715
|
-
throw new Error("Config must have at least one namespace defined")
|
|
716
|
-
}
|
|
717
|
-
|
|
718
706
|
// Try to find which namespaces this key is used in by scanning the codebase
|
|
719
707
|
const affectedNamespaces: string[] = []
|
|
720
708
|
|
|
721
709
|
try {
|
|
722
|
-
log(`🔍 Scanning codebase for key "${key}"...`)
|
|
723
|
-
log(` Glob patterns: ${extractGlobPatterns(globPatterns).length} pattern(s)`)
|
|
724
|
-
log(` Default namespace: ${defaultNamespace}`)
|
|
725
|
-
|
|
726
710
|
// Scan the codebase to find where this key is already being used
|
|
727
711
|
const keysWithNamespaces = await getKeysWithNamespaces({
|
|
728
712
|
globPatterns,
|
|
729
713
|
defaultNamespace,
|
|
730
714
|
})
|
|
731
715
|
|
|
732
|
-
log(` Scanned ${keysWithNamespaces.length} total key instances across all files`)
|
|
733
|
-
|
|
734
716
|
// Find entries for this specific key
|
|
735
717
|
const keyEntries = keysWithNamespaces.filter(
|
|
736
|
-
(entry) => entry.key === key || entry.key === `${defaultNamespace}:${key}`
|
|
718
|
+
(entry) => entry.key === key || entry.key === `${defaultNamespace}:${key}`
|
|
737
719
|
)
|
|
738
720
|
|
|
739
|
-
log(` Found ${keyEntries.length} instance(s) of key "${key}"`)
|
|
740
|
-
|
|
741
721
|
// Collect unique namespaces where this key is used
|
|
742
722
|
const foundNamespaces = new Set<string>()
|
|
743
723
|
for (const entry of keyEntries) {
|
|
744
|
-
log(` File: ${entry.file} → Namespaces: ${entry.namespaces.join(", ")}`)
|
|
745
724
|
for (const ns of entry.namespaces) {
|
|
746
725
|
foundNamespaces.add(ns)
|
|
747
726
|
}
|
|
@@ -749,25 +728,19 @@ export const addTranslationKey = async ({
|
|
|
749
728
|
|
|
750
729
|
if (foundNamespaces.size > 0) {
|
|
751
730
|
affectedNamespaces.push(...Array.from(foundNamespaces))
|
|
752
|
-
log(
|
|
753
|
-
|
|
731
|
+
console.log(
|
|
732
|
+
`🔍 Found key "${key}" in use across ${affectedNamespaces.length} namespace(s): ${affectedNamespaces.join(", ")}`
|
|
754
733
|
)
|
|
755
734
|
}
|
|
756
735
|
} catch (error) {
|
|
757
|
-
// If scanning fails,
|
|
758
|
-
|
|
759
|
-
if (error instanceof Error) {
|
|
760
|
-
log(` Error: ${error.message}`)
|
|
761
|
-
if (error.stack) {
|
|
762
|
-
log(` Stack trace: ${error.stack}`)
|
|
763
|
-
}
|
|
764
|
-
}
|
|
736
|
+
// If scanning fails, continue with fallback logic
|
|
737
|
+
console.error(`Warning: Failed to scan codebase for key usage: ${error}`)
|
|
765
738
|
}
|
|
766
739
|
|
|
767
740
|
if (affectedNamespaces.length === 0) {
|
|
768
741
|
// If the key is not found in the codebase, use the default namespace
|
|
769
742
|
affectedNamespaces.push(defaultNamespace)
|
|
770
|
-
log(
|
|
743
|
+
console.log(
|
|
771
744
|
`📝 Key "${key}" not found in codebase. Adding to default namespace "${defaultNamespace}".`
|
|
772
745
|
)
|
|
773
746
|
}
|
|
@@ -775,6 +748,9 @@ export const addTranslationKey = async ({
|
|
|
775
748
|
// Always use "en" as the locale for adding keys (English)
|
|
776
749
|
const locale = "en"
|
|
777
750
|
|
|
751
|
+
// Use console.error for logging when called from MCP server (console.log is suppressed)
|
|
752
|
+
const log = console.log
|
|
753
|
+
|
|
778
754
|
for (const targetNamespace of affectedNamespaces) {
|
|
779
755
|
log(`➕ Adding translation key "${key}" to namespace "${targetNamespace}" (${locale})`)
|
|
780
756
|
|
|
@@ -782,7 +758,6 @@ export const addTranslationKey = async ({
|
|
|
782
758
|
let existingKeys: Record<string, string>
|
|
783
759
|
try {
|
|
784
760
|
existingKeys = await loadLocalesFile(loadPath, locale, targetNamespace)
|
|
785
|
-
log(` Loaded ${Object.keys(existingKeys).length} existing keys from ${locale}/${targetNamespace}`)
|
|
786
761
|
} catch (error) {
|
|
787
762
|
// If file doesn't exist, start with empty object
|
|
788
763
|
log(`📄 Creating new namespace file for ${locale}/${targetNamespace}`)
|
|
@@ -803,51 +778,39 @@ export const addTranslationKey = async ({
|
|
|
803
778
|
log(`✅ Successfully saved key to ${locale}/${targetNamespace}`)
|
|
804
779
|
|
|
805
780
|
// If defaultLocale is different from "en", translate and save to defaultLocale
|
|
806
|
-
if (defaultLocale !== "en") {
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
const translatedValue = await translateKey({
|
|
820
|
-
inputLanguage: "en",
|
|
821
|
-
outputLanguage: defaultLocale,
|
|
822
|
-
context: context || "",
|
|
823
|
-
object: { [key]: value },
|
|
824
|
-
openai,
|
|
825
|
-
model: model as string,
|
|
826
|
-
})
|
|
827
|
-
|
|
828
|
-
// Load existing keys for the default locale
|
|
829
|
-
let defaultLocaleKeys: Record<string, string>
|
|
830
|
-
try {
|
|
831
|
-
defaultLocaleKeys = await loadLocalesFile(loadPath, defaultLocale, targetNamespace)
|
|
832
|
-
} catch (error) {
|
|
833
|
-
// If file doesn't exist, start with empty object
|
|
834
|
-
log(`📄 Creating new namespace file for ${defaultLocale}/${targetNamespace}`)
|
|
835
|
-
defaultLocaleKeys = {}
|
|
836
|
-
}
|
|
837
|
-
|
|
838
|
-
// Add the translated key
|
|
839
|
-
defaultLocaleKeys[key] = translatedValue[key]
|
|
781
|
+
if (defaultLocale !== "en" && openai) {
|
|
782
|
+
log(`🌐 Translating key "${key}" to ${defaultLocale}...`)
|
|
783
|
+
|
|
784
|
+
try {
|
|
785
|
+
// Translate the single key
|
|
786
|
+
const translatedValue = await translateKey({
|
|
787
|
+
inputLanguage: "en",
|
|
788
|
+
outputLanguage: defaultLocale,
|
|
789
|
+
context: context || "",
|
|
790
|
+
object: { [key]: value },
|
|
791
|
+
openai,
|
|
792
|
+
model,
|
|
793
|
+
})
|
|
840
794
|
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
795
|
+
// Load existing keys for the default locale
|
|
796
|
+
let defaultLocaleKeys: Record<string, string>
|
|
797
|
+
try {
|
|
798
|
+
defaultLocaleKeys = await loadLocalesFile(loadPath, defaultLocale, targetNamespace)
|
|
844
799
|
} catch (error) {
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
}
|
|
849
|
-
log(` You can run 'i18n-magic sync' to translate this key later`)
|
|
800
|
+
// If file doesn't exist, start with empty object
|
|
801
|
+
log(`📄 Creating new namespace file for ${defaultLocale}/${targetNamespace}`)
|
|
802
|
+
defaultLocaleKeys = {}
|
|
850
803
|
}
|
|
804
|
+
|
|
805
|
+
// Add the translated key
|
|
806
|
+
defaultLocaleKeys[key] = translatedValue[key]
|
|
807
|
+
|
|
808
|
+
// Save the updated keys
|
|
809
|
+
await writeLocalesFile(savePath, defaultLocale, targetNamespace, defaultLocaleKeys)
|
|
810
|
+
log(`✅ Successfully translated and saved key to ${defaultLocale}/${targetNamespace}`)
|
|
811
|
+
} catch (error) {
|
|
812
|
+
log(`⚠️ Failed to translate key to ${defaultLocale}: ${error instanceof Error ? error.message : "Unknown error"}`)
|
|
813
|
+
log(` You can run 'i18n-magic sync' to translate this key later`)
|
|
851
814
|
}
|
|
852
815
|
}
|
|
853
816
|
}
|