UNPKG

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.

178 lines (150 loc) 6.76 kB
import { failedTranslationPrompt, generationPrompt as csvGenerationPrompt, translationVerificationPrompt as csvTranslationVerificationPrompt, } from "../generate_csv/prompts"; import { translationPromptJSON, verificationPromptJSON, } from "../generate_json/prompts"; describe("prompt builders", () => { describe("language-name expansion", () => { it("CSV generation expands en/fr to English/French", () => { const out = csvGenerationPrompt("en", "fr", '"hello"'); expect(out).toContain("from English to French"); expect(out).not.toContain("from en to fr"); }); it("JSON generation expands en/es to English/Spanish", () => { const out = translationPromptJSON("en", "es", []); expect(out).toContain("from English to Spanish"); expect(out).not.toContain("from en to es"); }); it("falls back to the raw code for unknown language codes", () => { const out = translationPromptJSON("en", "xx", []); // "xx" isn't a real ISO code, so the fallback passes it through. expect(out).toContain("to xx"); }); }); describe("context injection", () => { it("prepends a Product context line when context is provided", () => { const out = translationPromptJSON("en", "fr", [], { context: "a B2B invoicing SaaS", }); expect(out).toMatch(/^Product context: a B2B invoicing SaaS\n\n/); }); it("omits the context line when context is absent", () => { const out = translationPromptJSON("en", "fr", []); expect(out).not.toMatch(/^Product context:/); }); it("trims whitespace around the context value", () => { const out = translationPromptJSON("en", "fr", [], { context: " a music trivia game ", }); expect(out).toContain("Product context: a music trivia game\n"); }); }); describe("glossary injection", () => { const glossary = { doNotTranslate: ["Acme"], terms: { es: { Account: "Cuenta" }, fr: { Account: "Compte" } }, }; it("injects do-not-translate and the matching language's terms (JSON generation)", () => { const out = translationPromptJSON("en", "fr", [], { glossary }); expect(out).toContain("Glossary (follow strictly):"); expect(out).toContain("\"Acme\""); expect(out).toContain("\"Account\" → \"Compte\""); // The Spanish forced term must not leak into a French prompt. expect(out).not.toContain("Cuenta"); }); it("injects the glossary into the JSON verification prompt", () => { const out = verificationPromptJSON("en", "fr", [], { glossary }); expect(out).toContain("\"Account\" → \"Compte\""); }); it("injects the glossary into CSV generation", () => { const out = csvGenerationPrompt("en", "fr", '"hello"', { glossary }); expect(out).toContain("Glossary (follow strictly):"); expect(out).toContain("\"Account\" → \"Compte\""); }); it("omits the glossary block when none is provided", () => { const out = translationPromptJSON("en", "fr", []); expect(out).not.toContain("Glossary (follow strictly):"); }); }); describe("plural-suffix hints", () => { it("fires when any key ends in a CLDR plural suffix", () => { const out = translationPromptJSON("en", "fr", [], { keys: ["notifications_one", "notifications_other"], }); expect(out).toContain("CLDR plural suffixes"); }); it("does not fire for keys without plural suffixes", () => { const out = translationPromptJSON("en", "fr", [], { keys: ["welcome_message", "goodbye"], }); expect(out).not.toContain("CLDR plural suffixes"); }); it("recognises _zero, _two, _few, _many alongside _one/_other", () => { for (const suffix of [ "zero", "one", "two", "few", "many", "other", ]) { const out = translationPromptJSON("en", "fr", [], { keys: [`item_${suffix}`], }); expect(out).toContain("CLDR plural suffixes"); } }); }); describe("placeholder delimiter customisation", () => { it("references the user's configured delimiter in the {{NEWLINE}} line", () => { const out = translationPromptJSON("en", "fr", [], { templatedStringPrefix: "${", templatedStringSuffix: "}", }); expect(out).toContain("${NEWLINE}"); expect(out).not.toContain("{{NEWLINE}}"); }); it("defaults to {{...}} when no delimiter is provided", () => { const out = translationPromptJSON("en", "fr", []); expect(out).toContain("{{NEWLINE}}"); }); }); describe("failedTranslationPrompt", () => { it("includes both the source and the failed output", () => { const out = failedTranslationPrompt( "en", "fr", "welcomeMessage", "welcomeMessage", ); // Source is distinguished from failed output by its heading. expect(out).toMatch(/Source \(English\):/); expect(out).toMatch(/Failed French output:/); expect(out).toContain("welcomeMessage"); }); it("expands the language codes to names", () => { const out = failedTranslationPrompt("en", "fr", "a", "b"); expect(out).toContain("English"); expect(out).toContain("French"); expect(out).not.toContain("[en]"); expect(out).not.toContain("[fr]"); }); }); describe("verificationPromptJSON", () => { it("contains the 'do not revise correct translations' instruction", () => { const out = verificationPromptJSON("en", "fr", []); expect(out).toMatch(/Do not revise correct translations/); }); }); describe("CSV translationVerificationPrompt", () => { it("now checks both accuracy and styling in one pass", () => { const out = csvTranslationVerificationPrompt("en", "fr", "a", "b"); expect(out).toMatch(/Inaccurate meaning/); expect(out).toMatch(/capitalization, punctuation, or whitespace/); }); }); });