In the following text, change it to use advanced vocabulary but do not overuse it. Make sure to use proper grammar and spell check thoroughly. Show expertise in the subject provided, but do not add any extra information. Try to keep your response at the same length of words as the original. Use the same language as the original text. ONLY return the modified text and nothing else.



import * as fs from "fs";
import * as path from "path";
import chalk from "chalk";

/**
 * validateTranslationFiles.ts
 *
 * This script ensures the consistency of translation files across multiple languages in the `packages/i18n/translations` directory.
 *
 * It performs the following tasks:
 *
 * 1. Language Folder Consistency:
 *  Ensures each language folder contains the same set of JSON files (e.g., if `./de/home.json` exists,
 *  then `./en/home.json` must also exist).
 *
 * 2. Translation Key Consistency
 *  Verifies that each JSON file across languages contains the same keys, ensuring no keys are missing.
 *
 * Run with: pnpm -F @filu/i18n run validate
 */

interface TranslationMap {
  [key: string]: any;
}

interface ValidationResult {
  language: string;
  missingFiles: string[];
  missingKeys: string[];
}

const translationsPath = path.join(__dirname, "../translations");

function getLanguageFolders(): string[] {
  return fs.readdirSync(translationsPath).filter((file) => {
    return fs.statSync(path.join(translationsPath, file)).isDirectory();
  });
}

function getTranslationFiles(language: string): string[] {
  const languagePath = path.join(translationsPath, language);
  return fs.readdirSync(languagePath).filter((file) => file.endsWith(".json"));
}

function loadTranslationFile(
  language: string,
  fileName: string,
): TranslationMap {
  const filePath = path.join(translationsPath, language, fileName);
  return JSON.parse(fs.readFileSync(filePath, "utf-8"));
}

function compareKeys(
  baseKeys: TranslationMap,
  otherKeys: TranslationMap,
  prefix = "",
): string[] {
  let missingKeys: string[] = [];

  for (const key in baseKeys) {
    const currentKey = prefix ? `${prefix}.${key}` : key;
    if (!(key in otherKeys)) {
      missingKeys.push(currentKey);
    } else if (typeof baseKeys[key] === "object") {
      missingKeys = missingKeys.concat(
        compareKeys(baseKeys[key], otherKeys[key], currentKey),
      );
    }
  }

  return missingKeys;
}

function validateLanguageFiles(languages: string[]): ValidationResult[] {
  const baseLanguage = languages[0];
  const baseFiles = getTranslationFiles(baseLanguage);
  const results: ValidationResult[] = [];

  for (const language of languages) {
    const translationFiles = getTranslationFiles(language);
    const missingFiles = baseFiles.filter(
      (file) => !translationFiles.includes(file),
    );
    let missingKeys: string[] = [];

    for (const file of baseFiles) {
      if (translationFiles.includes(file)) {
        const baseTranslations = loadTranslationFile(baseLanguage, file);
        const currentTranslations = loadTranslationFile(language, file);
        missingKeys = missingKeys.concat(
          compareKeys(baseTranslations, currentTranslations).map(
            (key) => `[${file}] ${key}`,
          ),
        );
      }
    }

    results.push({ language, missingFiles, missingKeys });
  }

  return results;
}

function printValidationResults(results: ValidationResult[]): void {
  let totalLanguages = 0;
  let totalNamespaces = 0;
  let totalKeysChecked = 0;
  let errorCount = 0;

  for (const result of results) {
    totalLanguages++;

    // Increment namespaces for each translation file checked
    const translationFiles = getTranslationFiles(result.language);
    totalNamespaces += translationFiles.length;

    for (const file of translationFiles) {
      const baseTranslations = loadTranslationFile(results[0].language, file);
      const currentTranslations = loadTranslationFile(result.language, file);

      // Count the total keys checked, including both present and missing keys
      totalKeysChecked += Object.keys(baseTranslations).length;
    }

    if (result.missingFiles.length > 0 || result.missingKeys.length > 0) {
      console.log(chalk.red(`\n[${result.language.toUpperCase()}] Errors:`));

      for (const file of result.missingFiles) {
        console.log(chalk.red(`  - ${file} file is missing.`));
      }

      for (const key of result.missingKeys) {
        console.log(chalk.red(`  - ${key} key is missing.`));
        errorCount++;
      }
    } else {
      console.log(
        chalk.green(`[${result.language.toUpperCase()}] No errors found.`),
      );
    }
  }

  console.log(chalk.green(`\n✓ Validation completed.`));
  console.log(chalk.green(`  - ${totalLanguages} languages checked`));
  console.log(chalk.green(`  - ${totalNamespaces} namespaces checked`));
  console.log(chalk.green(`  - ${totalKeysChecked} translation keys checked`));

  if (errorCount > 0) {
    console.log(chalk.red(`  - ${errorCount} errors found`));
    process.exit(1);
  } else {
    console.log(chalk.green(`  - No errors found!`));
    process.exit(0);
  }
}

function validateTranslations(): void {
  const languages = getLanguageFolders();

  if (languages.length === 0) {
    console.log(chalk.red("No language folders found."));
    return;
  }

  const results = validateLanguageFiles(languages);
  printValidationResults(results);
}

validateTranslations();