Skip to content

Commit 45a0847

Browse files
author
Eric Wheeler
committed
fix: improve translation linting output format
- Clearly distinguish between missing files and files with missing translations - Show English values for missing keys to help translators - Show all missing keys for missing files as well - Simplify display of complex values Signed-off-by: Eric Wheeler <[email protected]>
1 parent 7955ca1 commit 45a0847

File tree

1 file changed

+102
-25
lines changed

1 file changed

+102
-25
lines changed

locales/__tests__/lint-translations.test.ts

Lines changed: 102 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -367,25 +367,102 @@ function formatResults(results: Results, checkTypes: string[], options: LintOpti
367367
})
368368

369369
// Group by locale first
370-
const byLocale = new Map<string, string[]>()
370+
const missingFilesByLocale = new Map<string, string[]>()
371+
const missingKeysByLocale = new Map<string, Map<string, Set<string>>>()
372+
371373
missingByFile.forEach((keys, fileAndLang) => {
372374
const [file, lang] = fileAndLang.split(":")
373-
if (!byLocale.has(lang)) {
374-
byLocale.set(lang, [])
375-
}
376375
const mapping = mappings.find((m) => m.area === area)
377-
if (mapping) {
378-
const targetPath = resolveTargetPath(file, mapping.targetTemplate, lang)
379-
byLocale.get(lang)?.push(targetPath)
376+
if (!mapping) return
377+
378+
const targetPath = resolveTargetPath(file, mapping.targetTemplate, lang)
379+
380+
// Check if this is a missing file or missing keys
381+
let isMissingFile = false
382+
383+
// Check for the special "File missing" case
384+
if (keys.size === 1) {
385+
const key = Array.from(keys)[0]
386+
// Either the key equals the source file or it's a file path
387+
isMissingFile = key === file || key.includes("/")
388+
}
389+
390+
if (isMissingFile) {
391+
// This is a missing file
392+
if (!missingFilesByLocale.has(lang)) {
393+
missingFilesByLocale.set(lang, [])
394+
}
395+
missingFilesByLocale.get(lang)?.push(targetPath)
396+
} else {
397+
// These are missing keys
398+
if (!missingKeysByLocale.has(lang)) {
399+
missingKeysByLocale.set(lang, new Map())
400+
}
401+
if (!missingKeysByLocale.get(lang)?.has(targetPath)) {
402+
missingKeysByLocale.get(lang)?.set(targetPath, new Set())
403+
}
404+
keys.forEach((key) => {
405+
// Skip keys that look like file paths
406+
if (!key.includes("/")) {
407+
missingKeysByLocale.get(lang)?.get(targetPath)?.add(key)
408+
}
409+
})
380410
}
381411
})
382412

383-
byLocale.forEach((files, lang) => {
413+
// Report missing files
414+
missingFilesByLocale.forEach((files, lang) => {
384415
bufferLog(` ${lang}: missing ${files.length} files`)
385416
files.sort().forEach((file) => {
386417
bufferLog(` ${file}`)
418+
419+
// Show missing keys for missing files too
420+
const sourceFile = file.replace(`/${lang}/`, "/en/")
421+
const sourceContent = parseJsonContent(loadFileContent(sourceFile), sourceFile)
422+
423+
if (sourceContent) {
424+
bufferLog(` Missing keys:`)
425+
const sourceKeys = findKeys(sourceContent)
426+
sourceKeys.sort().forEach((key) => {
427+
const englishValue = getValueAtPath(sourceContent, key)
428+
if (typeof englishValue === "string") {
429+
bufferLog(` - ${key} - '${englishValue}' [en]`)
430+
}
431+
})
432+
}
387433
})
388434
})
435+
436+
// Report files with missing keys
437+
missingKeysByLocale.forEach((fileMap, lang) => {
438+
const filesWithMissingKeys = Array.from(fileMap.keys())
439+
if (filesWithMissingKeys.length > 0) {
440+
bufferLog(` ${lang}: ${filesWithMissingKeys.length} files with missing translations`)
441+
filesWithMissingKeys.sort().forEach((file) => {
442+
bufferLog(` ${file}`)
443+
const keys = fileMap.get(file)
444+
if (keys && keys.size > 0) {
445+
bufferLog(` Missing keys:`)
446+
// Get the source file to extract English values
447+
const sourceFile = file.replace(`/${lang}/`, "/en/")
448+
const sourceContent = parseJsonContent(loadFileContent(sourceFile), sourceFile)
449+
450+
Array.from(keys)
451+
.sort()
452+
.forEach((key) => {
453+
const englishValue = sourceContent
454+
? getValueAtPath(sourceContent, key)
455+
: undefined
456+
457+
// Skip displaying complex objects
458+
if (typeof englishValue === "string") {
459+
bufferLog(` - ${key} - '${englishValue}' [en]`)
460+
}
461+
})
462+
}
463+
})
464+
}
465+
})
389466
}
390467

391468
// Show extra translations if any
@@ -519,7 +596,7 @@ function printUsage(): void {
519596
bufferLog(" Example: --file=settings.json,commands.json")
520597
bufferLog(" Use 'all' for all files")
521598
bufferLog(" --area=<areas> Filter by specific areas (comma-separated)")
522-
bufferLog(` Valid areas: ${PATH_MAPPINGS.map(m => m.area).join(", ")}, all`)
599+
bufferLog(` Valid areas: ${PATH_MAPPINGS.map((m) => m.area).join(", ")}, all`)
523600
bufferLog(" Example: --area=docs,core")
524601
bufferLog(" --check=<checks> Specify which checks to run (comma-separated)")
525602
bufferLog(" Valid checks: missing, extra, all")
@@ -567,7 +644,7 @@ function parseArgs(args: string[] = process.argv.slice(2)): LintOptions {
567644
options.locale = values
568645
// Override the global languages array with the provided locales
569646
// Add 'en' as it's always needed as the source language
570-
languages = ['en', ...values] as unknown as Language[]
647+
languages = ["en", ...values] as unknown as Language[]
571648
break
572649
case "file":
573650
options.file = values
@@ -602,13 +679,13 @@ function parseArgs(args: string[] = process.argv.slice(2)): LintOptions {
602679
function lintTranslations(args?: LintOptions): { output: string } {
603680
clearLogs() // Clear the buffer at the start
604681
const options = args || parseArgs() || { area: ["all"], check: ["all"] }
605-
682+
606683
// If help flag is set, print usage and return
607684
if (options.help) {
608685
printUsage()
609686
return { output: printLogs() }
610687
}
611-
688+
612689
const checksToRun = options.check?.includes("all") ? ["missing", "extra"] : options.check || ["all"]
613690

614691
const filteredMappings = filterMappingsByArea(PATH_MAPPINGS, options.area)
@@ -680,22 +757,22 @@ describe("Translation Linting", () => {
680757
test("Run translation linting", () => {
681758
// Use the centralized parseArgs function to process Jest arguments
682759
// Jest passes arguments after -- to the test
683-
const options = parseArgs(process.argv);
684-
760+
const options = parseArgs(process.argv)
761+
685762
// If help flag is set, run with help option
686763
if (options.help) {
687-
const result = lintTranslations(options);
688-
console.log(result.output); // Print help directly to console for visibility
689-
expect(result.output).toContain("Usage: node lint-translations.js [options]");
690-
return;
764+
const result = lintTranslations(options)
765+
console.log(result.output) // Print help directly to console for visibility
766+
expect(result.output).toContain("Usage: node lint-translations.js [options]")
767+
return
691768
}
692-
769+
693770
// Run with processed options
694-
const result = lintTranslations(options);
695-
771+
const result = lintTranslations(options)
772+
696773
// MUST FAIL in ANY event where the output does not contain "All translations are complete"
697774
// This will cause the test to fail for locales with missing or extra translations
698-
expect(result.output).toContain("All translations are complete");
775+
expect(result.output).toContain("All translations are complete")
699776
})
700777

701778
test("Filters mappings by area correctly", () => {
@@ -708,15 +785,15 @@ describe("Translation Linting", () => {
708785
const result = lintTranslations({
709786
help: true,
710787
area: ["all"],
711-
check: ["all"]
788+
check: ["all"],
712789
})
713-
790+
714791
// Verify help content
715792
expect(result.output).toContain("Usage: node lint-translations.js [options]")
716793
expect(result.output).toContain("Description:")
717794
expect(result.output).toContain("Options:")
718795
expect(result.output).toContain("Examples:")
719-
796+
720797
// Verify it doesn't run the linting process
721798
expect(result.output).not.toContain("Translation Results")
722799
})

0 commit comments

Comments
 (0)