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.
203 lines (189 loc) • 8.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.generationPrompt = generationPrompt;
exports.failedTranslationPrompt = failedTranslationPrompt;
exports.translationVerificationPrompt = translationVerificationPrompt;
exports.stylingVerificationPrompt = stylingVerificationPrompt;
const glossary_1 = require("../glossary");
const utils_1 = require("../utils");
function buildContextPreamble(context) {
if (!context || context.trim() === "")
return "";
return `Product context: ${context.trim()}\n\n`;
}
/**
* Prompt an AI to convert a given input from one language to another
* @param inputLanguageCode - The ISO-639-1 code of the input
* @param outputLanguageCode - The ISO-639-1 code of the output
* @param input - The input to be translated
* @param options - Optional override/context knobs
* @returns A prompt for the AI to translate the input
*/
function generationPrompt(inputLanguageCode, outputLanguageCode, input, options) {
const inputLanguage = (0, utils_1.getLanguageName)(inputLanguageCode);
const outputLanguage = (0, utils_1.getLanguageName)(outputLanguageCode);
const customPrompt = options?.overridePrompt?.generationPrompt;
if (customPrompt) {
const requiredArguments = ["inputLanguage", "outputLanguage", "input"];
for (const arg of requiredArguments) {
if (!customPrompt.includes(`\${${arg}}`)) {
throw new Error(`Missing required argument: \${${arg}}`);
}
}
const argumentToValue = {
context: options?.context ?? "",
glossary: (0, glossary_1.buildGlossaryInstructions)(options?.glossary, outputLanguageCode),
input,
inputLanguage,
outputLanguage,
};
return customPrompt.replace(/\$\{([^}]+)\}/g, (match, key) => key in argumentToValue ? argumentToValue[key] : match);
}
const contextPreamble = buildContextPreamble(options?.context);
const glossaryBlock = (0, glossary_1.buildGlossaryInstructions)(options?.glossary, outputLanguageCode);
return `${contextPreamble}${glossaryBlock}You are a professional translator.
Translate each line from ${inputLanguage} to ${outputLanguage}.
Return translations in the same text formatting.
Maintain case sensitivity and whitespacing.
Output only the translations.
All lines should start and end with an ASCII quotation mark (").
\`\`\`
${input}
\`\`\`
`;
}
/**
* Prompt an AI to correct a failed translation
* @param inputLanguageCode - The ISO-639-1 code of the input
* @param outputLanguageCode - The ISO-639-1 code of the output
* @param source - The original source string that should have been translated
* @param failedOutput - The previous failed attempt at translating it
* @returns A prompt for the AI to retry the translation
*/
function failedTranslationPrompt(inputLanguageCode, outputLanguageCode, source, failedOutput) {
const inputLanguage = (0, utils_1.getLanguageName)(inputLanguageCode);
const outputLanguage = (0, utils_1.getLanguageName)(outputLanguageCode);
return `You are a professional translator.
A previous attempt to translate the following ${inputLanguage} text into ${outputLanguage} failed.
Source (${inputLanguage}):
\`\`\`
${source}
\`\`\`
Failed ${outputLanguage} output:
\`\`\`
${failedOutput}
\`\`\`
Re-translate the source into ${outputLanguage}. If the source reads like a concatenation of ${inputLanguage} words (camelCase, snake_case, or compound), split it mentally before translating. Return only the translation, wrapped in ASCII quotation marks ("). Maintain case sensitivity and whitespacing.
`;
}
/**
* Prompt an AI to ensure a translation is valid
*
* This is a single rubric that replaces the old separate accuracy and
* styling ACK/NAK prompts. The response is still text: NAK if any
* translation is incorrect on either accuracy or styling grounds,
* ACK otherwise. Merging the two prompts halves the round-trip cost
* and fixes the line-alignment fragility that showed up when one of
* the two prompts disagreed on line counts.
*
* @param inputLanguageCode - The ISO-639-1 code of the input
* @param outputLanguageCode - The ISO-639-1 code of the output
* @param input - The original input, one item per line
* @param output - The translated output, one item per line
* @param options - Optional override/context knobs
* @returns A prompt for the AI to verify the translation
*/
function translationVerificationPrompt(inputLanguageCode, outputLanguageCode, input, output, options) {
const inputLanguage = (0, utils_1.getLanguageName)(inputLanguageCode);
const outputLanguage = (0, utils_1.getLanguageName)(outputLanguageCode);
const splitInput = input.split("\n");
const splitOutput = output.split("\n");
const mergedCSV = splitInput
.map((x, i) => `${x},${splitOutput[i] ?? ""}`)
.join("\n");
const customPrompt = options?.overridePrompt?.translationVerificationPrompt;
if (customPrompt) {
const requiredArguments = [
"inputLanguage",
"outputLanguage",
"mergedCSV",
];
for (const arg of requiredArguments) {
if (!customPrompt.includes(`\${${arg}}`)) {
throw new Error(`Missing required argument: \${${arg}}`);
}
}
const argumentToValue = {
context: options?.context ?? "",
glossary: (0, glossary_1.buildGlossaryInstructions)(options?.glossary, outputLanguageCode),
inputLanguage,
mergedCSV,
outputLanguage,
};
return customPrompt.replace(/\$\{([^}]+)\}/g, (match, key) => key in argumentToValue ? argumentToValue[key] : match);
}
const contextPreamble = buildContextPreamble(options?.context);
const glossaryBlock = (0, glossary_1.buildGlossaryInstructions)(options?.glossary, outputLanguageCode);
return `${contextPreamble}${glossaryBlock}You are a translation reviewer checking a ${inputLanguage}-to-${outputLanguage} batch in CSV form.
Reply with NAK if ANY translation has a problem, including:
- Inaccurate meaning, wrong tone, or grammar errors
- Mismatched capitalization, punctuation, or whitespace vs. the original
- Missing or extra placeholders, or altered variable names
Otherwise, reply with ACK.
Reply with ACK or NAK only — no explanation.
\`\`\`
${inputLanguage},${outputLanguage}
${mergedCSV}
\`\`\`
`;
}
/**
* Legacy standalone styling prompt.
*
* Kept for backwards compatibility with custom override-prompt files
* that still reference `stylingVerificationPrompt`. New code should use
* `translationVerificationPrompt` above, which checks both accuracy and
* styling in a single pass. Calling this function without a matching
* override returns an ACK (no-op) — the merged prompt above already
* handles styling.
* @param inputLanguageCode - The ISO-639-1 code of the input
* @param outputLanguageCode - The ISO-639-1 code of the output
* @param input - The original input
* @param output - The translated output
* @param options - Optional override/context knobs
* @returns A prompt for the AI, or a sentinel indicating no standalone check
*/
function stylingVerificationPrompt(inputLanguageCode, outputLanguageCode, input, output, options) {
const inputLanguage = (0, utils_1.getLanguageName)(inputLanguageCode);
const outputLanguage = (0, utils_1.getLanguageName)(outputLanguageCode);
const splitInput = input.split("\n");
const splitOutput = output.split("\n");
const mergedCSV = splitInput
.map((x, i) => `${x},${splitOutput[i] ?? ""}`)
.join("\n");
const customPrompt = options?.overridePrompt?.stylingVerificationPrompt;
if (customPrompt) {
const requiredArguments = [
"inputLanguage",
"outputLanguage",
"mergedCSV",
];
for (const arg of requiredArguments) {
if (!customPrompt.includes(`\${${arg}}`)) {
throw new Error(`Missing required argument: \${${arg}}`);
}
}
const argumentToValue = {
context: options?.context ?? "",
inputLanguage,
mergedCSV,
outputLanguage,
};
return customPrompt.replace(/\$\{([^}]+)\}/g, (match, key) => key in argumentToValue ? argumentToValue[key] : match);
}
// No standalone styling check by default; the accuracy prompt above
// already folds in styling. Return a trivial ACK-producing prompt so
// callers that still invoke this function get a no-op.
return `Reply with ACK.`;
}
//# sourceMappingURL=prompts.js.map