import ts from "typescript"; export function findDuplicateTranslationKeys(sourceText, fileName = "generatedTranslations.ts") { const source = ts.createSourceFile( fileName, sourceText, ts.ScriptTarget.Latest, true, ts.ScriptKind.TS ); const findings = []; function visit(node) { if ( ts.isVariableDeclaration(node) && ts.isIdentifier(node.name) && node.name.text === "generatedTranslations" && node.initializer && ts.isObjectLiteralExpression(node.initializer) ) { scanCatalog(node.initializer); } ts.forEachChild(node, visit); } function scanCatalog(catalog) { for (const localeProperty of catalog.properties) { if (!ts.isPropertyAssignment(localeProperty) || !ts.isObjectLiteralExpression(localeProperty.initializer)) { continue; } const locale = propertyName(localeProperty.name); const firstDeclarations = new Map(); for (const translationProperty of localeProperty.initializer.properties) { if (!ts.isPropertyAssignment(translationProperty)) continue; const key = propertyName(translationProperty.name); if (!key) continue; const first = firstDeclarations.get(key); if (!first) { firstDeclarations.set(key, translationProperty.name); continue; } const duplicatePosition = source.getLineAndCharacterOfPosition( translationProperty.name.getStart(source) ); const firstPosition = source.getLineAndCharacterOfPosition(first.getStart(source)); findings.push( `${fileName}:${duplicatePosition.line + 1}:${duplicatePosition.character + 1} ` + `duplicate generated translation key ${JSON.stringify(key)} in locale ` + `${JSON.stringify(locale)}; first declared at line ${firstPosition.line + 1}` ); } } } visit(source); return findings; } function propertyName(name) { if ( ts.isIdentifier(name) || ts.isStringLiteral(name) || ts.isNumericLiteral(name) || ts.isNoSubstitutionTemplateLiteral(name) ) { return name.text; } return ""; }