1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-06 11:43:51 +00:00
Files
browser/scripts/migration/i18n/shared/translation-cli.ts
2025-07-28 15:57:28 +02:00

237 lines
7.8 KiB
JavaScript

#!/usr/bin/env node
/* eslint-disable no-console */
import * as fs from "fs";
import * as path from "path";
import * as chalk from "chalk";
import { Command } from "commander";
import { TranslationCombiner } from "./translation-combiner";
import { TranslationLookup } from "./translation-lookup";
const program = new Command();
program
.name("translation-combiner")
.description("CLI tool for combining translation files from all applications")
.version("1.0.0");
program
.command("combine")
.description("Combine all application translation files into a single lookup file")
.option(
"-o, --output <path>",
"Output file for combined translations",
"./combined-translations.json",
)
.option("-r, --report <path>", "Output file for combination report")
.option("-v, --verbose", "Enable verbose logging")
.action(async (options) => {
try {
console.log(chalk.blue("🔄 Combining translation files..."));
const combiner = new TranslationCombiner();
const result = combiner.combineTranslations();
// Save combined translations
combiner.saveCombinedTranslations(result, options.output);
console.log(chalk.green(`✅ Combined translations saved to: ${options.output}`));
// Generate and save report
const report = combiner.generateCombinationReport(result);
if (options.report) {
fs.writeFileSync(options.report, report);
console.log(chalk.green(`📊 Combination report saved to: ${options.report}`));
} else if (options.verbose) {
console.log(report);
}
// Display summary
console.log(chalk.blue("\n📈 Summary:"));
console.log(` Total unique keys: ${result.totalKeys}`);
console.log(` Source applications: ${result.sources.length}`);
console.log(` Conflicts found: ${result.conflicts.length}`);
if (result.conflicts.length > 0) {
console.log(
chalk.yellow(`\n⚠️ Found ${result.conflicts.length} conflicts between applications`),
);
if (!options.verbose) {
console.log(chalk.gray("Use --verbose or --report to see conflict details"));
}
}
} catch (error) {
console.error(chalk.red("❌ Failed to combine translations:"), error);
process.exit(1);
}
});
program
.command("validate")
.description("Validate translation keys against template usage")
.option(
"-c, --combined <path>",
"Path to combined translations file",
"./combined-translations.json",
)
.option("-p, --pattern <pattern>", "Glob pattern for template files", "**/*.html")
.option("-o, --output <path>", "Output file for validation report")
.option("-v, --verbose", "Enable verbose logging")
.action(async (options) => {
try {
console.log(chalk.blue("🔍 Validating translation keys..."));
if (!fs.existsSync(options.combined)) {
console.error(chalk.red(`❌ Combined translations file not found: ${options.combined}`));
console.log(
chalk.gray("Run 'combine' command first to generate the combined translations file"),
);
process.exit(1);
}
const lookup = new TranslationLookup();
await lookup.loadTranslations(options.combined);
// Find template files (simplified - in real implementation would use proper glob)
const templateFiles = findTemplateFiles(options.pattern);
if (templateFiles.length === 0) {
console.log(chalk.yellow("⚠️ No template files found matching pattern"));
return;
}
console.log(chalk.gray(`Found ${templateFiles.length} template files`));
// This would require the enhanced transformer
// For now, just show stats
const stats = lookup.getStats();
console.log(chalk.blue("\n📊 Translation Statistics:"));
console.log(` Total translations loaded: ${stats.totalKeys}`);
console.log(` Lookup service ready: ${stats.isLoaded ? "Yes" : "No"}`);
} catch (error) {
console.error(chalk.red("❌ Validation failed:"), error);
process.exit(1);
}
});
program
.command("search")
.description("Search for translation keys or values")
.argument("<query>", "Search query")
.option(
"-c, --combined <path>",
"Path to combined translations file",
"./combined-translations.json",
)
.option("-l, --limit <number>", "Maximum number of results", "10")
.option("-v, --verbose", "Enable verbose logging")
.action(async (query, options) => {
try {
if (!fs.existsSync(options.combined)) {
console.error(chalk.red(`❌ Combined translations file not found: ${options.combined}`));
console.log(
chalk.gray("Run 'combine' command first to generate the combined translations file"),
);
process.exit(1);
}
const lookup = new TranslationLookup();
await lookup.loadTranslations(options.combined);
console.log(chalk.blue(`🔍 Searching for: "${query}"`));
const results = lookup.search(query);
const limit = parseInt(options.limit);
const displayResults = results.slice(0, limit);
if (displayResults.length === 0) {
console.log(chalk.yellow("No results found"));
return;
}
console.log(
chalk.green(
`\n📋 Found ${results.length} results (showing top ${displayResults.length}):\n`,
),
);
displayResults.forEach((result, index) => {
console.log(`${index + 1}. ${chalk.cyan(result.key)} (relevance: ${result.relevance})`);
console.log(` "${result.message}"`);
console.log();
});
if (results.length > limit) {
console.log(chalk.gray(`... and ${results.length - limit} more results`));
}
} catch (error) {
console.error(chalk.red("❌ Search failed:"), error);
process.exit(1);
}
});
program
.command("stats")
.description("Show statistics about combined translations")
.option(
"-c, --combined <path>",
"Path to combined translations file",
"./combined-translations.json",
)
.action(async (options) => {
try {
if (!fs.existsSync(options.combined)) {
console.error(chalk.red(`❌ Combined translations file not found: ${options.combined}`));
console.log(
chalk.gray("Run 'combine' command first to generate the combined translations file"),
);
process.exit(1);
}
const content = fs.readFileSync(options.combined, "utf-8");
const data = JSON.parse(content);
console.log(chalk.blue("📊 Translation Statistics\n"));
if (data.metadata) {
console.log(`Generated: ${new Date(data.metadata.generatedAt).toLocaleString()}`);
console.log(`Total keys: ${data.metadata.totalKeys}`);
console.log(`Conflicts: ${data.metadata.conflictCount}`);
console.log(`Sources: ${data.metadata.sources.length}\n`);
console.log(chalk.blue("📱 Source Applications:"));
data.metadata.sources.forEach((source: any) => {
console.log(` ${source.app}: ${source.keyCount} keys`);
});
} else {
const keys = Object.keys(data.translations || data);
console.log(`Total keys: ${keys.length}`);
}
} catch (error) {
console.error(chalk.red("❌ Failed to show stats:"), error);
process.exit(1);
}
});
// Simple file finder (would be replaced with proper glob in real implementation)
function findTemplateFiles(pattern: string): string[] {
// This is a simplified implementation
// In practice, you'd use the same logic as in the template CLI
return [];
}
// Handle uncaught errors
process.on("uncaughtException", (error) => {
console.error(chalk.red("❌ Uncaught Exception:"), error);
process.exit(1);
});
process.on("unhandledRejection", (reason, promise) => {
console.error(chalk.red("❌ Unhandled Rejection at:"), promise, "reason:", reason);
process.exit(1);
});
program.parse();