i18n-ai-translate
Version:
AI-powered localization CLI, Node library, and GitHub Action. Translate i18next JSON, Gettext PO, Java .properties, and iOS .strings with ChatGPT, Claude, Gemini, or local Ollama models.
86 lines • 4.15 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const glossary_1 = require("../glossary");
const fs_1 = __importDefault(require("fs"));
const os_1 = __importDefault(require("os"));
const path_1 = __importDefault(require("path"));
const writeGlossary = (content) => {
const file = path_1.default.join(fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), "i18n-glossary-")), "glossary.json");
fs_1.default.writeFileSync(file, content);
return file;
};
describe("loadGlossary", () => {
it("loads a valid glossary", () => {
const file = writeGlossary(JSON.stringify({
doNotTranslate: ["Acme"],
terms: { fr: { Account: "Compte" } },
}));
const glossary = (0, glossary_1.loadGlossary)(file);
expect(glossary.doNotTranslate).toEqual(["Acme"]);
expect(glossary.terms?.fr?.Account).toBe("Compte");
});
it("accepts an empty object", () => {
expect((0, glossary_1.loadGlossary)(writeGlossary("{}"))).toEqual({});
});
it("throws for a missing file", () => {
expect(() => (0, glossary_1.loadGlossary)("/no/such/glossary.json")).toThrow(/Could not read glossary/);
});
it("throws for invalid JSON", () => {
expect(() => (0, glossary_1.loadGlossary)(writeGlossary("{ not json"))).toThrow(/not valid JSON/);
});
it("throws when doNotTranslate is not a string array", () => {
const file = writeGlossary(JSON.stringify({ doNotTranslate: [1, 2] }));
expect(() => (0, glossary_1.loadGlossary)(file)).toThrow(/doNotTranslate/);
});
it("throws when terms is not a nested string map", () => {
const file = writeGlossary(JSON.stringify({ terms: { fr: { Account: 5 } } }));
expect(() => (0, glossary_1.loadGlossary)(file)).toThrow(/terms\.fr/);
});
it("throws when the root is not an object", () => {
expect(() => (0, glossary_1.loadGlossary)(writeGlossary("42"))).toThrow(/must be a JSON object/);
});
it("throws when terms is not an object", () => {
const file = writeGlossary(JSON.stringify({ terms: "nope" }));
expect(() => (0, glossary_1.loadGlossary)(file)).toThrow(/keyed by language code/);
});
});
describe("buildGlossaryInstructions", () => {
const glossary = {
doNotTranslate: ["Acme", "ProductX"],
terms: {
es: { Account: "Cuenta" },
fr: { Account: "Compte", Settings: "Paramètres" },
pt: { Account: "Conta" },
},
};
it("returns empty string when there is no glossary", () => {
expect((0, glossary_1.buildGlossaryInstructions)(undefined, "fr")).toBe("");
});
it("returns empty string when nothing applies to the language", () => {
expect((0, glossary_1.buildGlossaryInstructions)({ terms: { fr: {} } }, "de")).toBe("");
});
it("lists do-not-translate terms", () => {
const out = (0, glossary_1.buildGlossaryInstructions)({ doNotTranslate: ["Acme"] }, "fr");
expect(out).toContain("do not translate them");
expect(out).toContain("\"Acme\"");
});
it("includes only the matching language's forced terms", () => {
const out = (0, glossary_1.buildGlossaryInstructions)(glossary, "fr");
expect(out).toContain("\"Account\" → \"Compte\"");
expect(out).toContain("\"Settings\" → \"Paramètres\"");
// The Spanish term must not leak into the French prompt.
expect(out).not.toContain("Cuenta");
});
it("falls back to the base subtag for BCP-47 codes", () => {
const out = (0, glossary_1.buildGlossaryInstructions)(glossary, "pt-BR");
expect(out).toContain("\"Account\" → \"Conta\"");
});
it("ends in a blank line so it can be prepended to a prompt", () => {
const out = (0, glossary_1.buildGlossaryInstructions)({ doNotTranslate: ["Acme"] }, "fr");
expect(out.endsWith("\n\n")).toBe(true);
});
});
//# sourceMappingURL=glossary.spec.js.map