UNPKG

i18n-llm-translate

Version:

Automatically translates namespace-based JSON translation files across multiple languages from any source language

180 lines (178 loc) 8.43 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __rest = (this && this.__rest) || function (s, e) { var t = {}; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) t[p] = s[p]; if (s != null && typeof Object.getOwnPropertySymbols === "function") for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) { if (e.indexOf(p[i]) < 0 && Object.prototype.propertyIsEnumerable.call(s, p[i])) t[p[i]] = s[p[i]]; } return t; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.createClaudeTranslateEngine = createClaudeTranslateEngine; const ABSOLUTE_CONTEXT = [ 'You have to output JSON and only JSON.', 'Keep all variable names and JSON structure exactly the same, only translate the values.', ]; const DEFAULT_MODEL = 'claude-3-7-sonnet-20250219'; // const DEFAULT_MODEL: ClaudeModel = 'claude-3-5-haiku-20241022'; const ClaudeModelMaxTokens = { 'claude-3-7-sonnet-20250219': 64000, //128000, <- for that extended thinking is needed 'claude-3-5-sonnet-20241022': 8192, 'claude-3-5-haiku-20241022': 8192, 'claude-3-5-sonnet-20240620': 8192, 'claude-3-opus-20240229': 4096, 'claude-3-haiku-20240307': 4096 }; function createClaudeTranslateEngine(config) { if (!config.apiKey) { throw new Error('Claude > Missing apiKey'); } const model = config.model || DEFAULT_MODEL; function fetchAnthropic(context) { return __awaiter(this, void 0, void 0, function* () { const response = yield fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'x-api-key': config.apiKey, 'anthropic-version': '2023-06-01', 'content-type': 'application/json' }, body: JSON.stringify({ model, max_tokens: ClaudeModelMaxTokens[model] || 1024, messages: [ { role: "user", content: context }, { role: "assistant", // https://github.com/anthropics/anthropic-cookbook/blob/main/misc/how_to_enable_json_mode.ipynb content: "[JSON Translator]: {" }, ] }) }); const data = yield response.json(); if ('error' in data) { throw new Error(`${data.error.type}: ${data.error.message}`); } let translations; try { // Not elegant solution, but working for something like: // At the beginning of output> Here is the JSON requested: Fixed by: // https://github.com/anthropics/anthropic-cookbook/blob/main/misc/how_to_enable_json_mode.ipynb // At the end> Note: I've kept the entire JSON structure const potentialJSON = extractBracedContent('{' + data.content[0].text); translations = JSON.parse(potentialJSON); } catch (e) { console.log('----------'); console.log('Error while parsing translations to json!'); console.log('Context:\n\n', context, '\n\n'); console.log('Claude response data:\n\n', data); console.log('----------'); throw e; } return { data: translations, input_tokens: data.usage.input_tokens, output_tokens: data.usage.output_tokens, }; }); } return { name: `Claude (${model})`, translate(translations, options) { return __awaiter(this, void 0, void 0, function* () { const languagesTranslations = {}; let input_tokens = 0; let output_tokens = 0; for (let targetLanguageCode of options.targetLanguageCodes) { const context = [ `Translate this JSON from language code ${options.baseLanguageCode} to ${targetLanguageCode}.`, ...ABSOLUTE_CONTEXT, ...options.applicationContextEntries, `Maintain professional terminology and context: ${JSON.stringify(translations)}` ].join(' '); if (options.debug) { console.log(`Claude > Fetching translations for language "${targetLanguageCode}"`); } const _a = yield fetchAnthropic(context), { data } = _a, consumption = __rest(_a, ["data"]); languagesTranslations[targetLanguageCode] = data; input_tokens += consumption.input_tokens; output_tokens += consumption.output_tokens; if (options.debug) { console.log(`Claude > Fetched translations which consumed:`, consumption); } } if (options.debug && options.targetLanguageCodes.length > 1) { console.log(`Claude > Consumed in total:`, { input_tokens, output_tokens }); } return languagesTranslations; }); }, translateMissed(missingTranslations, options) { return __awaiter(this, void 0, void 0, function* () { const context = [ ...ABSOLUTE_CONTEXT, ...options.applicationContextEntries, `Here is a translation dictionary from the language with the code "${options.baseLanguageCode}": ${missingTranslations.baseLanguageTranslations}.`, 'Next, a structure that needs to be completed will be provided.', 'The first keys in it are language codes,', 'and all translations nested under them should be in their respective languages.', 'Maintain professional terminology and context', `Translate fully next object: ${JSON.stringify(missingTranslations.targetLanguageTranslationsKeys)}` ].join(' '); if (options.debug) { console.log(`Claude > Fetching missing translations`); } const _a = yield fetchAnthropic(context), { data } = _a, consumption = __rest(_a, ["data"]); console.log(JSON.stringify(data, null, 2)); if (options.debug) { console.log(`Claude > Fetched translations which consumed:`, consumption); } return data; }); } }; } function extractBracedContent(input) { const firstIndex = input.indexOf('{'); const lastIndex = input.lastIndexOf('}'); if (firstIndex === -1 || lastIndex === -1 || firstIndex > lastIndex) { return null; } return input.substring(firstIndex, lastIndex + 1); } /* const countTokens = async (text: string) => { const response = await fetch('https://api.anthropic.com/v1/messages/count_tokens', { method: 'POST', headers: { 'x-api-key': CLAUDE_KEY, 'anthropic-version': '2023-06-01', 'content-type': 'application/json' }, body: JSON.stringify({ model: "claude-3-7-sonnet-20250219", messages: [ {role: "user", content: text} ] }) }); const data = await response.json(); console.log(data); return data.input_tokens; }*/