import { describe, expect, it } from "vitest";
import { isSecretMask, maskSecret } from "@/lib/core/secret-mask";
import {
buildTranslateMessages,
buildTranslatePrompt,
COMMERCIAL_MT_PROVIDER_IDS,
classifyTranslateQuery,
FREE_MT_PROVIDER_IDS,
getTranslateService,
isCommercialProviderConfigured,
isCommercialTranslateProvider,
isFreeMtProvider,
isShortColloquialPrompt,
isTranslateProviderId,
langsFromSettings,
listSelectableProviders,
resolveTargetLangCode,
resolveTargetLangName,
resolveTranslateAgent,
targetLangDisplayName,
translateSystemTemplateIssue,
translateUserTemplateIssue,
unknownTranslatePlaceholders,
} from "@/lib/translate";
import { DEFAULT_TRANSLATE_SETTINGS } from "@/lib/translate/defaults";
describe("translate lang", () => {
it("resolves ui target from interface language", () => {
expect(resolveTargetLangCode("ui", "zh-CN")).toBe("zh-CN");
expect(resolveTargetLangCode("ui", "en")).toBe("en");
expect(resolveTargetLangCode("en", "zh-CN")).toBe("en");
expect(resolveTargetLangName("ui", "zh-CN")).toBe("Chinese");
expect(resolveTargetLangName("en", "zh-CN")).toBe("English");
});
it("maps codes and names via targetLangDisplayName", () => {
expect(targetLangDisplayName("zh-CN")).toBe("Chinese");
expect(targetLangDisplayName("zh")).toBe("Chinese");
expect(targetLangDisplayName("en")).toBe("English");
expect(targetLangDisplayName("Chinese")).toBe("Chinese");
expect(targetLangDisplayName("English")).toBe("English");
});
it("builds langs from settings", () => {
const l = langsFromSettings(
{ ...DEFAULT_TRANSLATE_SETTINGS, targetLang: "ui" },
"zh-CN",
);
expect(l.sourceLang).toBe("auto");
expect(l.targetLang).toBe("zh-CN");
expect(l.targetLangName).toBe("Chinese");
});
});
describe("translate services registry", () => {
it("registers free web engines, commercial BYOK engines, and agent", () => {
for (const id of FREE_MT_PROVIDER_IDS) {
expect(getTranslateService(id)?.kind).toBe("free-mt");
expect(getTranslateService(id)?.requireSecret).toBe(false);
}
for (const id of COMMERCIAL_MT_PROVIDER_IDS) {
expect(getTranslateService(id)?.kind).toBe("commercial-mt");
expect(getTranslateService(id)?.requireSecret).toBe(true);
}
expect(getTranslateService("agent")?.kind).toBe("agent");
expect(getTranslateService("agent")?.requireExternalConfig).toBe(true);
expect(isTranslateProviderId("deepl")).toBe(true);
expect(isCommercialTranslateProvider("deepl")).toBe(true);
expect(isFreeMtProvider("deeplx")).toBe(true);
expect(isFreeMtProvider("cnki")).toBe(true);
expect(isFreeMtProvider("agent")).toBe(false);
expect(isFreeMtProvider("deepl")).toBe(false);
});
it("settings list includes free engines, commercial BYOK engines, and agent", () => {
const ids = listSelectableProviders().map((s) => s.id);
expect(ids).toContain("googleapi");
expect(ids).toContain("tencenttransmart");
expect(ids).toContain("huoshanweb");
expect(ids).toContain("deeplx");
expect(ids).toContain("cnki");
expect(ids).toContain("deepl");
expect(ids).toContain("azure");
expect(ids).toContain("googleCloud");
expect(ids).toContain("openaiCompatible");
expect(ids).toContain("agent");
});
it("defaults to tencent transmart", () => {
expect(DEFAULT_TRANSLATE_SETTINGS.provider).toBe("tencenttransmart");
expect(DEFAULT_TRANSLATE_SETTINGS.agentId).toBe("");
expect(DEFAULT_TRANSLATE_SETTINGS.modelId).toBe("");
});
it("keeps commercial engines out of free-MT probes", () => {
for (const id of FREE_MT_PROVIDER_IDS) {
expect(isFreeMtProvider(id)).toBe(true);
}
for (const id of COMMERCIAL_MT_PROVIDER_IDS) {
expect(isFreeMtProvider(id)).toBe(false);
}
});
it("treats same-length host API key mask as configured", () => {
const masked = maskSecret("sk-secret-key");
expect(masked).toBe("*************");
expect(isSecretMask(masked)).toBe(true);
expect(isSecretMask("sk-secret")).toBe(false);
expect(
isCommercialProviderConfigured("deepl", {
apiKey: masked,
baseUrl: "",
region: "",
model: "",
}),
).toBe(true);
expect(
isCommercialProviderConfigured("deepl", {
apiKey: "",
baseUrl: "",
region: "",
model: "",
}),
).toBe(false);
expect(
isCommercialProviderConfigured("azure", {
apiKey: masked,
baseUrl: "",
region: "",
model: "",
}),
).toBe(false);
expect(
isCommercialProviderConfigured("azure", {
apiKey: masked,
baseUrl: "",
region: "eastasia",
model: "",
}),
).toBe(true);
});
});
describe("resolveTranslateAgent", () => {
it("follows default agent when agentId empty", () => {
const r = resolveTranslateAgent(
{ agentId: "", modelId: "" },
{
defaultId: "codex-1",
agents: [
{
id: "codex-1",
name: "Codex",
template: "codex-acp",
command: "codex",
args: [],
env: {},
available: true,
},
],
},
);
expect(r.agentId).toBe("codex-1");
});
it("uses pinned agentId when available", () => {
const r = resolveTranslateAgent(
{ agentId: "claude-1", modelId: "m1" },
{
defaultId: "codex-1",
agents: [
{
id: "codex-1",
name: "Codex",
template: "codex-acp",
command: "codex",
args: [],
env: {},
available: true,
},
{
id: "claude-1",
name: "Claude",
template: "claude-acp",
command: "claude",
args: [],
env: {},
available: true,
},
],
},
);
expect(r.agentId).toBe("claude-1");
expect(r.modelId).toBe("m1");
});
});
describe("translate prompts", () => {
it("splits a generic prompt into engine rules and quoted source", () => {
const text =
"The ablation study confirms the gain comes from the attention module.";
const { system, user } = buildTranslateMessages({
text,
targetLangName: "Chinese",
});
expect(system).toContain("focusing solely on translating");
expect(system).toContain("Only return the translated text");
expect(system).toContain("Chinese");
expect(system).toContain("the source language");
expect(system).toContain("OUTPUT ONLY THE TRANSLATION.");
expect(system).toContain("Transformer 架构");
expect(system).not.toContain("compact dictionary gloss");
expect(system).not.toContain(text);
expect(system).not.toContain("{{targetLang}}");
expect(user).toBe(`"""${text}"""`);
expect(classifyTranslateQuery(text)).toBe("prose");
});
it("forbids a clarifying question on a word or short phrase", () => {
expect(classifyTranslateQuery("attention")).toBe("word");
expect(classifyTranslateQuery("multi-head attention")).toBe("word");
expect(classifyTranslateQuery("注意力")).toBe("word");
expect(
classifyTranslateQuery(
"We propose a transformer architecture for sequence transduction.",
),
).toBe("prose");
for (const text of ["attention", "multi-head attention", "注意力"]) {
const { system, user } = buildTranslateMessages({
text,
targetLangName: "Chinese",
});
expect(system).toContain("compact dictionary gloss");
expect(system).toContain(
"Do not ask whether the user wants a translation",
);
expect(system).not.toMatch(/你想翻译还是解释/);
expect(user).toBe(`"""${text}"""`);
}
const sentence = buildTranslateMessages({
text: "We propose a transformer architecture for sequence transduction.",
targetLangName: "Chinese",
});
expect(sentence.system).not.toContain("compact dictionary gloss");
});
it("puts system rules first in the agent string", () => {
const prompt = buildTranslatePrompt({
text: "attention",
targetLangName: "Chinese",
page: 3,
surface: "pdf-selection",
});
expect(prompt.startsWith("OUTPUT ONLY THE TRANSLATION.")).toBe(true);
expect(prompt.indexOf("SYSTEM:")).toBeLessThan(
prompt.indexOf("USER TEXT:"),
);
expect(prompt).toContain("page 3");
expect(prompt.endsWith('"""attention"""')).toBe(true);
expect(prompt).not.toContain("User request:");
});
it("keeps a one-line custom prompt as a preference", () => {
expect(isShortColloquialPrompt("Be terse.")).toBe(true);
expect(isShortColloquialPrompt("翻译得自然一点")).toBe(true);
const { system, user } = buildTranslateMessages({
text: "Hello world",
targetLangName: "Chinese",
customPrompt: "Be terse.",
});
expect(system).toContain("OUTPUT ONLY THE TRANSLATION.");
expect(system).toContain("Transformer 架构");
expect(system).toContain("Additional preference");
expect(system).toContain("Be terse.");
expect(system.indexOf("Be terse.")).toBeLessThan(
system.indexOf("Final rule:"),
);
expect(user).toBe('"""Hello world"""');
});
it("lets a substantial custom system replace style only", () => {
const { system, user } = buildTranslateMessages({
text: "[[1]] alpha\n\n[[2]] beta",
targetLangName: "Chinese",
page: 7,
surface: "pdf-selection",
customPrompt:
"Terse literary translator. {{targetLang}} from {{sourceLang}}.\nKeep formulas.",
});
expect(system).toContain(
"Terse literary translator. Chinese from the source language.",
);
expect(system).toContain("not a chat assistant");
expect(system).toContain("OUTPUT ONLY THE TRANSLATION.");
expect(system).not.toContain("Transformer 架构");
expect(system).toContain("page 7");
expect(system).toContain("[[n]] marker");
expect(system).toContain("do not merge markers");
expect(system).not.toContain("compact dictionary gloss");
expect(user).toBe('"""[[1]] alpha\n\n[[2]] beta"""');
});
it("empty custom prompt matches the built-in prompt", () => {
const base = {
text: "Hello world",
targetLangName: "Chinese",
page: 2,
surface: "pdf-selection" as const,
};
expect(buildTranslatePrompt({ ...base, customPrompt: "" })).toBe(
buildTranslatePrompt(base),
);
const rendered = buildTranslatePrompt(base);
expect(rendered).toContain("into Chinese");
expect(rendered).not.toContain("{{targetLang}}");
expect(rendered).not.toContain("{{sourceLang}}");
});
it("rejects unknown placeholders and keeps the three known ones", () => {
expect(unknownTranslatePlaceholders("{{targetLang}} {{text}}")).toEqual([]);
expect(
unknownTranslatePlaceholders("See {{tone}} and {{ targetLang }}."),
).toEqual(["tone"]);
expect(translateSystemTemplateIssue("{{tone}}")).toEqual({
kind: "unknown",
names: ["tone"],
});
expect(translateSystemTemplateIssue("")).toBeNull();
expect(translateUserTemplateIssue("")).toBeNull();
expect(translateUserTemplateIssue("just say it")).toEqual({
kind: "missingText",
});
expect(translateUserTemplateIssue("{{text}} {{mood}}")).toEqual({
kind: "unknown",
names: ["mood"],
});
expect(translateUserTemplateIssue("<<{{ text }}>>")).toBeNull();
});
it("prefers customSystem and inserts the source through customUser", () => {
const { system, user } = buildTranslateMessages({
text: "attention",
targetLangName: "Chinese",
customPrompt: "legacy preference",
customSystem: "Be terse.",
customUser: "SRC:\n{{ text }}",
});
expect(system).toContain("Be terse.");
expect(system).not.toContain("legacy preference");
expect(system).toContain("OUTPUT ONLY THE TRANSLATION.");
expect(user).toBe("SRC:\nattention");
expect(user).not.toContain('"""');
});
it("default translate settings ship empty custom prompts", () => {
expect(DEFAULT_TRANSLATE_SETTINGS.customPrompt).toBe("");
expect(DEFAULT_TRANSLATE_SETTINGS.customSystem).toBe("");
expect(DEFAULT_TRANSLATE_SETTINGS.customUser).toBe("");
});
});