UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

182 lines (181 loc) 8.76 kB
"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;