|
| 1 | +/** |
| 2 | + * @fileoverview |
| 3 | + * This script is designed to generate SEO-friendly content for a given HTML source. |
| 4 | + * It uses the Gemini API to translate and generate content based on the provided HTML and languages. |
| 5 | + * |
| 6 | + * Prerequisites: |
| 7 | + * - Node.js installed on your system. |
| 8 | + * - The Google Generative AI library installed: |
| 9 | + * `npm install @google/generative-ai` |
| 10 | + * - A valid Gemini API key. |
| 11 | + * |
| 12 | + * Usage: |
| 13 | + * Import and call the `translateHtml` function with the HTML source, languages array, and options. |
| 14 | + */ |
| 15 | + |
| 16 | +/** |
| 17 | + * Example for structured data translation: |
| 18 | + * |
| 19 | + * { |
| 20 | + * "selector": "script[type='application/ld+json']", |
| 21 | + * "innerHTML": { |
| 22 | + * "en": { |
| 23 | + * "@context": "http://schema.org", |
| 24 | + * "@type": "WebPage", |
| 25 | + * "name": "Basketball Betting Sportsbook | NBA, EuroLeague, NCAA & Global Leagues", |
| 26 | + * "description": "Unlock premier basketball betting at Amapola Sportsbook. Get competitive odds for NBA, EuroLeague, NCAA, and global basketball. Enjoy live betting, swift payouts, and insightful picks for an unmatched wagering experience.", |
| 27 | + * "url": "https://amapolacasino.com/sportsbook/basketball/", |
| 28 | + * "image": "https://amapolacasino.com/assets/basketball-og.jpg", |
| 29 | + * "author": { |
| 30 | + * "@type": "Organization", |
| 31 | + * "name": "AmapolaCasino" |
| 32 | + * } |
| 33 | + * }, |
| 34 | + * "es": { ... }, |
| 35 | + * "fr": { ... }, |
| 36 | + * "pt": { ... }, |
| 37 | + * "ht": { ... }, |
| 38 | + * "nl": { ... }, |
| 39 | + * "gn": { ... } |
| 40 | + * } |
| 41 | + * } |
| 42 | + */ |
| 43 | + |
| 44 | +const { GoogleGenerativeAI } = require("@google/generative-ai"); |
| 45 | +const Config = require("@cocreate/config"); |
| 46 | +const MODEL_NAME = "gemini-2.5-flash-lite"; |
| 47 | + |
| 48 | +// Send HTML to Gemini AI and get translation JSON |
| 49 | +// Exported function to generate translation object for HTML source and languages |
| 50 | +async function getApiKey(options) { |
| 51 | + if (options.apiKey) return options.apiKey; |
| 52 | + const config = await Config({ |
| 53 | + GoogleGenerativeAIApiKey: { |
| 54 | + prompt: "Enter your Google Generative AI API key: " |
| 55 | + } |
| 56 | + }); |
| 57 | + return config.GoogleGenerativeAIApiKey; |
| 58 | +} |
| 59 | + |
| 60 | +module.exports = async function translateHtml(html, languages, options = {}) { |
| 61 | + const apiKey = await getApiKey(options); |
| 62 | + if (!apiKey) |
| 63 | + throw new Error( |
| 64 | + "Google Generative AI API key is required in options.apiKey, process.env, or via prompt." |
| 65 | + ); |
| 66 | + const genAI = new GoogleGenerativeAI(apiKey); |
| 67 | + const model = |
| 68 | + options.model || genAI.getGenerativeModel({ model: MODEL_NAME }); |
| 69 | + const translationObj = await generateTranslationObject( |
| 70 | + html, |
| 71 | + model, |
| 72 | + languages |
| 73 | + ); |
| 74 | + return translationObj; |
| 75 | +}; |
| 76 | + |
| 77 | +// Update generateTranslationObject to accept only html, model, languages |
| 78 | +async function generateTranslationObject(html, model, languages) { |
| 79 | + const langList = languages.map((l) => `"${l}"`).join(", "); |
| 80 | + const prompt = ` |
| 81 | +You are an expert web localization AI. Given the following HTML file, extract all translatable content (titles, meta tags, headers, buttons, video/image alt/title, labels, aria-label, and all aria-* attributes) and generate a JSON object in the following format: |
| 82 | +
|
| 83 | +{ |
| 84 | + "translations": [ |
| 85 | + { |
| 86 | + "selector": "<css selector>", |
| 87 | + "innerHTML": { |
| 88 | + ${languages |
| 89 | + .map((l) => `\"${l}\"`) |
| 90 | + .join( |
| 91 | + ", \ |
| 92 | + " |
| 93 | + )} |
| 94 | + } |
| 95 | + }, |
| 96 | + { |
| 97 | + "selector": "<css selector>", |
| 98 | + "attributes": { |
| 99 | + "alt": { ${langList} }, |
| 100 | + "label": { ${langList} }, |
| 101 | + "aria-label": { ${langList} }, |
| 102 | + "aria-*": { ${langList} }, |
| 103 | + "title": { ${langList} } |
| 104 | + } |
| 105 | + }, |
| 106 | + // Example for structured data translation: |
| 107 | + { |
| 108 | + "selector": "script[type='application/ld+json']", |
| 109 | + "innerHTML": { |
| 110 | + "en": { "@context": "http://schema.org", "@type": "WebPage", "name": "English name", "description": "English description" }, |
| 111 | + "es": { "@context": "http://schema.org", "@type": "WebPage", "name": "Spanish name", "description": "Spanish description" }, |
| 112 | + "fr": { "@context": "http://schema.org", "@type": "WebPage", "name": "French name", "description": "French description" } |
| 113 | + // ...other languages |
| 114 | + } |
| 115 | + } |
| 116 | + // ...more selectors as needed |
| 117 | + ] |
| 118 | +} |
| 119 | +
|
| 120 | +Do not add any extra keys or key names not shown in this structure. Only use the keys: name, directory, path, content-type, translations, selector, innerHTML, attributes, alt, label, aria-label, aria-*, title, and the language codes (${languages.join( |
| 121 | + ", " |
| 122 | + )}). |
| 123 | +
|
| 124 | +For every translatable item (innerHTML and attributes), provide a translation for each language: ${languages.join( |
| 125 | + ", " |
| 126 | + )}. Do not leave any language blank. For Guarani (\"gn\"), always translate to Guarani and never leave it in English. |
| 127 | +
|
| 128 | +Only output the JSON object, do not include any explanation or extra text. |
| 129 | +
|
| 130 | +HTML: |
| 131 | +${html} |
| 132 | +`; |
| 133 | + |
| 134 | + try { |
| 135 | + const result = await model.generateContent(prompt); |
| 136 | + let jsonText = result.response.candidates[0].content.parts[0].text; |
| 137 | + jsonText = jsonText |
| 138 | + .replace(/^```json\s*([\s\S]*?)\s*```$/i, "$1") |
| 139 | + .trim(); |
| 140 | + return JSON.parse(jsonText); |
| 141 | + } catch (err) { |
| 142 | + console.error(`AI error:`, err); |
| 143 | + return null; |
| 144 | + } |
| 145 | +} |
0 commit comments