sicua
Version:
A tool for analyzing project structure and dependencies
182 lines (181 loc) • 8.76 kB
JavaScript
"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.TranslationAnalyzer = void 0;
const lodash_1 = __importDefault(require("lodash"));
const translationFileFinder_1 = require("./finders/translationFileFinder");
const translationKeyFinder_1 = require("./finders/translationKeyFinder");
const missingTranslationAnalyzer_1 = require("./analyzers/missingTranslationAnalyzer");
const duplicateTranslationAnalyzer_1 = require("./analyzers/duplicateTranslationAnalyzer");
const coverageAnalyzer_1 = require("./analyzers/coverageAnalyzer");
// Library detection
const libraryDetector_1 = require("./utils/libraryDetector");
// React-i18next specific analyzers
const ReactI18nextAnalyzer_1 = require("./reacti18next/ReactI18nextAnalyzer");
/**
* Analyzer for detecting translation issues in a React project
* Supports both next-intl and react-i18next libraries
*/
class TranslationAnalyzer {
/**
* Constructor for the translation analyzer
* @param projectPath Path to the project root
* @param sourceFiles Map of source files to analyze
*/
constructor(projectPath, sourceFiles) {
this.projectPath = projectPath;
this.sourceFiles = sourceFiles;
// Initialize library detector
this.libraryDetector = new libraryDetector_1.LibraryDetector();
// Create context object for next-intl file finder (only component that needs it)
const context = {
projectPath,
debugMode: false,
log: () => { }, // No-op logger
translationHooks: new Map(),
};
// Initialize next-intl analyzers
this.translationFileFinder = new translationFileFinder_1.TranslationFileFinder(context);
this.translationKeyFinder = new translationKeyFinder_1.TranslationKeyFinder();
this.missingTranslationAnalyzer = new missingTranslationAnalyzer_1.MissingTranslationAnalyzer();
this.duplicateTranslationAnalyzer = new duplicateTranslationAnalyzer_1.DuplicateTranslationAnalyzer();
this.coverageAnalyzer = new coverageAnalyzer_1.CoverageAnalyzer();
}
/**
* Main analysis method with library detection and routing
* @returns Promise that resolves to analysis results
*/
async analyze() {
// Step 1: Detect which translation library is being used
const libraryDetection = this.libraryDetector.detectTranslationLibrary(this.sourceFiles);
// Step 2: Route to appropriate analyzer based on detected library
switch (libraryDetection.library) {
case libraryDetector_1.TranslationLibrary.NEXT_INTL:
return this.analyzeNextIntl();
case libraryDetector_1.TranslationLibrary.REACT_I18NEXT:
return this.analyzeReactI18next();
case libraryDetector_1.TranslationLibrary.UNKNOWN:
default:
// Fallback to next-intl analysis if library is unknown
// This maintains backward compatibility
return this.analyzeNextIntl();
}
}
/**
* Analyzes projects using next-intl (original implementation)
* @returns Promise that resolves to analysis results
*/
async analyzeNextIntl() {
// Step 1: Find translation files
await this.translationFileFinder.findAllTranslationFiles();
const translationFiles = this.translationFileFinder.getTranslationFiles();
const mainTranslationFile = this.translationFileFinder.getMainTranslationFile();
// Step 2: Find translation keys in code
await this.translationKeyFinder.findAllTranslationKeys(this.sourceFiles);
const translationKeys = this.translationKeyFinder.getTranslationKeys();
// Step 3: Analyze missing translations
const missingTranslations = this.missingTranslationAnalyzer.detectMissingTranslations(translationKeys, translationFiles);
// Step 4: Analyze duplicate translations
const duplicateTranslations = mainTranslationFile
? this.duplicateTranslationAnalyzer.detectDuplicateTranslations(translationKeys, mainTranslationFile)
: [];
// Step 5: Analyze translation file coverage
const translationFilesCoverage = this.coverageAnalyzer.analyzeTranslationFilesCoverage(translationKeys, translationFiles);
// Step 6: Generate statistics
const statistics = this.generateNextIntlStatistics(translationKeys, missingTranslations, duplicateTranslations);
return {
missingTranslations,
duplicateTranslations,
translationFilesCoverage,
statistics,
};
}
/**
* Analyzes projects using react-i18next
* @returns Promise that resolves to analysis results
*/
async analyzeReactI18next() {
// Create and use react-i18next specific analyzer
const reactI18nextAnalyzer = new ReactI18nextAnalyzer_1.ReactI18nextAnalyzer(this.projectPath, this.sourceFiles);
return reactI18nextAnalyzer.analyze();
}
/**
* Generate statistics for next-intl projects (original implementation)
*/
generateNextIntlStatistics(translationKeys, missingTranslations, duplicateTranslations) {
// Group missing translations by file
const filesMissingCount = lodash_1.default.groupBy(missingTranslations, (translation) => translation.key.filePath);
const filesWithMostMissingTranslations = Object.entries(filesMissingCount)
.map(([filePath, translations]) => ({
filePath,
count: translations.length,
}))
.sort((a, b) => b.count - a.count)
.slice(0, 5); // Top 5 files
return {
totalTranslationKeysUsed: translationKeys.length,
totalMissingTranslations: missingTranslations.length,
totalDuplicateValues: duplicateTranslations.length,
filesWithMostMissingTranslations,
};
}
/**
* Gets information about the detected translation library
* @returns Library detection result
*/
getLibraryDetectionInfo() {
return this.libraryDetector.detectTranslationLibrary(this.sourceFiles);
}
/**
* Forces analysis using a specific library (for testing or edge cases)
* @param library Library to use for analysis
* @returns Promise that resolves to analysis results
*/
async analyzeWithLibrary(library) {
switch (library) {
case libraryDetector_1.TranslationLibrary.NEXT_INTL:
return this.analyzeNextIntl();
case libraryDetector_1.TranslationLibrary.REACT_I18NEXT:
return this.analyzeReactI18next();
default:
throw new Error(`Unsupported translation library: ${library}`);
}
}
/**
* Gets detailed analysis information based on detected library
* @returns Library-specific analysis information
*/
async getDetailedAnalysisInfo() {
const detection = this.getLibraryDetectionInfo();
let librarySpecificInfo = undefined;
if (detection.library === libraryDetector_1.TranslationLibrary.REACT_I18NEXT) {
// Get react-i18next specific information
const reactI18nextAnalyzer = new ReactI18nextAnalyzer_1.ReactI18nextAnalyzer(this.projectPath, this.sourceFiles);
// Run a minimal analysis to get setup info
await reactI18nextAnalyzer.analyze();
librarySpecificInfo = reactI18nextAnalyzer.getReactI18nextInfo();
}
else if (detection.library === libraryDetector_1.TranslationLibrary.NEXT_INTL) {
// Get next-intl specific information
await this.translationFileFinder.findAllTranslationFiles();
await this.translationKeyFinder.findAllTranslationKeys(this.sourceFiles);
librarySpecificInfo = {
translationFiles: this.translationFileFinder.getTranslationFiles().length,
mainFile: this.translationFileFinder.getMainTranslationFile()?.path,
keyStatistics: {
totalKeys: this.translationKeyFinder.getTranslationKeys().length,
filesWithKeys: this.translationKeyFinder.groupKeysByFile().size,
componentsWithKeys: this.translationKeyFinder.groupKeysByComponent().size,
},
};
}
return {
library: detection.library,
confidence: detection.confidence,
librarySpecificInfo,
};
}
}
exports.TranslationAnalyzer = TranslationAnalyzer;