UNPKG

angular-translation-checker

Version:

A comprehensive tool for analyzing translation keys in Angular projects using ngx-translate

283 lines 11.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.TranslationChecker = void 0; const types_1 = require("../types"); const plugin_manager_1 = require("./plugin-manager"); const config_manager_1 = require("./config-manager"); const logger_1 = require("./logger"); const event_bus_1 = require("./event-bus"); const filesystem_1 = require("./filesystem"); class TranslationChecker { constructor() { this.logger = new logger_1.ConsoleLogger(); this.eventBus = new event_bus_1.SimpleEventBus(); this.pluginManager = new plugin_manager_1.PluginManager(this.logger, this.eventBus); this.configManager = new config_manager_1.ConfigurationManager(this.logger); this.fileSystem = new filesystem_1.NodeFileSystemAdapter(); } /** * Initialize the translation checker with configuration */ async initialize(configPath, configOverrides) { try { this.eventBus.emit('checker:initializing', { configPath }); // Load configuration const config = await this.configManager.loadConfig(configPath); // Apply overrides if provided if (configOverrides) { Object.assign(config, configOverrides); } // Set logger verbosity if (this.logger instanceof logger_1.ConsoleLogger) { this.logger.setVerbose(config.verbose || false); } // Validate configuration paths await this.configManager.validatePaths(config); // Register built-in plugins await this.registerBuiltInPlugins(config); // Register custom plugins from config await this.registerCustomPlugins(config); this.eventBus.emit('checker:initialized', { config }); this.logger.debug('Translation checker initialized successfully'); return config; } catch (error) { const checkerError = error instanceof types_1.TranslationCheckerError ? error : new types_1.TranslationCheckerError('Initialization failed', 'INIT_ERROR', error); this.logger.error(checkerError.message); this.eventBus.emit('checker:error', { error: checkerError }); throw checkerError; } } /** * Analyze translations based on configuration */ async analyze(config) { try { this.eventBus.emit('analysis:started', { config }); this.logger.info('Starting translation analysis...'); // Discover translation files const translationFiles = await this.discoverTranslationFiles(config); this.logger.debug(`Found ${translationFiles.length} translation files`); // Discover source files const sourceFiles = await this.discoverSourceFiles(config); this.logger.debug(`Found ${sourceFiles.length} source files`); // Extract translation keys from source files const extractedKeys = await this.extractTranslationKeys(sourceFiles, config); this.logger.debug(`Extracted ${extractedKeys.length} translation keys`); // Create analysis context const context = { config, sourceFiles, translationFiles, extractedKeys }; // Run analyzers const analysisResult = await this.runAnalyzers(context); // Run validators await this.runValidators(analysisResult); this.eventBus.emit('analysis:completed', { result: analysisResult }); this.logger.info('Translation analysis completed'); return analysisResult; } catch (error) { const checkerError = error instanceof types_1.TranslationCheckerError ? error : new types_1.TranslationCheckerError('Analysis failed', 'ANALYSIS_ERROR', error); this.logger.error(checkerError.message); this.eventBus.emit('analysis:error', { error: checkerError }); throw checkerError; } } /** * Format analysis result */ async format(result, format, sections) { try { const formatter = this.pluginManager.getFormatter(format); if (!formatter) { throw new types_1.TranslationCheckerError(`No formatter found for format '${format}'`, 'FORMATTER_NOT_FOUND'); } this.logger.debug(`Formatting result with ${formatter.name}`); return await formatter.format(result, sections); } catch (error) { const checkerError = error instanceof types_1.TranslationCheckerError ? error : new types_1.TranslationCheckerError('Formatting failed', 'FORMAT_ERROR', error); this.logger.error(checkerError.message); throw checkerError; } } /** * Generate reports */ async report(result, output) { const reporters = this.pluginManager.getReporters(); for (const reporter of reporters) { try { await reporter.report(result, output); } catch (error) { this.logger.error(`Reporter ${reporter.name} failed: ${error}`); } } } /** * Cleanup resources */ async cleanup() { await this.pluginManager.cleanup(); this.eventBus.emit('checker:cleanup', {}); } // Private methods async registerBuiltInPlugins(config) { // Built-in plugins will be registered here // For now, we'll create them in separate files this.logger.debug('Registering built-in plugins'); } async registerCustomPlugins(config) { if (!config.plugins) return; for (const pluginConfig of config.plugins) { if (!pluginConfig.enabled) continue; try { // Dynamic plugin loading would go here this.logger.debug(`Loading custom plugin: ${pluginConfig.name}`); } catch (error) { this.logger.error(`Failed to load plugin ${pluginConfig.name}: ${error}`); } } } async discoverTranslationFiles(config) { const files = []; try { const entries = await this.fileSystem.readdir(config.localesPath); for (const entry of entries) { const fullPath = `${config.localesPath}/${entry}`; if (entry.endsWith('.json')) { const language = entry.replace('.json', ''); const content = await this.fileSystem.readFile(fullPath); const keys = JSON.parse(content); files.push({ language, path: fullPath, keys }); } } } catch (error) { throw new types_1.TranslationCheckerError(`Failed to discover translation files: ${error}`, 'DISCOVERY_ERROR'); } return files; } async discoverSourceFiles(config) { const files = []; try { if (config.patterns) { for (const [fileType, patterns] of Object.entries(config.patterns)) { for (const pattern of patterns) { const matches = await this.fileSystem.glob(pattern, { cwd: config.srcPath }); // Convert relative paths to absolute paths const absolutePaths = matches.map(relativePath => `${config.srcPath}/${relativePath}`); files.push(...absolutePaths); } } } } catch (error) { throw new types_1.TranslationCheckerError(`Failed to discover source files: ${error}`, 'DISCOVERY_ERROR'); } return files; } async extractTranslationKeys(sourceFiles, config) { const keys = []; const extractors = this.pluginManager.getExtractors(); for (const filePath of sourceFiles) { const content = await this.fileSystem.readFile(filePath); const fileExtension = filePath.split('.').pop() || ''; const relevantExtractors = extractors.filter(extractor => extractor.supportedExtensions.includes(`.${fileExtension}`)); for (const extractor of relevantExtractors) { try { const extractedKeys = await extractor.extractKeys(filePath, content); keys.push(...extractedKeys); } catch (error) { this.logger.warn(`Extraction failed for ${filePath} with ${extractor.name}: ${error}`); } } } return keys; } async runAnalyzers(context) { const analyzers = this.pluginManager.getAnalyzers(); // Initialize result with basic structure let result = { summary: { totalTranslations: 0, totalUsedKeys: 0, totalUnusedKeys: 0, totalMissingKeys: 0, coverage: 0, languages: context.translationFiles.map(f => f.language) }, usedKeys: [], unusedKeys: [], missingKeys: [], dynamicPatterns: [], ignoredKeys: [], translationFiles: context.translationFiles, config: context.config }; // Merge results from all analyzers for (const analyzer of analyzers) { try { const partialResult = await analyzer.analyze(context); result = this.mergeAnalysisResults(result, partialResult); } catch (error) { this.logger.error(`Analyzer ${analyzer.name} failed: ${error}`); } } return result; } async runValidators(result) { const validators = this.pluginManager.getValidators(); for (const validator of validators) { try { const validation = await validator.validate(result); if (!validation.isValid) { for (const error of validation.errors) { this.logger.error(`Validation error: ${error.message}`); } for (const warning of validation.warnings) { this.logger.warn(`Validation warning: ${warning.message}`); } } } catch (error) { this.logger.error(`Validator ${validator.name} failed: ${error}`); } } } mergeAnalysisResults(base, partial) { return { ...base, ...partial, summary: { ...base.summary, ...partial.summary }, usedKeys: [...base.usedKeys, ...(partial.usedKeys || [])], unusedKeys: [...base.unusedKeys, ...(partial.unusedKeys || [])], missingKeys: [...base.missingKeys, ...(partial.missingKeys || [])], dynamicPatterns: [...base.dynamicPatterns, ...(partial.dynamicPatterns || [])], ignoredKeys: [...base.ignoredKeys, ...(partial.ignoredKeys || [])] }; } } exports.TranslationChecker = TranslationChecker; //# sourceMappingURL=translation-checker.js.map