Skip to content

Commit 1a55dce

Browse files
committed
refactor(scripts): Improve locale key ordering linter output and logic
Refactor the `lint-locale-key-ordering.js` script to enhance readability and provide more concise output. - Updated help message for clarity and brevity. - Simplified key extraction logic to directly push keys. - Streamlined issue reporting for missing, extra, and out-of-order keys. - Improved console logging for better user experience. - Removed redundant comments and simplified function descriptions.
1 parent 55ac540 commit 1a55dce

File tree

1 file changed

+52
-139
lines changed

1 file changed

+52
-139
lines changed

scripts/lint-locale-key-ordering.js

Lines changed: 52 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -43,31 +43,19 @@ const args = process.argv.slice(2).reduce(
4343
{ area: "both" },
4444
)
4545

46-
// Show help if requested
4746
if (args.help) {
4847
console.log(`
49-
Locale Key Ordering Linter
48+
Locale Key Ordering Linter - Ensures consistent key ordering across locale files
5049
51-
A utility script to ensure consistent key ordering across all locale files.
52-
Compares the key ordering in non-English locale files to the English reference
53-
to identify any ordering mismatches.
54-
55-
Usage:
56-
node scripts/lint-locale-key-ordering.js [options]
57-
tsx scripts/lint-locale-key-ordering.js [options]
50+
Usage: tsx scripts/lint-locale-key-ordering.js [options]
5851
5952
Options:
60-
--locale=<locale> Only check a specific locale (e.g. --locale=fr)
61-
--file=<file> Only check a specific file (e.g. --file=chat.json)
62-
--area=<area> Only check a specific area (core, webview, or both)
63-
'core' = Backend (src/i18n/locales)
64-
'webview' = Frontend UI (webview-ui/src/i18n/locales)
65-
'both' = Check both areas (default)
66-
--help Show this help message
53+
--locale=<locale> Check specific locale (e.g. --locale=fr)
54+
--file=<file> Check specific file (e.g. --file=chat.json)
55+
--area=<area> Check area: core, webview, or both (default)
56+
--help Show this help
6757
68-
Exit Codes:
69-
0 = All key ordering is consistent
70-
1 = Key ordering inconsistencies found
58+
Exit: 0=consistent, 1=issues found
7159
`)
7260
process.exit(0)
7361
}
@@ -81,176 +69,118 @@ const LOCALES_DIRS = {
8169
// Determine which areas to check based on args
8270
const areasToCheck = args.area === "both" ? ["core", "webview"] : [args.area]
8371

84-
/**
85-
* Extract keys from a JSON object in the order they appear
86-
* @param {Object} obj - The JSON object
87-
* @param {string} prefix - The current key prefix for nested objects
88-
* @returns {string[]} Array of dot-notation keys in order
89-
*/
72+
// Extract keys from JSON object recursively in dot notation
9073
function extractKeysInOrder(obj, prefix = "") {
9174
const keys = []
92-
9375
for (const [key, value] of Object.entries(obj)) {
9476
const fullKey = prefix ? `${prefix}.${key}` : key
95-
77+
keys.push(fullKey)
9678
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
97-
// For nested objects, add the parent key first, then recursively add child keys
98-
keys.push(fullKey)
9979
keys.push(...extractKeysInOrder(value, fullKey))
100-
} else {
101-
// For primitive values, just add the key
102-
keys.push(fullKey)
10380
}
10481
}
105-
10682
return keys
10783
}
10884

109-
/**
110-
* Compare two arrays of keys and find ordering differences
111-
* @param {string[]} englishKeys - Keys from English locale
112-
* @param {string[]} localeKeys - Keys from target locale
113-
* @returns {Object} Object containing ordering issues
114-
*/
85+
// Compare key ordering and find differences
11586
function compareKeyOrdering(englishKeys, localeKeys) {
116-
const issues = {
117-
missing: [],
118-
extra: [],
119-
outOfOrder: [],
120-
}
121-
122-
// Find missing and extra keys
12387
const englishSet = new Set(englishKeys)
12488
const localeSet = new Set(localeKeys)
89+
const missing = englishKeys.filter((key) => !localeSet.has(key))
90+
const extra = localeKeys.filter((key) => !englishSet.has(key))
12591

126-
issues.missing = englishKeys.filter((key) => !localeSet.has(key))
127-
issues.extra = localeKeys.filter((key) => !englishSet.has(key))
128-
129-
// Check ordering for common keys
13092
const commonKeys = englishKeys.filter((key) => localeSet.has(key))
13193
const localeCommonKeys = localeKeys.filter((key) => englishSet.has(key))
94+
const outOfOrder = []
13295

13396
for (let i = 0; i < commonKeys.length; i++) {
13497
if (commonKeys[i] !== localeCommonKeys[i]) {
135-
issues.outOfOrder.push({
98+
outOfOrder.push({
13699
expected: commonKeys[i],
137100
actual: localeCommonKeys[i],
138-
position: i,
139101
})
140102
}
141103
}
142104

143-
return issues
105+
return { missing, extra, outOfOrder }
144106
}
145107

146-
/**
147-
* Check key ordering for a specific area
148-
* @param {string} area - Area to check ('core' or 'webview')
149-
* @returns {boolean} True if there are ordering issues
150-
*/
151108
function checkAreaKeyOrdering(area) {
152109
const LOCALES_DIR = LOCALES_DIRS[area]
153-
154-
// Get all locale directories (excluding English)
155110
const allLocales = fs.readdirSync(LOCALES_DIR).filter((item) => {
156-
const stats = fs.statSync(path.join(LOCALES_DIR, item))
157-
return stats.isDirectory() && item !== "en"
111+
return fs.statSync(path.join(LOCALES_DIR, item)).isDirectory() && item !== "en"
158112
})
159113

160-
// Filter to the specified locale if provided
161114
const locales = args.locale ? allLocales.filter((locale) => locale === args.locale) : allLocales
162-
163115
if (args.locale && locales.length === 0) {
164116
console.error(`Error: Locale '${args.locale}' not found in ${LOCALES_DIR}`)
165117
process.exit(1)
166118
}
167119

168-
console.log(
169-
`\n${area === "core" ? "BACKEND" : "FRONTEND"} - Checking key ordering for ${locales.length} locale(s): ${locales.join(", ")}`,
170-
)
120+
console.log(`\n${area} - Checking key ordering for ${locales.length} locale(s): ${locales.join(", ")}`)
171121

172-
// Get all English JSON files
173122
const englishDir = path.join(LOCALES_DIR, "en")
174123
let englishFiles = fs.readdirSync(englishDir).filter((file) => file.endsWith(".json") && !file.startsWith("."))
175124

176-
// Filter to the specified file if provided
177125
if (args.file) {
178126
if (!englishFiles.includes(args.file)) {
179127
console.error(`Error: File '${args.file}' not found in ${englishDir}`)
180128
process.exit(1)
181129
}
182-
englishFiles = englishFiles.filter((file) => file === args.file)
130+
englishFiles = [args.file]
183131
}
184132

185133
console.log(`Checking ${englishFiles.length} file(s): ${englishFiles.join(", ")}`)
186-
187134
let hasOrderingIssues = false
188135

189-
// Check each locale
190136
for (const locale of locales) {
191-
let localeHasIssues = false
192137
const localeIssues = []
193138

194139
for (const fileName of englishFiles) {
195140
const englishFilePath = path.join(englishDir, fileName)
196141
const localeFilePath = path.join(LOCALES_DIR, locale, fileName)
197142

198-
// Check if the locale file exists
199143
if (!fs.existsSync(localeFilePath)) {
200-
localeHasIssues = true
201-
localeIssues.push(` ⚠️ ${fileName}: File missing in ${locale}`)
144+
localeIssues.push(` ⚠️ ${fileName}: File missing`)
202145
continue
203146
}
204147

205-
// Load and parse both files
206-
let englishContent, localeContent
207-
208148
try {
209-
englishContent = JSON.parse(fs.readFileSync(englishFilePath, "utf8"))
210-
localeContent = JSON.parse(fs.readFileSync(localeFilePath, "utf8"))
211-
} catch (e) {
212-
localeHasIssues = true
213-
localeIssues.push(` ❌ ${fileName}: JSON parsing error - ${e.message}`)
214-
continue
215-
}
216-
217-
// Extract keys in order
218-
const englishKeys = extractKeysInOrder(englishContent)
219-
const localeKeys = extractKeysInOrder(localeContent)
220-
221-
// Compare ordering
222-
const issues = compareKeyOrdering(englishKeys, localeKeys)
223-
224-
if (issues.missing.length > 0 || issues.extra.length > 0 || issues.outOfOrder.length > 0) {
225-
localeHasIssues = true
226-
localeIssues.push(` ❌ ${fileName}: Key ordering issues found`)
227-
228-
if (issues.missing.length > 0) {
229-
localeIssues.push(
230-
` Missing keys: ${issues.missing.slice(0, 3).join(", ")}${issues.missing.length > 3 ? ` (+${issues.missing.length - 3} more)` : ""}`,
231-
)
232-
}
233-
234-
if (issues.extra.length > 0) {
235-
localeIssues.push(
236-
` Extra keys: ${issues.extra.slice(0, 3).join(", ")}${issues.extra.length > 3 ? ` (+${issues.extra.length - 3} more)` : ""}`,
237-
)
238-
}
239-
240-
if (issues.outOfOrder.length > 0) {
241-
const firstMismatches = issues.outOfOrder
242-
.slice(0, 2)
243-
.map((issue) => `expected '${issue.expected}' but found '${issue.actual}'`)
244-
.join(", ")
245-
localeIssues.push(
246-
` Order mismatches: ${firstMismatches}${issues.outOfOrder.length > 2 ? ` (+${issues.outOfOrder.length - 2} more)` : ""}`,
247-
)
149+
const englishContent = JSON.parse(fs.readFileSync(englishFilePath, "utf8"))
150+
const localeContent = JSON.parse(fs.readFileSync(localeFilePath, "utf8"))
151+
const issues = compareKeyOrdering(extractKeysInOrder(englishContent), extractKeysInOrder(localeContent))
152+
153+
if (issues.missing.length + issues.extra.length + issues.outOfOrder.length > 0) {
154+
localeIssues.push(` ❌ ${fileName}: Key ordering issues`)
155+
156+
if (issues.missing.length > 0) {
157+
const preview = issues.missing.slice(0, 3).join(", ")
158+
localeIssues.push(
159+
` Missing: ${preview}${issues.missing.length > 3 ? ` (+${issues.missing.length - 3} more)` : ""}`,
160+
)
161+
}
162+
if (issues.extra.length > 0) {
163+
const preview = issues.extra.slice(0, 3).join(", ")
164+
localeIssues.push(
165+
` Extra: ${preview}${issues.extra.length > 3 ? ` (+${issues.extra.length - 3} more)` : ""}`,
166+
)
167+
}
168+
if (issues.outOfOrder.length > 0) {
169+
const preview = issues.outOfOrder
170+
.slice(0, 2)
171+
.map((issue) => `expected '${issue.expected}' but found '${issue.actual}'`)
172+
.join(", ")
173+
localeIssues.push(
174+
` Order: ${preview}${issues.outOfOrder.length > 2 ? ` (+${issues.outOfOrder.length - 2} more)` : ""}`,
175+
)
176+
}
248177
}
178+
} catch (e) {
179+
localeIssues.push(` ❌ ${fileName}: JSON error - ${e.message}`)
249180
}
250181
}
251182

252-
// Only print issues
253-
if (localeHasIssues) {
183+
if (localeIssues.length > 0) {
254184
console.log(`\n 📋 Checking locale: ${locale}`)
255185
localeIssues.forEach((issue) => console.log(issue))
256186
hasOrderingIssues = true
@@ -260,40 +190,23 @@ function checkAreaKeyOrdering(area) {
260190
return hasOrderingIssues
261191
}
262192

263-
/**
264-
* Main function to check locale key ordering
265-
*/
266193
function lintLocaleKeyOrdering() {
267194
try {
268195
console.log("🔍 Starting locale key ordering check...")
196+
const anyAreaHasIssues = areasToCheck.some((area) => checkAreaKeyOrdering(area))
269197

270-
let anyAreaHasIssues = false
271-
272-
// Check each requested area
273-
for (const area of areasToCheck) {
274-
const hasIssues = checkAreaKeyOrdering(area)
275-
anyAreaHasIssues = anyAreaHasIssues || hasIssues
276-
}
277-
278-
// Summary
279198
if (!anyAreaHasIssues) {
280199
console.log("✅ All locale files have consistent key ordering!")
281200
process.exit(0)
282201
} else {
283202
console.log("\n❌ Key ordering inconsistencies detected!")
284-
console.log("\n💡 To fix ordering issues:")
285-
console.log("1. Review the files with ordering mismatches")
286-
console.log("2. Reorder keys to match the English locale files")
287-
console.log("3. Use MCP sort_i18n_keys tool to fix ordering")
288-
console.log("4. Run this linter again to verify fixes")
203+
console.log("\n💡 To fix: Use MCP sort_i18n_keys tool or manually reorder keys to match English files")
289204
process.exit(1)
290205
}
291206
} catch (error) {
292207
console.error("Error:", error.message)
293-
console.error(error.stack)
294208
process.exit(1)
295209
}
296210
}
297211

298-
// Run the main function
299212
lintLocaleKeyOrdering()

0 commit comments

Comments
 (0)