1
0
mirror of https://github.com/bitwarden/browser synced 2026-02-19 10:54:00 +00:00
Files
browser/scripts/migration/i18n/shared/translation-combiner.spec.ts
2025-07-28 15:57:28 +02:00

257 lines
8.5 KiB
TypeScript

import * as fs from "fs";
import * as path from "path";
import { TranslationCombiner } from "./translation-combiner";
describe("TranslationCombiner", () => {
let combiner: TranslationCombiner;
let testDir: string;
beforeEach(() => {
testDir = path.join(__dirname, "test-translations");
combiner = new TranslationCombiner(testDir);
// Create test directory structure
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true });
}
fs.mkdirSync(testDir, { recursive: true });
// Create mock translation files
createMockTranslationFiles();
});
afterEach(() => {
if (fs.existsSync(testDir)) {
fs.rmSync(testDir, { recursive: true });
}
});
function createMockTranslationFiles() {
// Browser translations
const browserDir = path.join(testDir, "apps/browser/src/_locales/en");
fs.mkdirSync(browserDir, { recursive: true });
fs.writeFileSync(
path.join(browserDir, "messages.json"),
JSON.stringify(
{
appName: { message: "Bitwarden" },
login: { message: "Log in" },
password: { message: "Password" },
browserSpecific: { message: "Browser Extension" },
},
null,
2,
),
);
// Desktop translations
const desktopDir = path.join(testDir, "apps/desktop/src/locales/en");
fs.mkdirSync(desktopDir, { recursive: true });
fs.writeFileSync(
path.join(desktopDir, "messages.json"),
JSON.stringify(
{
appName: { message: "Bitwarden" }, // Same as browser
login: { message: "Sign in" }, // Different from browser (conflict)
vault: { message: "Vault" },
desktopSpecific: { message: "Desktop Application" },
},
null,
2,
),
);
// Web translations
const webDir = path.join(testDir, "apps/web/src/locales/en");
fs.mkdirSync(webDir, { recursive: true });
fs.writeFileSync(
path.join(webDir, "messages.json"),
JSON.stringify(
{
dashboard: { message: "Dashboard" },
settings: { message: "Settings" },
webSpecific: { message: "Web Vault" },
},
null,
2,
),
);
// CLI translations
const cliDir = path.join(testDir, "apps/cli/src/locales/en");
fs.mkdirSync(cliDir, { recursive: true });
fs.writeFileSync(
path.join(cliDir, "messages.json"),
JSON.stringify(
{
version: { message: "Version" },
help: { message: "Help" },
cliSpecific: { message: "Command Line Interface" },
},
null,
2,
),
);
}
describe("combineTranslations", () => {
it("should combine translations from all applications", () => {
const result = combiner.combineTranslations();
expect(result.totalKeys).toBeGreaterThan(0);
expect(result.sources).toHaveLength(4); // browser, desktop, web, cli
expect(result.translations).toHaveProperty("appName");
expect(result.translations).toHaveProperty("browserSpecific");
expect(result.translations).toHaveProperty("desktopSpecific");
expect(result.translations).toHaveProperty("webSpecific");
expect(result.translations).toHaveProperty("cliSpecific");
});
it("should detect conflicts between applications", () => {
const result = combiner.combineTranslations();
expect(result.conflicts.length).toBeGreaterThan(0);
// Should detect the login conflict between browser and desktop
const loginConflict = result.conflicts.find((c) => c.key === "login");
expect(loginConflict).toBeDefined();
expect(loginConflict?.values).toContain("Log in");
expect(loginConflict?.values).toContain("Sign in");
});
it("should preserve first occurrence for conflicting keys", () => {
const result = combiner.combineTranslations();
// Browser is processed first, so its value should be preserved
expect(result.translations.appName.message).toBe("Bitwarden");
expect(result.translations.login.message).toBe("Log in"); // Browser version
});
it("should track source information", () => {
const result = combiner.combineTranslations();
const browserSource = result.sources.find((s) => s.app === "browser");
const desktopSource = result.sources.find((s) => s.app === "desktop");
const webSource = result.sources.find((s) => s.app === "web");
const cliSource = result.sources.find((s) => s.app === "cli");
expect(browserSource).toBeDefined();
expect(desktopSource).toBeDefined();
expect(webSource).toBeDefined();
expect(cliSource).toBeDefined();
expect(browserSource?.keyCount).toBe(4);
expect(desktopSource?.keyCount).toBe(4);
expect(webSource?.keyCount).toBe(3);
expect(cliSource?.keyCount).toBe(3);
});
});
describe("saveCombinedTranslations", () => {
it("should save combined translations with metadata", () => {
const result = combiner.combineTranslations();
const outputPath = path.join(testDir, "combined.json");
combiner.saveCombinedTranslations(result, outputPath);
expect(fs.existsSync(outputPath)).toBe(true);
const saved = JSON.parse(fs.readFileSync(outputPath, "utf-8"));
expect(saved).toHaveProperty("metadata");
expect(saved).toHaveProperty("translations");
expect(saved.metadata).toHaveProperty("generatedAt");
expect(saved.metadata).toHaveProperty("sources");
expect(saved.metadata).toHaveProperty("totalKeys");
expect(saved.metadata).toHaveProperty("conflictCount");
});
});
describe("generateCombinationReport", () => {
it("should generate a comprehensive report", () => {
const result = combiner.combineTranslations();
const report = combiner.generateCombinationReport(result);
expect(report).toContain("Translation Combination Report");
expect(report).toContain("Summary");
expect(report).toContain("Sources");
expect(report).toContain("Key Distribution");
if (result.conflicts.length > 0) {
expect(report).toContain("Conflicts");
}
});
});
describe("utility methods", () => {
it("should get translation message for existing key", () => {
const result = combiner.combineTranslations();
const message = combiner.getTranslationMessage(result.translations, "appName");
expect(message).toBe("Bitwarden");
});
it("should return null for non-existing key", () => {
const result = combiner.combineTranslations();
const message = combiner.getTranslationMessage(result.translations, "nonExistentKey");
expect(message).toBeNull();
});
it("should check if translation exists", () => {
const result = combiner.combineTranslations();
expect(combiner.hasTranslation(result.translations, "appName")).toBe(true);
expect(combiner.hasTranslation(result.translations, "nonExistentKey")).toBe(false);
});
it("should get all keys sorted", () => {
const result = combiner.combineTranslations();
const keys = combiner.getAllKeys(result.translations);
expect(keys).toBeInstanceOf(Array);
expect(keys.length).toBe(result.totalKeys);
// Should be sorted
const sortedKeys = [...keys].sort();
expect(keys).toEqual(sortedKeys);
});
it("should search keys and messages", () => {
const result = combiner.combineTranslations();
const searchResults = combiner.searchKeys(result.translations, "app");
expect(searchResults).toBeInstanceOf(Array);
expect(searchResults.length).toBeGreaterThan(0);
expect(searchResults).toContain("appName");
});
});
describe("error handling", () => {
it("should handle missing translation files gracefully", () => {
const emptyCombiner = new TranslationCombiner("/non/existent/path");
const result = emptyCombiner.combineTranslations();
expect(result.totalKeys).toBe(0);
expect(result.sources).toHaveLength(0);
expect(result.conflicts).toHaveLength(0);
});
it("should handle malformed JSON files", () => {
// Overwrite one of the existing files with malformed JSON
const badPath = path.join(testDir, "apps/browser/src/_locales/en/messages.json");
fs.writeFileSync(badPath, "{ invalid json }");
// Should not throw, but should log error
const consoleSpy = jest.spyOn(console, "error").mockImplementation();
const result = combiner.combineTranslations();
expect(result).toBeDefined();
expect(consoleSpy).toHaveBeenCalled();
consoleSpy.mockRestore();
});
});
});