sicua
Version:
A tool for analyzing project structure and dependencies
211 lines (210 loc) • 8.1 kB
JavaScript
;
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;