UNPKG

sicua

Version:

A tool for analyzing project structure and dependencies

211 lines (210 loc) 8.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TranslationFileFinder = void 0; const path_1 = __importDefault(require("path")); const fs_1 = __importDefault(require("fs")); const pathUtils_1 = require("../../../utils/common/pathUtils"); const translationFileUtils_1 = require("../utils/translationFileUtils"); /** * Finds and analyzes translation files in a project */ class TranslationFileFinder { /** * Creates a new translation file finder * @param context Translation file context */ constructor(context) { this.translationFiles = []; this.mainTranslationFile = null; this.context = context; } /** * Finds all translation files in the project * @returns Promise resolving when files are found */ async findAllTranslationFiles() { this.translationFiles = await this.findTranslationFiles(); if (this.translationFiles.length > 0) { this.mainTranslationFile = this.determineMainTranslationFile(); } } /** * Gets the discovered translation files * @returns Array of translation files */ getTranslationFiles() { return this.translationFiles; } /** * Gets the main translation file (largest one) * @returns Main translation file or null if none found */ getMainTranslationFile() { return this.mainTranslationFile; } /** * Finds translation files in common locations */ async findTranslationFiles() { const translationFiles = []; const { projectPath } = this.context; // Check common locations for translation files const possibleLocations = [ "messages", // next-intl "locales", // next-i18next "translations", // common folder name "i18n/locales", // i18next "src/locales", // common location in src "public/locales", // another common location ]; for (const location of possibleLocations) { const dirPath = path_1.default.join(projectPath, location); if (!fs_1.default.existsSync(dirPath) || !fs_1.default.statSync(dirPath).isDirectory()) { continue; } // Search for translation files const foundFiles = await this.findFilesInDirectory(dirPath); translationFiles.push(...foundFiles); // If we found files in one location, don't check others if (foundFiles.length > 0) { break; } } return translationFiles; } /** * Finds translation files in a directory * @param dirPath Directory path * @returns Promise that resolves to an array of translation files */ async findFilesInDirectory(dirPath) { const translationFiles = []; try { const files = fs_1.default.readdirSync(dirPath); for (const file of files) { const filePath = path_1.default.join(dirPath, file); const stat = fs_1.default.statSync(filePath); if (stat.isDirectory()) { // Recursively check subdirectories const nestedFiles = await this.findFilesInDirectory(filePath); translationFiles.push(...nestedFiles); } else if (file.endsWith(".json")) { // Process JSON files try { const content = await (0, pathUtils_1.readJsonFile)(filePath); // Check if this looks like a translation file if ((0, translationFileUtils_1.isLikelyTranslationFile)(content)) { const size = this.countTranslationEntries(content); translationFiles.push({ path: filePath, content, size, }); } } catch (error) { // Skip files that can't be read } } } } catch (error) { // Skip directories that can't be read } return translationFiles; } /** * Counts the number of translation entries in a file (recursive) * @param obj Translation object * @param prefix Optional prefix for recursive calls * @returns Number of translations */ countTranslationEntries(obj, prefix = "") { let count = 0; for (const [key, value] of Object.entries(obj)) { const fullKey = prefix ? `${prefix}.${key}` : key; if (typeof value === "object" && value !== null) { count += this.countTranslationEntries(value, fullKey); } else { count++; } } return count; } /** * Determines the main translation file (largest one) * @returns The largest translation file or null if none found */ determineMainTranslationFile() { if (this.translationFiles.length === 0) return null; return this.translationFiles.reduce((largest, current) => (current.size > largest.size ? current : largest), this.translationFiles[0]); } /** * Analyzes a translation file structure * @param file Translation file to analyze * @returns Analysis information about the file */ analyzeTranslationFile(file) { const fileName = path_1.default.basename(file.path); let languageCode = null; // Try to extract language code from filename const localeMatch = fileName.match(/^([a-z]{2}(-[A-Z]{2})?)\.json$/); if (localeMatch) { languageCode = localeMatch[1]; } // Calculate max nesting depth const calculateNestingDepth = (obj, currentDepth = 0) => { let maxDepth = currentDepth; for (const [key, value] of Object.entries(obj)) { if (typeof value === "object" && value !== null) { const depth = calculateNestingDepth(value, currentDepth + 1); maxDepth = Math.max(maxDepth, depth); } } return maxDepth; }; const nestedLevels = calculateNestingDepth(file.content); const format = nestedLevels > 0 ? "nested" : "flat"; return { keysCount: file.size, nestedLevels, languageCode, format, }; } /** * Gets information about the translation file format * @returns Information about translation file format */ detectTranslationLibrary() { if (this.translationFiles.length === 0) { return { library: "unknown", format: "flat", multiLanguage: false }; } // Check file paths and structure to guess the library const paths = this.translationFiles.map((f) => f.path); let library = "unknown"; let multiLanguage = this.translationFiles.length > 1; // Check if any path contains specific library patterns if (paths.some((p) => p.includes("/messages/"))) { library = "next-intl"; } else if (paths.some((p) => p.includes("/locales/") && p.includes("/translation.json"))) { library = "i18next"; } else if (paths.some((p) => p.includes("/lang/") || p.includes("/translations/"))) { library = "react-intl"; } // Determine format based on main file const format = this.mainTranslationFile ? this.analyzeTranslationFile(this.mainTranslationFile).format : "flat"; return { library, format, multiLanguage }; } } exports.TranslationFileFinder = TranslationFileFinder;