automatic/test/localize.mjs

151 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
// script used to localize sdnext ui and hints to multiple languages using google gemini ai
import { GoogleGenAI, Type, ThinkingLevel } from '@google/genai';
import * as fs from 'node:fs';
import * as process from 'node:process';
const apiKey = process.env.GOOGLE_API_KEY;
const model = 'gemini-3-flash-preview';
const prompt = `## You are expert translator AI.
Translate attached JSON from English to {language}.
## Translation Rules:
- Fields \`id\`, \`label\` and \`reload\` should be preserved from original.
- Field \`localized\` should be a translated version of field \`label\`.
- If field \`localized\` is less than 3 characters, do not translate it and keep it as is.
- Field \`hint\` should be translated in-place.
- Use alphabet and writing system of the target language.
- Use terminology that is commonly used in the target language for software interfaces, especially in the context of Stable Diffusion and Generative AI.
- Do not leave non-translated words in the output, except for technical terms that do not have a widely accepted translation in the target language. In such cases, provide a transliteration or a brief explanation in parentheses.
- Ensure that all lines are present in the output, and that the output is valid JSON matching the provided schema.
`;
const languages = {
tb: 'English: Techno-Babble',
nb: 'English: For-N00bs',
hr: 'Croatian',
es: 'Spanish',
it: 'Italian',
xx: 'Esperanto',
qq: 'Latin',
fr: 'French',
de: 'German',
pt: 'Portuguese',
ru: 'Russian',
zh: 'Chinese',
ja: 'Japanese',
ko: 'Korean',
hi: 'Hindi',
ar: 'Arabic',
bn: 'Bengali',
ur: 'Urdu',
id: 'Indonesian',
vi: 'Vietnamese',
tr: 'Turkish',
sr: 'Serbian',
po: 'Polish',
he: 'Hebrew',
tlh: 'Klingon',
};
const responseSchema = {
type: Type.ARRAY,
items: {
type: Type.OBJECT,
properties: {
id: { type: Type.INTEGER, description: 'id of the item' },
label: { type: Type.STRING, description: 'original label of the item' },
localized: { type: Type.STRING, description: 'translated label of the item' },
reload: { type: Type.STRING, description: 'n/a' },
hint: { type: Type.STRING, description: 'long hint for the item' },
},
required: ['id', 'label', 'localized', 'reload', 'hint'],
},
};
async function localize() {
if (!apiKey || apiKey.length < 10) {
console.error('localize: set GOOGLE_API_KEY env variable with your API key');
process.exit();
}
const httpOptions = { timeout: 60000 };
const ai = new GoogleGenAI({ apiKey, httpOptions });
const thinkingConfig = {
includeThoughts: false,
thinkingLevel: ThinkingLevel.LOW,
};
const params = {
model,
contents: {
parts: [
{ text: 'prompt' },
{ text: 'data' },
],
},
config: {
responseMimeType: 'application/json',
thinkingConfig,
responseSchema,
},
};
console.log('params:', params);
const raw = fs.readFileSync('html/locale_en.json');
console.log('raw:', { bytes: raw.length });
const json = JSON.parse(raw);
console.log('targets:', { lang: Object.keys(languages), count: Object.keys(languages).length });
for (const index in Object.keys(languages)) {
const locale = Object.keys(languages)[index];
const lang = languages[locale];
const langPrompt = prompt.replace('{language}', lang).trim();
const output = {};
const fn = `html/locale_${locale}.json`;
if (fs.existsSync(fn)) {
console.log('skip:', { index, locale, lang, fn });
continue;
}
console.log('localize:', { index, locale, lang, fn });
const t0 = performance.now();
let allOk = true;
for (const section of Object.keys(json)) {
const keys = Object.keys(json[section]).length;
console.log(' start:', { locale, section, keys });
try {
const t1 = performance.now();
const sectionJSON = json[section];
const sectionParams = { ...params };
sectionParams.contents.parts[0].text = langPrompt;
sectionParams.contents.parts[1].text = `## JSON Data: \n${JSON.stringify(sectionJSON)}`;
const response = await ai.models.generateContent(sectionParams);
const responseJSON = JSON.parse(response.text);
const diff = Math.abs(keys - responseJSON.length);
if (diff > 1) {
console.error(' error:', { locale, section, input: keys, output: responseJSON.length });
allOk = false;
continue;
}
output[section] = JSON.parse(response.text);
const t2 = performance.now();
const kps = Math.round(1000000 * keys / (t2 - t1)) / 1000;
console.log(' end:', { locale, section, time: Math.round(t2 - t1) / 1000, kps });
} catch (err) {
allOk = false;
console.error(' error:', err);
process.exit(1);
}
// break; // for testing, remove this to process all sections
}
if (allOk) {
const txt = JSON.stringify(output, null, 2);
fs.writeFileSync(fn, txt);
}
const t3 = performance.now();
console.log(' time:', { locale, time: Math.round(t3 - t0) / 1000 });
}
}
localize();