wuchale
Version:
Protobuf-like i18n from normal code
97 lines • 3.47 kB
JavaScript
// $$ cd .. && npm run test
// $$ node %f
import PO from 'pofile';
const baseURL = 'https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?key=';
const h = { 'Content-Type': 'application/json' };
// implements a queue for a sequential translation useful for vite's transform during dev
// as vite can do async transform
export default class GeminiQueue {
batches = [];
running = null;
sourceLang;
targetLang;
url;
instruction;
onComplete;
constructor(sourceLang, targetLang, apiKey, onComplete) {
if (apiKey === 'env') {
apiKey = process.env.GEMINI_API_KEY;
}
if (!apiKey) {
return;
}
this.sourceLang = sourceLang;
this.targetLang = targetLang;
this.url = `${baseURL}${apiKey}`;
this.instruction = `
You will be given the contents of a gettext .po file for a web app.
Translate each of the items from ${this.sourceLang} to ${this.targetLang}.
You can read all of the information for the items including contexts,
comments and references to get the appropriate context about each item.
Provide the same content with the only difference being that the
empty msgstr quotes should be filled with the appropriate translations,
preserving all placeholders.
The placeholder format is like the following examples:
- {0}: means arbitrary values.
- <0>something</0>: means something enclosed in some tags, like HTML tags
- <0/>: means a self closing tag, like in HTML
In all of the examples, 0 is an example for any integer.
`;
this.onComplete = onComplete;
}
prepareData(fragments) {
const po = new PO();
po.items = fragments;
return {
system_instruction: {
parts: [{ text: this.instruction }]
},
contents: [{ parts: [{ text: po.toString() }] }]
};
}
async translate(fragments) {
const data = this.prepareData(fragments);
const res = await fetch(this.url, { method: 'POST', headers: h, body: JSON.stringify(data) });
const json = await res.json();
if (json.error) {
console.error('Gemini error', json.error.code, json.error.message);
return;
}
const resText = json.candidates[0]?.content.parts[0].text;
for (const [i, item] of PO.parse(resText).items.entries()) {
if (item.msgstr[0]) {
fragments[i].msgstr = item.msgstr;
}
}
}
*getBatches() {
while (this.batches.length > 0) {
yield this.batches.pop(); // order doesn't matter, because they are given by ref
}
}
async run() {
for (const batch of this.getBatches()) {
await this.translate(batch);
}
await this.onComplete();
this.running = null;
}
add(items) {
if (!this.url) {
return;
}
let newRequest = false;
if (this.batches.length > 0) {
this.batches[0].push(...items);
}
else {
this.batches.push(items);
newRequest = true;
}
if (!this.running) {
this.running = this.run();
}
return newRequest;
}
}
//# sourceMappingURL=gemini.js.map