diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fefe6ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,20 @@ +Promotional +release +.env + +node_modules +.DS_Store +dist +coverage +.vscode +.idea +*.log +*.env.local +*.env.development.local +*.env.test.local +*.env.production.local +*.env.*.local +npm-debug.log* +yarn-debug.log* +yarn-error.log* + diff --git a/README.md b/README.md index 4a7299e..acc5045 100644 --- a/README.md +++ b/README.md @@ -5,20 +5,20 @@ CodeTranslateAI is a powerful Chrome extension that allows you to translate code ## ✨ Key Features - **On-the-Fly Translation:** Instantly translate code on platforms like Stack Overflow, Medium, and technical blogs. -- **Secure Backend:** Uses a serverless Cloudflare Worker so your AI API key is never exposed on the frontend. +- **Secure Serverless Backend:** Uses a Cloudflare Worker. - **Multi-Language Tabs:** Translate the same code block into multiple languages and switch between them easily. -- **Smart Caching:** Translations are cached in your browser for 10 days to reduce API calls and provide instant results for previously translated code. -- **Elegant UI:** A clean, non-intrusive UI that matches the width of the original code block and uses a tabbed layout for multiple translations. -- **Powered by Gemini:** Leverages the power of Google's Gemini AI for high-quality code translations. +- **Smart Caching:** Translations are cached in your browser for 10 days to reduce API calls and provide instant results. +- **Powered by Gemini:** Leverages Google's Gemini AI for high-quality code translations with syntax highlighting. --- ## 🔧 Tech Stack - **Frontend:** - - Plain JavaScript (ES6+) + - Modular JavaScript (ES6+) + - **esbuild** & **dotenv** (for bundling and environment variables) - HTML5 & CSS3 - - Chrome Extension APIs (`storage`, `runtime`, `scripting`) + - Chrome Extension APIs (`storage`, `activeTab`) - Shadow DOM for style isolation. - **Backend:** - Cloudflare Workers @@ -34,13 +34,13 @@ To get a local copy up and running, follow these simple steps. ### Prerequisites -You must have Node.js and npm installed on your machine. +You must have **Node.js** and **npm** installed on your machine. - [Download Node.js](https://nodejs.org/) ### ⚙️ Part 1: Backend Setup (Cloudflare Worker) -1. **Clone the Repository** (or set up your backend folder). +1. **Clone the Repository** ```sh git clone https://github.com/dineshsutihar/CodeTranslateAI.git @@ -65,7 +65,9 @@ You must have Node.js and npm installed on your machine. 5. **Set the Secret Key** - - Run the following command and paste your Gemini API key when prompted. This stores it securely on Cloudflare. + - Run the following command and paste your Gemini API key when prompted. + + ```sh npx wrangler secret put GEMINI_API_KEY @@ -75,33 +77,97 @@ You must have Node.js and npm installed on your machine. - Deploy the backend to make it live. + + ```sh npx wrangler deploy ``` - - After deployment, **copy the URL** that Wrangler provides. It will look like `https://backend..dev`. + - After deployment, **copy the URL** that Wrangler provides. ### 🖥️ Part 2: Frontend Setup (Chrome Extension) -1. **Configure the Backend URL** +1. **Navigate to the Frontend Directory** + + ```sh + cd ../frontend + ``` + +2. **Install Dependencies** + + ```sh + npm install + ``` - - Navigate to your Chrome extension folder (e.g., `my-code-translator`). - - Open the `background.js` file. - - Find the `BACKEND_URL` constant and **paste the Cloudflare Worker URL** you copied in the previous step. +3. **Configure the Backend URL** + + - In the `frontend` folder, create a new file named `.env`. + - Add the Cloudflare Worker URL you copied in the previous step to this file: + + + + ``` + # .env file + BACKEND_URL="https://your-worker-url.workers.dev" + ``` + +4. **Create the Build Configuration** + + - In the `frontend` folder, create a file named `build.js` and add the following content. This file tells our build script how to use the `.env` variable. + + ```javascript - // background.js - const BACKEND_URL = "https://backend..dev"; // PASTE YOUR URL HERE + // build.js + import esbuild from "esbuild"; + import "dotenv/config"; + + const define = {}; + for (const k in process.env) { + define[`process.env.${k}`] = JSON.stringify(process.env[k]); + } + + esbuild + .build({ + entryPoints: ["scripts/content.js", "background.js"], + bundle: true, + outdir: "dist", + define: define, + }) + .catch(() => process.exit(1)); ``` -2. **Load the Extension in Chrome** +5. **Build the Extension** + + - Run the build command to bundle your scripts and inject the environment variable. + + + + ```sh + npm run build + ``` + + This will create a `dist` folder containing your final `content.js` and `background.js` files. + +6. **Load the Extension in Chrome** - Open Google Chrome and navigate to `chrome://extensions`. - - Enable **"Developer mode"** using the toggle in the top-right corner. + - Enable **"Developer mode"**. - Click the **"Load unpacked"** button. - - Select your Chrome extension folder (the one containing `manifest.json`). + - Select your Chrome extension folder (the `frontend` folder that contains `manifest.json`). -The **CodeTranslateAI** icon should now appear in your Chrome toolbar, and the extension is ready to use\! +The **CodeTranslateAI** icon should now appear in your Chrome toolbar\! + +--- + +## 💻 Development Workflow + +1. Make any changes to your JavaScript files in the `scripts/` folder or `background.js`. +2. Run the build command in your terminal: + ```sh + npm run build + ``` +3. Go to `chrome://extensions` and click the **reload** button for the CodeTranslateAI extension. --- @@ -110,40 +176,24 @@ The **CodeTranslateAI** icon should now appear in your Chrome toolbar, and the e 1. Click the extension icon in the Chrome toolbar. 2. Select your desired target language from the dropdown menu. 3. Click the **"Enable Code Selector"** button. -4. Your cursor will change to a crosshair. Hover over any code block on a webpage and click on it. -5. A "Translating..." message will appear, followed by a new UI element containing the translated code. -6. If you translate the same block into another language, a new tab will be added to the UI. +4. Your cursor will change to a crosshair. Click on any code block on a webpage. +5. A "Translating..." message will appear, followed by the translated code in a new UI. --- ## 🐛 Debugging the Backend -If you encounter errors or the translation isn't working, the first step is to check the live logs from your Cloudflare Worker. This allows you to see what's happening on the server in real-time. - -1. **Navigate to your Backend Directory** - - - Open your terminal and change into the directory where your Cloudflare Worker code is located (e.g., `CodeTranslateAI/backend`). - -2. **Run the Tail Command** - - - Execute the following command to start streaming the logs: +If you encounter errors, check the live logs from your Cloudflare Worker. +1. **Navigate to your Backend Directory**. +2. **Run the Tail Command**: ```sh npx wrangler tail ``` - - - The terminal will connect and say `Connected to [worker-name], waiting for logs...`. - -3. **Trigger the Error** - - - With the log stream running, go to your browser and use the extension to perform an action that causes an error. - -4. **Check the Terminal** - - - Look back at your terminal. Any errors or log messages from your worker will appear instantly. Look for lines that start with `(error)`. This will give you the exact reason for the failure, such as an invalid API key or a quota issue. +3. **Trigger the Error** by using the extension in your browser and check the terminal for error messages. --- ## ⚖️ License -Distributed under the MIT License. See `LICENSE.txt` for more information. +Distributed under the MIT License. diff --git a/frontend/.env.example b/frontend/.env.example new file mode 100644 index 0000000..88bf632 --- /dev/null +++ b/frontend/.env.example @@ -0,0 +1 @@ +BACKEND_URL="https://backend.your-cloudflare-worker.workers.dev" \ No newline at end of file diff --git a/frontend/background.js b/frontend/background.js index 363c1b8..57ab698 100644 --- a/frontend/background.js +++ b/frontend/background.js @@ -1,7 +1,7 @@ -chrome.runtime.onMessage.addListener((request, sender, sendResponse) => { +chrome.runtime.onMessage.addListener((request, _, sendResponse) => { if (request.type === "TRANSLATE_CODE") { - const BACKEND_URL = "https://backend.dineshsutihar123.workers.dev"; + const BACKEND_URL = process.env.BACKEND_URL; chrome.storage.sync.get(['targetLanguage'], (result) => { const targetLanguage = result.targetLanguage || 'Java'; diff --git a/frontend/build.js b/frontend/build.js new file mode 100644 index 0000000..a27e7bb --- /dev/null +++ b/frontend/build.js @@ -0,0 +1,16 @@ +import esbuild from 'esbuild'; +import 'dotenv/config'; + +const define = {}; +for (const k in process.env) { + define[`process.env.${k}`] = JSON.stringify(process.env[k]); +} + +esbuild.build({ + entryPoints: ['scripts/content.js', 'background.js'], + bundle: true, + outdir: 'dist', + define: define, +}).catch(() => process.exit(1)); + +console.log('Build complete. Files are in the /dist folder.'); \ No newline at end of file diff --git a/frontend/content.js b/frontend/content.js deleted file mode 100644 index 7683219..0000000 --- a/frontend/content.js +++ /dev/null @@ -1,226 +0,0 @@ -let isPickerEnabled = false; -let hoveredElement = null; - -chrome.runtime.onMessage.addListener((message, sender, sendResponse) => { - if (message.type === "ENABLE_PICKER") { - if (!isPickerEnabled) enablePicker(); - } -}); - -function enablePicker() { - isPickerEnabled = true; - document.body.style.cursor = 'crosshair'; - document.addEventListener('mouseover', onMouseOver); - document.addEventListener('mouseout', onMouseOut); - document.addEventListener('click', onClick, true); -} - -function disablePicker() { - isPickerEnabled = false; - document.body.style.cursor = 'default'; - if (hoveredElement) hoveredElement.classList.remove('translator-highlight'); - document.removeEventListener('mouseover', onMouseOver); - document.removeEventListener('mouseout', onMouseOut); - document.removeEventListener('click', onClick, true); -} - -function onMouseOver(e) { - hoveredElement = e.target; - hoveredElement.classList.add('translator-highlight'); -} - -function onMouseOut(e) { - e.target.classList.remove('translator-highlight'); - hoveredElement = null; -} - -async function onClick(e) { - e.preventDefault(); - e.stopPropagation(); - - const clickedElement = e.target; - const selectedCode = clickedElement.textContent; - const cacheKey = `translation_${hashCode(selectedCode)}`; - const originalWidth = clickedElement.getBoundingClientRect().width; - disablePicker(); - - const { targetLanguage } = await chrome.storage.sync.get('targetLanguage'); - const lang = targetLanguage || 'Java'; - - const cachedData = await getFromCache(cacheKey); - if (cachedData && cachedData[lang]) { - injectOrUpdateTranslations(cachedData, clickedElement, originalWidth); - return; - } - - const loadingDiv = document.createElement('div'); - loadingDiv.className = 'translator-loading'; - loadingDiv.textContent = `Translating to ${lang}...`; - clickedElement.parentNode.insertBefore(loadingDiv, clickedElement.nextSibling); - - chrome.runtime.sendMessage({ type: "TRANSLATE_CODE", code: selectedCode }, async (response) => { - loadingDiv.remove(); - - if (response.error) { - alert(`Error: ${response.error}`); - } else if (response.translation) { - const cleanedTranslation = response.translation.replace(/```[a-z]*\n/g, '').replace(/```/g, '').trim(); - - const newData = cachedData || {}; - newData[lang] = cleanedTranslation; - await saveToCache(cacheKey, newData, 10); - - injectOrUpdateTranslations(newData, clickedElement, originalWidth); - } - }); -} - -function injectOrUpdateTranslations(translations, originalElement, width) { - const componentStyles = ` - .tab-nav { - display: flex; - border-bottom: 1px solid #ccc; - background-color: #f0f0f0; - } - .tab-link { - padding: 10px 15px; - cursor: pointer; - border: none; - background-color: transparent; - font-size: 1em; - font-weight: 500; - color: #333; - border-bottom: 3px solid transparent; - } - .tab-link:hover { - background-color: #e5e5e5; - } - .tab-link.active { - color: #007bff; - border-bottom: 3px solid #007bff; - } - .tab-content-area { - background-color: #fff; - } - .tab-content { - display: none; - } - .tab-content.active { - display: block; - } - pre { - margin: 0; - padding: 16px; - white-space: pre-wrap; - word-wrap: break-word; - background-color: #f6f8fa; - border-top: 1px solid #ddd; - margin-bottom: 10px; - } - code { - font-family: monospace; - font-size: 14px; - color: #24292e; - line-height: 1; - } - `; - - let container = originalElement.nextElementSibling; - if (!container || container.id !== 'my-code-translator-container') { - container = document.createElement('div'); - container.id = 'my-code-translator-container'; - - const shadowRoot = container.attachShadow({ mode: 'open' }); - - const styleElement = document.createElement('style'); - styleElement.textContent = componentStyles; - shadowRoot.appendChild(styleElement); - - const uiWrapper = document.createElement('div'); - shadowRoot.appendChild(uiWrapper); - - originalElement.parentNode.insertBefore(container, originalElement.nextSibling); - } - - container.style.width = `${width}px`; - container.style.boxSizing = 'border-box'; - - const shadowRoot = container.shadowRoot; - const uiWrapper = shadowRoot.querySelector('div'); - uiWrapper.innerHTML = ''; - - const tabNav = document.createElement('div'); - tabNav.className = 'tab-nav'; - - const contentArea = document.createElement('div'); - contentArea.className = 'tab-content-area'; - - uiWrapper.appendChild(tabNav); - uiWrapper.appendChild(contentArea); - - Object.keys(translations).forEach((lang, index) => { - const tabButton = document.createElement('button'); - tabButton.className = 'tab-link'; - tabButton.textContent = lang; - - const contentPanel = document.createElement('div'); - contentPanel.className = 'tab-content'; - - const pre = document.createElement('pre'); - const code = document.createElement('code'); - code.textContent = translations[lang]; - pre.appendChild(code); - contentPanel.appendChild(pre); - - tabNav.appendChild(tabButton); - contentArea.appendChild(contentPanel); - - if (index === 0) { - tabButton.classList.add('active'); - contentPanel.classList.add('active'); - } - - tabButton.addEventListener('click', () => { - shadowRoot.querySelectorAll('.tab-link').forEach(btn => btn.classList.remove('active')); - shadowRoot.querySelectorAll('.tab-content').forEach(panel => panel.classList.remove('active')); - - tabButton.classList.add('active'); - contentPanel.classList.add('active'); - }); - }); -} - -function hashCode(str) { - let hash = 0; - for (let i = 0, len = str.length; i < len; i++) { - let chr = str.charCodeAt(i); - hash = (hash << 5) - hash + chr; - hash |= 0; - } - return hash; -} - -async function saveToCache(key, data, daysToExpire) { - const expirationMs = daysToExpire * 24 * 60 * 60 * 1000; - const cacheItem = { - data: data, - expiresAt: Date.now() + expirationMs, - }; - await chrome.storage.local.set({ [key]: cacheItem }); -} - -async function getFromCache(key) { - const result = await chrome.storage.local.get(key); - const cacheItem = result[key]; - - if (!cacheItem) { - return null; - } - - if (Date.now() > cacheItem.expiresAt) { - await chrome.storage.local.remove(key); - return null; - } - - return cacheItem.data; -} \ No newline at end of file diff --git a/frontend/manifest.json b/frontend/manifest.json index d874244..a2343e8 100644 --- a/frontend/manifest.json +++ b/frontend/manifest.json @@ -1,10 +1,10 @@ { "manifest_version": 3, "name": "CodeTranslateAI", - "version": "1.0.1", - "description": "Select code on a page and get instant AI-powered translations in a clean, tabbed interface.", + "version": "1.1.0", + "description": "Select code on a page and get instant AI-powered translation.", "author": "Dinesh Kumar Sutihar", - "permissions": ["activeTab", "scripting", "storage"], + "permissions": ["activeTab", "storage"], "host_permissions": ["https://*.workers.dev/"], "action": { "default_popup": "popup.html", @@ -25,11 +25,18 @@ "content_scripts": [ { "matches": [""], - "js": ["content.js"], + "js": ["packages/prism.js", "dist/scripts/content.js"], "css": ["styles.css"] } ], "background": { - "service_worker": "background.js" - } + "service_worker": "dist/background.js", + "type": "module" + }, + "web_accessible_resources": [ + { + "resources": ["packages/prism.css"], + "matches": [""] + } + ] } diff --git a/frontend/package-lock.json b/frontend/package-lock.json new file mode 100644 index 0000000..ff3f3a0 --- /dev/null +++ b/frontend/package-lock.json @@ -0,0 +1,485 @@ +{ + "name": "code-translate-ai", + "version": "1.2.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "code-translate-ai", + "version": "1.2.0", + "devDependencies": { + "dotenv": "^17.2.1", + "esbuild": "^0.25.8" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.8.tgz", + "integrity": "sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.8.tgz", + "integrity": "sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.8.tgz", + "integrity": "sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.8.tgz", + "integrity": "sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.8.tgz", + "integrity": "sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.8.tgz", + "integrity": "sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.8.tgz", + "integrity": "sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.8.tgz", + "integrity": "sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.8.tgz", + "integrity": "sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.8.tgz", + "integrity": "sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.8.tgz", + "integrity": "sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.8.tgz", + "integrity": "sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.8.tgz", + "integrity": "sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.8.tgz", + "integrity": "sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.8.tgz", + "integrity": "sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.8.tgz", + "integrity": "sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.8.tgz", + "integrity": "sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.8.tgz", + "integrity": "sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.8.tgz", + "integrity": "sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.8.tgz", + "integrity": "sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.8.tgz", + "integrity": "sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.8.tgz", + "integrity": "sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.8.tgz", + "integrity": "sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.8.tgz", + "integrity": "sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.8.tgz", + "integrity": "sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.8.tgz", + "integrity": "sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/dotenv": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz", + "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/esbuild": { + "version": "0.25.8", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.8.tgz", + "integrity": "sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.8", + "@esbuild/android-arm": "0.25.8", + "@esbuild/android-arm64": "0.25.8", + "@esbuild/android-x64": "0.25.8", + "@esbuild/darwin-arm64": "0.25.8", + "@esbuild/darwin-x64": "0.25.8", + "@esbuild/freebsd-arm64": "0.25.8", + "@esbuild/freebsd-x64": "0.25.8", + "@esbuild/linux-arm": "0.25.8", + "@esbuild/linux-arm64": "0.25.8", + "@esbuild/linux-ia32": "0.25.8", + "@esbuild/linux-loong64": "0.25.8", + "@esbuild/linux-mips64el": "0.25.8", + "@esbuild/linux-ppc64": "0.25.8", + "@esbuild/linux-riscv64": "0.25.8", + "@esbuild/linux-s390x": "0.25.8", + "@esbuild/linux-x64": "0.25.8", + "@esbuild/netbsd-arm64": "0.25.8", + "@esbuild/netbsd-x64": "0.25.8", + "@esbuild/openbsd-arm64": "0.25.8", + "@esbuild/openbsd-x64": "0.25.8", + "@esbuild/openharmony-arm64": "0.25.8", + "@esbuild/sunos-x64": "0.25.8", + "@esbuild/win32-arm64": "0.25.8", + "@esbuild/win32-ia32": "0.25.8", + "@esbuild/win32-x64": "0.25.8" + } + } + } +} diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..167c63d --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,14 @@ +{ + "name": "code-translate-ai", + "version": "1.2.0", + "description": "Select code on a page and get instant AI-powered translations in a clean, tabbed interface.", + "author": "Dinesh Kumar Sutihar", + "type": "module", + "scripts": { + "build": "node build.js" + }, + "devDependencies": { + "dotenv": "^17.2.1", + "esbuild": "^0.25.8" + } +} diff --git a/frontend/packages/prism.css b/frontend/packages/prism.css new file mode 100644 index 0000000..6163e9f --- /dev/null +++ b/frontend/packages/prism.css @@ -0,0 +1,98 @@ +/* PrismJS 1.30.0 +https://prismjs.com/download#themes=prism-tomorrow&languages=markup+css+clike+javascript+c+cpp+go+java+python+ruby+rust+typescript+typoscript */ +code[class*="language-"], +pre[class*="language-"] { + color: #ccc; + background: 0 0; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-size: 1em; + text-align: left; + white-space: pre; + word-spacing: normal; + word-break: normal; + word-wrap: normal; + line-height: 1.5; + -moz-tab-size: 4; + -o-tab-size: 4; + tab-size: 4; + -webkit-hyphens: none; + -moz-hyphens: none; + -ms-hyphens: none; + hyphens: none; +} +pre[class*="language-"] { + padding: 1em; + margin: 0.5em 0; + overflow: auto; +} +:not(pre) > code[class*="language-"], +pre[class*="language-"] { + background: #2d2d2d; +} +:not(pre) > code[class*="language-"] { + padding: 0.1em; + border-radius: 0.3em; + white-space: normal; +} +.token.block-comment, +.token.cdata, +.token.comment, +.token.doctype, +.token.prolog { + color: #999; +} +.token.punctuation { + color: #ccc; +} +.token.attr-name, +.token.deleted, +.token.namespace, +.token.tag { + color: #e2777a; +} +.token.function-name { + color: #6196cc; +} +.token.boolean, +.token.function, +.token.number { + color: #f08d49; +} +.token.class-name, +.token.constant, +.token.property, +.token.symbol { + color: #f8c555; +} +.token.atrule, +.token.builtin, +.token.important, +.token.keyword, +.token.selector { + color: #cc99cd; +} +.token.attr-value, +.token.char, +.token.regex, +.token.string, +.token.variable { + color: #7ec699; +} +.token.entity, +.token.operator, +.token.url { + color: #67cdcc; +} +.token.bold, +.token.important { + font-weight: 700; +} +.token.italic { + font-style: italic; +} +.token.entity { + cursor: help; +} +.token.inserted { + color: green; +} diff --git a/frontend/packages/prism.js b/frontend/packages/prism.js new file mode 100644 index 0000000..e272fe7 --- /dev/null +++ b/frontend/packages/prism.js @@ -0,0 +1,16 @@ +/* PrismJS 1.30.0 +https://prismjs.com/download#themes=prism-tomorrow&languages=markup+css+clike+javascript+c+cpp+go+java+python+ruby+rust+typescript+typoscript */ +var _self = "undefined" != typeof window ? window : "undefined" != typeof WorkerGlobalScope && self instanceof WorkerGlobalScope ? self : {}, Prism = function (e) { var n = /(?:^|\s)lang(?:uage)?-([\w-]+)(?=\s|$)/i, t = 0, r = {}, a = { manual: e.Prism && e.Prism.manual, disableWorkerMessageHandler: e.Prism && e.Prism.disableWorkerMessageHandler, util: { encode: function e(n) { return n instanceof i ? new i(n.type, e(n.content), n.alias) : Array.isArray(n) ? n.map(e) : n.replace(/&/g, "&").replace(/= g.reach); A += w.value.length, w = w.next) { var P = w.value; if (n.length > e.length) return; if (!(P instanceof i)) { var E, S = 1; if (y) { if (!(E = l(b, A, e, m)) || E.index >= e.length) break; var L = E.index, O = E.index + E[0].length, C = A; for (C += w.value.length; L >= C;)C += (w = w.next).value.length; if (A = C -= w.value.length, w.value instanceof i) continue; for (var j = w; j !== n.tail && (C < O || "string" == typeof j.value); j = j.next)S++, C += j.value.length; S--, P = e.slice(A, C), E.index -= A } else if (!(E = l(b, 0, P, m))) continue; L = E.index; var N = E[0], _ = P.slice(0, L), M = P.slice(L + N.length), W = A + P.length; g && W > g.reach && (g.reach = W); var I = w.prev; if (_ && (I = u(n, I, _), A += _.length), c(n, I, S), w = u(n, I, new i(f, p ? a.tokenize(N, p) : N, k, N)), M && u(n, w, M), S > 1) { var T = { cause: f + "," + d, reach: W }; o(e, n, t, w.prev, A, T), g && T.reach > g.reach && (g.reach = T.reach) } } } } } } function s() { var e = { value: null, prev: null, next: null }, n = { value: null, prev: e, next: null }; e.next = n, this.head = e, this.tail = n, this.length = 0 } function u(e, n, t) { var r = n.next, a = { value: t, prev: n, next: r }; return n.next = a, r.prev = a, e.length++, a } function c(e, n, t) { for (var r = n.next, a = 0; a < t && r !== e.tail; a++)r = r.next; n.next = r, r.prev = n, e.length -= a } if (e.Prism = a, i.stringify = function e(n, t) { if ("string" == typeof n) return n; if (Array.isArray(n)) { var r = ""; return n.forEach((function (n) { r += e(n, t) })), r } var i = { type: n.type, content: e(n.content, t), tag: "span", classes: ["token", n.type], attributes: {}, language: t }, l = n.alias; l && (Array.isArray(l) ? Array.prototype.push.apply(i.classes, l) : i.classes.push(l)), a.hooks.run("wrap", i); var o = ""; for (var s in i.attributes) o += " " + s + '="' + (i.attributes[s] || "").replace(/"/g, """) + '"'; return "<" + i.tag + ' class="' + i.classes.join(" ") + '"' + o + ">" + i.content + "" }, !e.document) return e.addEventListener ? (a.disableWorkerMessageHandler || e.addEventListener("message", (function (n) { var t = JSON.parse(n.data), r = t.language, i = t.code, l = t.immediateClose; e.postMessage(a.highlight(i, a.languages[r], r)), l && e.close() }), !1), a) : a; var g = a.util.currentScript(); function f() { a.manual || a.highlightAll() } if (g && (a.filename = g.src, g.hasAttribute("data-manual") && (a.manual = !0)), !a.manual) { var h = document.readyState; "loading" === h || "interactive" === h && g && g.defer ? document.addEventListener("DOMContentLoaded", f) : window.requestAnimationFrame ? window.requestAnimationFrame(f) : window.setTimeout(f, 16) } return a }(_self); "undefined" != typeof module && module.exports && (module.exports = Prism), "undefined" != typeof global && (global.Prism = Prism); +Prism.languages.markup = { comment: { pattern: //, greedy: !0 }, prolog: { pattern: /<\?[\s\S]+?\?>/, greedy: !0 }, doctype: { pattern: /"'[\]]|"[^"]*"|'[^']*')+(?:\[(?:[^<"'\]]|"[^"]*"|'[^']*'|<(?!!--)|)*\]\s*)?>/i, greedy: !0, inside: { "internal-subset": { pattern: /(^[^\[]*\[)[\s\S]+(?=\]>$)/, lookbehind: !0, greedy: !0, inside: null }, string: { pattern: /"[^"]*"|'[^']*'/, greedy: !0 }, punctuation: /^$|[[\]]/, "doctype-tag": /^DOCTYPE/i, name: /[^\s<>'"]+/ } }, cdata: { pattern: //i, greedy: !0 }, tag: { pattern: /<\/?(?!\d)[^\s>\/=$<%]+(?:\s(?:\s*[^\s>\/=]+(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+(?=[\s>]))|(?=[\s/>])))+)?\s*\/?>/, greedy: !0, inside: { tag: { pattern: /^<\/?[^\s>\/]+/, inside: { punctuation: /^<\/?/, namespace: /^[^\s>\/:]+:/ } }, "special-attr": [], "attr-value": { pattern: /=\s*(?:"[^"]*"|'[^']*'|[^\s'">=]+)/, inside: { punctuation: [{ pattern: /^=/, alias: "attr-equals" }, { pattern: /^(\s*)["']|["']$/, lookbehind: !0 }] } }, punctuation: /\/?>/, "attr-name": { pattern: /[^\s>\/]+/, inside: { namespace: /^[^\s>\/:]+:/ } } } }, entity: [{ pattern: /&[\da-z]{1,8};/i, alias: "named-entity" }, /&#x?[\da-f]{1,8};/i] }, Prism.languages.markup.tag.inside["attr-value"].inside.entity = Prism.languages.markup.entity, Prism.languages.markup.doctype.inside["internal-subset"].inside = Prism.languages.markup, Prism.hooks.add("wrap", (function (a) { "entity" === a.type && (a.attributes.title = a.content.replace(/&/, "&")) })), Object.defineProperty(Prism.languages.markup.tag, "addInlined", { value: function (a, e) { var s = {}; s["language-" + e] = { pattern: /(^$)/i, lookbehind: !0, inside: Prism.languages[e] }, s.cdata = /^$/i; var t = { "included-cdata": { pattern: //i, inside: s } }; t["language-" + e] = { pattern: /[\s\S]+/, inside: Prism.languages[e] }; var n = {}; n[a] = { pattern: RegExp("(<__[^>]*>)(?:))*\\]\\]>|(?!)".replace(/__/g, (function () { return a })), "i"), lookbehind: !0, greedy: !0, inside: t }, Prism.languages.insertBefore("markup", "cdata", n) } }), Object.defineProperty(Prism.languages.markup.tag, "addAttribute", { value: function (a, e) { Prism.languages.markup.tag.inside["special-attr"].push({ pattern: RegExp("(^|[\"'\\s])(?:" + a + ")\\s*=\\s*(?:\"[^\"]*\"|'[^']*'|[^\\s'\">=]+(?=[\\s>]))", "i"), lookbehind: !0, inside: { "attr-name": /^[^\s=]+/, "attr-value": { pattern: /=[\s\S]+/, inside: { value: { pattern: /(^=\s*(["']|(?!["'])))\S[\s\S]*(?=\2$)/, lookbehind: !0, alias: [e, "language-" + e], inside: Prism.languages[e] }, punctuation: [{ pattern: /^=/, alias: "attr-equals" }, /"|'/] } } } }) } }), Prism.languages.html = Prism.languages.markup, Prism.languages.mathml = Prism.languages.markup, Prism.languages.svg = Prism.languages.markup, Prism.languages.xml = Prism.languages.extend("markup", {}), Prism.languages.ssml = Prism.languages.xml, Prism.languages.atom = Prism.languages.xml, Prism.languages.rss = Prism.languages.xml; +!function (s) { var e = /(?:"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"|'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n])*')/; s.languages.css = { comment: /\/\*[\s\S]*?\*\//, atrule: { pattern: RegExp("@[\\w-](?:[^;{\\s\"']|\\s+(?!\\s)|" + e.source + ")*?(?:;|(?=\\s*\\{))"), inside: { rule: /^@[\w-]+/, "selector-function-argument": { pattern: /(\bselector\s*\(\s*(?![\s)]))(?:[^()\s]|\s+(?![\s)])|\((?:[^()]|\([^()]*\))*\))+(?=\s*\))/, lookbehind: !0, alias: "selector" }, keyword: { pattern: /(^|[^\w-])(?:and|not|only|or)(?![\w-])/, lookbehind: !0 } } }, url: { pattern: RegExp("\\burl\\((?:" + e.source + "|(?:[^\\\\\r\n()\"']|\\\\[^])*)\\)", "i"), greedy: !0, inside: { function: /^url/i, punctuation: /^\(|\)$/, string: { pattern: RegExp("^" + e.source + "$"), alias: "url" } } }, selector: { pattern: RegExp("(^|[{}\\s])[^{}\\s](?:[^{};\"'\\s]|\\s+(?![\\s{])|" + e.source + ")*(?=\\s*\\{)"), lookbehind: !0 }, string: { pattern: e, greedy: !0 }, property: { pattern: /(^|[^-\w\xA0-\uFFFF])(?!\s)[-_a-z\xA0-\uFFFF](?:(?!\s)[-\w\xA0-\uFFFF])*(?=\s*:)/i, lookbehind: !0 }, important: /!important\b/i, function: { pattern: /(^|[^-a-z0-9])[-a-z0-9]+(?=\()/i, lookbehind: !0 }, punctuation: /[(){};:,]/ }, s.languages.css.atrule.inside.rest = s.languages.css; var t = s.languages.markup; t && (t.tag.addInlined("style", "css"), t.tag.addAttribute("style", "css")) }(Prism); +Prism.languages.clike = { comment: [{ pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0, greedy: !0 }, { pattern: /(^|[^\\:])\/\/.*/, lookbehind: !0, greedy: !0 }], string: { pattern: /(["'])(?:\\(?:\r\n|[\s\S])|(?!\1)[^\\\r\n])*\1/, greedy: !0 }, "class-name": { pattern: /(\b(?:class|extends|implements|instanceof|interface|new|trait)\s+|\bcatch\s+\()[\w.\\]+/i, lookbehind: !0, inside: { punctuation: /[.\\]/ } }, keyword: /\b(?:break|catch|continue|do|else|finally|for|function|if|in|instanceof|new|null|return|throw|try|while)\b/, boolean: /\b(?:false|true)\b/, function: /\b\w+(?=\()/, number: /\b0x[\da-f]+\b|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?/i, operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/, punctuation: /[{}[\];(),.:]/ }; +Prism.languages.javascript = Prism.languages.extend("clike", { "class-name": [Prism.languages.clike["class-name"], { pattern: /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$A-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\.(?:constructor|prototype))/, lookbehind: !0 }], keyword: [{ pattern: /((?:^|\})\s*)catch\b/, lookbehind: !0 }, { pattern: /(^|[^.]|\.\.\.\s*)\b(?:as|assert(?=\s*\{)|async(?=\s*(?:function\b|\(|[$\w\xA0-\uFFFF]|$))|await|break|case|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally(?=\s*(?:\{|$))|for|from(?=\s*(?:['"]|$))|function|(?:get|set)(?=\s*(?:[#\[$\w\xA0-\uFFFF]|$))|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)\b/, lookbehind: !0 }], function: /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*(?:\.\s*(?:apply|bind|call)\s*)?\()/, number: { pattern: RegExp("(^|[^\\w$])(?:NaN|Infinity|0[bB][01]+(?:_[01]+)*n?|0[oO][0-7]+(?:_[0-7]+)*n?|0[xX][\\dA-Fa-f]+(?:_[\\dA-Fa-f]+)*n?|\\d+(?:_\\d+)*n|(?:\\d+(?:_\\d+)*(?:\\.(?:\\d+(?:_\\d+)*)?)?|\\.\\d+(?:_\\d+)*)(?:[Ee][+-]?\\d+(?:_\\d+)*)?)(?![\\w$])"), lookbehind: !0 }, operator: /--|\+\+|\*\*=?|=>|&&=?|\|\|=?|[!=]==|<<=?|>>>?=?|[-+*/%&|^!=<>]=?|\.{3}|\?\?=?|\?\.?|[~:]/ }), Prism.languages.javascript["class-name"][0].pattern = /(\b(?:class|extends|implements|instanceof|interface|new)\s+)[\w.\\]+/, Prism.languages.insertBefore("javascript", "keyword", { regex: { pattern: RegExp("((?:^|[^$\\w\\xA0-\\uFFFF.\"'\\])\\s]|\\b(?:return|yield))\\s*)/(?:(?:\\[(?:[^\\]\\\\\r\n]|\\\\.)*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}|(?:\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.|\\[(?:[^[\\]\\\\\r\n]|\\\\.)*\\])*\\])*\\]|\\\\.|[^/\\\\\\[\r\n])+/[dgimyus]{0,7}v[dgimyus]{0,7})(?=(?:\\s|/\\*(?:[^*]|\\*(?!/))*\\*/)*(?:$|[\r\n,.;:})\\]]|//))"), lookbehind: !0, greedy: !0, inside: { "regex-source": { pattern: /^(\/)[\s\S]+(?=\/[a-z]*$)/, lookbehind: !0, alias: "language-regex", inside: Prism.languages.regex }, "regex-delimiter": /^\/|\/$/, "regex-flags": /^[a-z]+$/ } }, "function-variable": { pattern: /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*[=:]\s*(?:async\s*)?(?:\bfunction\b|(?:\((?:[^()]|\([^()]*\))*\)|(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)\s*=>))/, alias: "function" }, parameter: [{ pattern: /(function(?:\s+(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*)?\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\))/, lookbehind: !0, inside: Prism.languages.javascript }, { pattern: /(^|[^$\w\xA0-\uFFFF])(?!\s)[_$a-z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*=>)/i, lookbehind: !0, inside: Prism.languages.javascript }, { pattern: /(\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*=>)/, lookbehind: !0, inside: Prism.languages.javascript }, { pattern: /((?:\b|\s|^)(?!(?:as|async|await|break|case|catch|class|const|continue|debugger|default|delete|do|else|enum|export|extends|finally|for|from|function|get|if|implements|import|in|instanceof|interface|let|new|null|of|package|private|protected|public|return|set|static|super|switch|this|throw|try|typeof|undefined|var|void|while|with|yield)(?![$\w\xA0-\uFFFF]))(?:(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*)\(\s*|\]\s*\(\s*)(?!\s)(?:[^()\s]|\s+(?![\s)])|\([^()]*\))+(?=\s*\)\s*\{)/, lookbehind: !0, inside: Prism.languages.javascript }], constant: /\b[A-Z](?:[A-Z_]|\dx?)*\b/ }), Prism.languages.insertBefore("javascript", "string", { hashbang: { pattern: /^#!.*/, greedy: !0, alias: "comment" }, "template-string": { pattern: /`(?:\\[\s\S]|\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}|(?!\$\{)[^\\`])*`/, greedy: !0, inside: { "template-punctuation": { pattern: /^`|`$/, alias: "string" }, interpolation: { pattern: /((?:^|[^\\])(?:\\{2})*)\$\{(?:[^{}]|\{(?:[^{}]|\{[^}]*\})*\})+\}/, lookbehind: !0, inside: { "interpolation-punctuation": { pattern: /^\$\{|\}$/, alias: "punctuation" }, rest: Prism.languages.javascript } }, string: /[\s\S]+/ } }, "string-property": { pattern: /((?:^|[,{])[ \t]*)(["'])(?:\\(?:\r\n|[\s\S])|(?!\2)[^\\\r\n])*\2(?=\s*:)/m, lookbehind: !0, greedy: !0, alias: "property" } }), Prism.languages.insertBefore("javascript", "operator", { "literal-property": { pattern: /((?:^|[,{])[ \t]*)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?=\s*:)/m, lookbehind: !0, alias: "property" } }), Prism.languages.markup && (Prism.languages.markup.tag.addInlined("script", "javascript"), Prism.languages.markup.tag.addAttribute("on(?:abort|blur|change|click|composition(?:end|start|update)|dblclick|error|focus(?:in|out)?|key(?:down|up)|load|mouse(?:down|enter|leave|move|out|over|up)|reset|resize|scroll|select|slotchange|submit|unload|wheel)", "javascript")), Prism.languages.js = Prism.languages.javascript; +Prism.languages.c = Prism.languages.extend("clike", { comment: { pattern: /\/\/(?:[^\r\n\\]|\\(?:\r\n?|\n|(?![\r\n])))*|\/\*[\s\S]*?(?:\*\/|$)/, greedy: !0 }, string: { pattern: /"(?:\\(?:\r\n|[\s\S])|[^"\\\r\n])*"/, greedy: !0 }, "class-name": { pattern: /(\b(?:enum|struct)\s+(?:__attribute__\s*\(\([\s\S]*?\)\)\s*)?)\w+|\b[a-z]\w*_t\b/, lookbehind: !0 }, keyword: /\b(?:_Alignas|_Alignof|_Atomic|_Bool|_Complex|_Generic|_Imaginary|_Noreturn|_Static_assert|_Thread_local|__attribute__|asm|auto|break|case|char|const|continue|default|do|double|else|enum|extern|float|for|goto|if|inline|int|long|register|return|short|signed|sizeof|static|struct|switch|typedef|typeof|union|unsigned|void|volatile|while)\b/, function: /\b[a-z_]\w*(?=\s*\()/i, number: /(?:\b0x(?:[\da-f]+(?:\.[\da-f]*)?|\.[\da-f]+)(?:p[+-]?\d+)?|(?:\b\d+(?:\.\d*)?|\B\.\d+)(?:e[+-]?\d+)?)[ful]{0,4}/i, operator: />>=?|<<=?|->|([-+&|:])\1|[?:~]|[-+*/%&|^!=<>]=?/ }), Prism.languages.insertBefore("c", "string", { char: { pattern: /'(?:\\(?:\r\n|[\s\S])|[^'\\\r\n]){0,32}'/, greedy: !0 } }), Prism.languages.insertBefore("c", "string", { macro: { pattern: /(^[\t ]*)#\s*[a-z](?:[^\r\n\\/]|\/(?!\*)|\/\*(?:[^*]|\*(?!\/))*\*\/|\\(?:\r\n|[\s\S]))*/im, lookbehind: !0, greedy: !0, alias: "property", inside: { string: [{ pattern: /^(#\s*include\s*)<[^>]+>/, lookbehind: !0 }, Prism.languages.c.string], char: Prism.languages.c.char, comment: Prism.languages.c.comment, "macro-name": [{ pattern: /(^#\s*define\s+)\w+\b(?!\()/i, lookbehind: !0 }, { pattern: /(^#\s*define\s+)\w+\b(?=\()/i, lookbehind: !0, alias: "function" }], directive: { pattern: /^(#\s*)[a-z]+/, lookbehind: !0, alias: "keyword" }, "directive-hash": /^#/, punctuation: /##|\\(?=[\r\n])/, expression: { pattern: /\S[\s\S]*/, inside: Prism.languages.c } } } }), Prism.languages.insertBefore("c", "function", { constant: /\b(?:EOF|NULL|SEEK_CUR|SEEK_END|SEEK_SET|__DATE__|__FILE__|__LINE__|__TIMESTAMP__|__TIME__|__func__|stderr|stdin|stdout)\b/ }), delete Prism.languages.c.boolean; +!function (e) { var t = /\b(?:alignas|alignof|asm|auto|bool|break|case|catch|char|char16_t|char32_t|char8_t|class|co_await|co_return|co_yield|compl|concept|const|const_cast|consteval|constexpr|constinit|continue|decltype|default|delete|do|double|dynamic_cast|else|enum|explicit|export|extern|final|float|for|friend|goto|if|import|inline|int|int16_t|int32_t|int64_t|int8_t|long|module|mutable|namespace|new|noexcept|nullptr|operator|override|private|protected|public|register|reinterpret_cast|requires|return|short|signed|sizeof|static|static_assert|static_cast|struct|switch|template|this|thread_local|throw|try|typedef|typeid|typename|uint16_t|uint32_t|uint64_t|uint8_t|union|unsigned|using|virtual|void|volatile|wchar_t|while)\b/, n = "\\b(?!)\\w+(?:\\s*\\.\\s*\\w+)*\\b".replace(//g, (function () { return t.source })); e.languages.cpp = e.languages.extend("c", { "class-name": [{ pattern: RegExp("(\\b(?:class|concept|enum|struct|typename)\\s+)(?!)\\w+".replace(//g, (function () { return t.source }))), lookbehind: !0 }, /\b[A-Z]\w*(?=\s*::\s*\w+\s*\()/, /\b[A-Z_]\w*(?=\s*::\s*~\w+\s*\()/i, /\b\w+(?=\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>\s*::\s*\w+\s*\()/], keyword: t, number: { pattern: /(?:\b0b[01']+|\b0x(?:[\da-f']+(?:\.[\da-f']*)?|\.[\da-f']+)(?:p[+-]?[\d']+)?|(?:\b[\d']+(?:\.[\d']*)?|\B\.[\d']+)(?:e[+-]?[\d']+)?)[ful]{0,4}/i, greedy: !0 }, operator: />>=?|<<=?|->|--|\+\+|&&|\|\||[?:~]|<=>|[-+*/%&|^!=<>]=?|\b(?:and|and_eq|bitand|bitor|not|not_eq|or|or_eq|xor|xor_eq)\b/, boolean: /\b(?:false|true)\b/ }), e.languages.insertBefore("cpp", "string", { module: { pattern: RegExp('(\\b(?:import|module)\\s+)(?:"(?:\\\\(?:\r\n|[^])|[^"\\\\\r\n])*"|<[^<>\r\n]*>|' + "(?:\\s*:\\s*)?|:\\s*".replace(//g, (function () { return n })) + ")"), lookbehind: !0, greedy: !0, inside: { string: /^[<"][\s\S]+/, operator: /:/, punctuation: /\./ } }, "raw-string": { pattern: /R"([^()\\ ]{0,16})\([\s\S]*?\)\1"/, alias: "string", greedy: !0 } }), e.languages.insertBefore("cpp", "keyword", { "generic-function": { pattern: /\b(?!operator\b)[a-z_]\w*\s*<(?:[^<>]|<[^<>]*>)*>(?=\s*\()/i, inside: { function: /^\w+/, generic: { pattern: /<[\s\S]+/, alias: "class-name", inside: e.languages.cpp } } } }), e.languages.insertBefore("cpp", "operator", { "double-colon": { pattern: /::/, alias: "punctuation" } }), e.languages.insertBefore("cpp", "class-name", { "base-clause": { pattern: /(\b(?:class|struct)\s+\w+\s*:\s*)[^;{}"'\s]+(?:\s+[^;{}"'\s]+)*(?=\s*[;{])/, lookbehind: !0, greedy: !0, inside: e.languages.extend("cpp", {}) } }), e.languages.insertBefore("inside", "double-colon", { "class-name": /\b[a-z_]\w*\b(?!\s*::)/i }, e.languages.cpp["base-clause"]) }(Prism); +Prism.languages.go = Prism.languages.extend("clike", { string: { pattern: /(^|[^\\])"(?:\\.|[^"\\\r\n])*"|`[^`]*`/, lookbehind: !0, greedy: !0 }, keyword: /\b(?:break|case|chan|const|continue|default|defer|else|fallthrough|for|func|go(?:to)?|if|import|interface|map|package|range|return|select|struct|switch|type|var)\b/, boolean: /\b(?:_|false|iota|nil|true)\b/, number: [/\b0(?:b[01_]+|o[0-7_]+)i?\b/i, /\b0x(?:[a-f\d_]+(?:\.[a-f\d_]*)?|\.[a-f\d_]+)(?:p[+-]?\d+(?:_\d+)*)?i?(?!\w)/i, /(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?[\d_]+)?i?(?!\w)/i], operator: /[*\/%^!=]=?|\+[=+]?|-[=-]?|\|[=|]?|&(?:=|&|\^=?)?|>(?:>=?|=)?|<(?:<=?|=|-)?|:=|\.\.\./, builtin: /\b(?:append|bool|byte|cap|close|complex|complex(?:64|128)|copy|delete|error|float(?:32|64)|u?int(?:8|16|32|64)?|imag|len|make|new|panic|print(?:ln)?|real|recover|rune|string|uintptr)\b/ }), Prism.languages.insertBefore("go", "string", { char: { pattern: /'(?:\\.|[^'\\\r\n]){0,10}'/, greedy: !0 } }), delete Prism.languages.go["class-name"]; +!function (e) { var n = /\b(?:abstract|assert|boolean|break|byte|case|catch|char|class|const|continue|default|do|double|else|enum|exports|extends|final|finally|float|for|goto|if|implements|import|instanceof|int|interface|long|module|native|new|non-sealed|null|open|opens|package|permits|private|protected|provides|public|record(?!\s*[(){}[\]<>=%~.:,;?+\-*/&|^])|requires|return|sealed|short|static|strictfp|super|switch|synchronized|this|throw|throws|to|transient|transitive|try|uses|var|void|volatile|while|with|yield)\b/, t = "(?:[a-z]\\w*\\s*\\.\\s*)*(?:[A-Z]\\w*\\s*\\.\\s*)*", s = { pattern: RegExp("(^|[^\\w.])" + t + "[A-Z](?:[\\d_A-Z]*[a-z]\\w*)?\\b"), lookbehind: !0, inside: { namespace: { pattern: /^[a-z]\w*(?:\s*\.\s*[a-z]\w*)*(?:\s*\.)?/, inside: { punctuation: /\./ } }, punctuation: /\./ } }; e.languages.java = e.languages.extend("clike", { string: { pattern: /(^|[^\\])"(?:\\.|[^"\\\r\n])*"/, lookbehind: !0, greedy: !0 }, "class-name": [s, { pattern: RegExp("(^|[^\\w.])" + t + "[A-Z]\\w*(?=\\s+\\w+\\s*[;,=()]|\\s*(?:\\[[\\s,]*\\]\\s*)?::\\s*new\\b)"), lookbehind: !0, inside: s.inside }, { pattern: RegExp("(\\b(?:class|enum|extends|implements|instanceof|interface|new|record|throws)\\s+)" + t + "[A-Z]\\w*\\b"), lookbehind: !0, inside: s.inside }], keyword: n, function: [e.languages.clike.function, { pattern: /(::\s*)[a-z_]\w*/, lookbehind: !0 }], number: /\b0b[01][01_]*L?\b|\b0x(?:\.[\da-f_p+-]+|[\da-f_]+(?:\.[\da-f_p+-]+)?)\b|(?:\b\d[\d_]*(?:\.[\d_]*)?|\B\.\d[\d_]*)(?:e[+-]?\d[\d_]*)?[dfl]?/i, operator: { pattern: /(^|[^.])(?:<<=?|>>>?=?|->|--|\+\+|&&|\|\||::|[?:~]|[-+*/%&|^!=<>]=?)/m, lookbehind: !0 }, constant: /\b[A-Z][A-Z_\d]+\b/ }), e.languages.insertBefore("java", "string", { "triple-quoted-string": { pattern: /"""[ \t]*[\r\n](?:(?:"|"")?(?:\\.|[^"\\]))*"""/, greedy: !0, alias: "string" }, char: { pattern: /'(?:\\.|[^'\\\r\n]){1,6}'/, greedy: !0 } }), e.languages.insertBefore("java", "class-name", { annotation: { pattern: /(^|[^.])@\w+(?:\s*\.\s*\w+)*/, lookbehind: !0, alias: "punctuation" }, generics: { pattern: /<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&)|<(?:[\w\s,.?]|&(?!&))*>)*>)*>)*>/, inside: { "class-name": s, keyword: n, punctuation: /[<>(),.:]/, operator: /[?&|]/ } }, import: [{ pattern: RegExp("(\\bimport\\s+)" + t + "(?:[A-Z]\\w*|\\*)(?=\\s*;)"), lookbehind: !0, inside: { namespace: s.inside.namespace, punctuation: /\./, operator: /\*/, "class-name": /\w+/ } }, { pattern: RegExp("(\\bimport\\s+static\\s+)" + t + "(?:\\w+|\\*)(?=\\s*;)"), lookbehind: !0, alias: "static", inside: { namespace: s.inside.namespace, static: /\b\w+$/, punctuation: /\./, operator: /\*/, "class-name": /\w+/ } }], namespace: { pattern: RegExp("(\\b(?:exports|import(?:\\s+static)?|module|open|opens|package|provides|requires|to|transitive|uses|with)\\s+)(?!)[a-z]\\w*(?:\\.[a-z]\\w*)*\\.?".replace(//g, (function () { return n.source }))), lookbehind: !0, inside: { punctuation: /\./ } } }) }(Prism); +Prism.languages.python = { comment: { pattern: /(^|[^\\])#.*/, lookbehind: !0, greedy: !0 }, "string-interpolation": { pattern: /(?:f|fr|rf)(?:("""|''')[\s\S]*?\1|("|')(?:\\.|(?!\2)[^\\\r\n])*\2)/i, greedy: !0, inside: { interpolation: { pattern: /((?:^|[^{])(?:\{\{)*)\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}]|\{(?!\{)(?:[^{}])+\})+\})+\}/, lookbehind: !0, inside: { "format-spec": { pattern: /(:)[^:(){}]+(?=\}$)/, lookbehind: !0 }, "conversion-option": { pattern: /![sra](?=[:}]$)/, alias: "punctuation" }, rest: null } }, string: /[\s\S]+/ } }, "triple-quoted-string": { pattern: /(?:[rub]|br|rb)?("""|''')[\s\S]*?\1/i, greedy: !0, alias: "string" }, string: { pattern: /(?:[rub]|br|rb)?("|')(?:\\.|(?!\1)[^\\\r\n])*\1/i, greedy: !0 }, function: { pattern: /((?:^|\s)def[ \t]+)[a-zA-Z_]\w*(?=\s*\()/g, lookbehind: !0 }, "class-name": { pattern: /(\bclass\s+)\w+/i, lookbehind: !0 }, decorator: { pattern: /(^[\t ]*)@\w+(?:\.\w+)*/m, lookbehind: !0, alias: ["annotation", "punctuation"], inside: { punctuation: /\./ } }, keyword: /\b(?:_(?=\s*:)|and|as|assert|async|await|break|case|class|continue|def|del|elif|else|except|exec|finally|for|from|global|if|import|in|is|lambda|match|nonlocal|not|or|pass|print|raise|return|try|while|with|yield)\b/, builtin: /\b(?:__import__|abs|all|any|apply|ascii|basestring|bin|bool|buffer|bytearray|bytes|callable|chr|classmethod|cmp|coerce|compile|complex|delattr|dict|dir|divmod|enumerate|eval|execfile|file|filter|float|format|frozenset|getattr|globals|hasattr|hash|help|hex|id|input|int|intern|isinstance|issubclass|iter|len|list|locals|long|map|max|memoryview|min|next|object|oct|open|ord|pow|property|range|raw_input|reduce|reload|repr|reversed|round|set|setattr|slice|sorted|staticmethod|str|sum|super|tuple|type|unichr|unicode|vars|xrange|zip)\b/, boolean: /\b(?:False|None|True)\b/, number: /\b0(?:b(?:_?[01])+|o(?:_?[0-7])+|x(?:_?[a-f0-9])+)\b|(?:\b\d+(?:_\d+)*(?:\.(?:\d+(?:_\d+)*)?)?|\B\.\d+(?:_\d+)*)(?:e[+-]?\d+(?:_\d+)*)?j?(?!\w)/i, operator: /[-+%=]=?|!=|:=|\*\*?=?|\/\/?=?|<[<=>]?|>[=>]?|[&|^~]/, punctuation: /[{}[\];(),.:]/ }, Prism.languages.python["string-interpolation"].inside.interpolation.inside.rest = Prism.languages.python, Prism.languages.py = Prism.languages.python; +!function (e) { e.languages.ruby = e.languages.extend("clike", { comment: { pattern: /#.*|^=begin\s[\s\S]*?^=end/m, greedy: !0 }, "class-name": { pattern: /(\b(?:class|module)\s+|\bcatch\s+\()[\w.\\]+|\b[A-Z_]\w*(?=\s*\.\s*new\b)/, lookbehind: !0, inside: { punctuation: /[.\\]/ } }, keyword: /\b(?:BEGIN|END|alias|and|begin|break|case|class|def|define_method|defined|do|each|else|elsif|end|ensure|extend|for|if|in|include|module|new|next|nil|not|or|prepend|private|protected|public|raise|redo|require|rescue|retry|return|self|super|then|throw|undef|unless|until|when|while|yield)\b/, operator: /\.{2,3}|&\.|===||[!=]?~|(?:&&|\|\||<<|>>|\*\*|[+\-*/%<>!^&|=])=?|[?:]/, punctuation: /[(){}[\].,;]/ }), e.languages.insertBefore("ruby", "operator", { "double-colon": { pattern: /::/, alias: "punctuation" } }); var n = { pattern: /((?:^|[^\\])(?:\\{2})*)#\{(?:[^{}]|\{[^{}]*\})*\}/, lookbehind: !0, inside: { content: { pattern: /^(#\{)[\s\S]+(?=\}$)/, lookbehind: !0, inside: e.languages.ruby }, delimiter: { pattern: /^#\{|\}$/, alias: "punctuation" } } }; delete e.languages.ruby.function; var t = "(?:" + ["([^a-zA-Z0-9\\s{(\\[<=])(?:(?!\\1)[^\\\\]|\\\\[^])*\\1", "\\((?:[^()\\\\]|\\\\[^]|\\((?:[^()\\\\]|\\\\[^])*\\))*\\)", "\\{(?:[^{}\\\\]|\\\\[^]|\\{(?:[^{}\\\\]|\\\\[^])*\\})*\\}", "\\[(?:[^\\[\\]\\\\]|\\\\[^]|\\[(?:[^\\[\\]\\\\]|\\\\[^])*\\])*\\]", "<(?:[^<>\\\\]|\\\\[^]|<(?:[^<>\\\\]|\\\\[^])*>)*>"].join("|") + ")", i = '(?:"(?:\\\\.|[^"\\\\\r\n])*"|(?:\\b[a-zA-Z_]\\w*|[^\\s\0-\\x7F]+)[?!]?|\\$.)'; e.languages.insertBefore("ruby", "keyword", { "regex-literal": [{ pattern: RegExp("%r" + t + "[egimnosux]{0,6}"), greedy: !0, inside: { interpolation: n, regex: /[\s\S]+/ } }, { pattern: /(^|[^/])\/(?!\/)(?:\[[^\r\n\]]+\]|\\.|[^[/\\\r\n])+\/[egimnosux]{0,6}(?=\s*(?:$|[\r\n,.;})#]))/, lookbehind: !0, greedy: !0, inside: { interpolation: n, regex: /[\s\S]+/ } }], variable: /[@$]+[a-zA-Z_]\w*(?:[?!]|\b)/, symbol: [{ pattern: RegExp("(^|[^:]):" + i), lookbehind: !0, greedy: !0 }, { pattern: RegExp("([\r\n{(,][ \t]*)" + i + "(?=:(?!:))"), lookbehind: !0, greedy: !0 }], "method-definition": { pattern: /(\bdef\s+)\w+(?:\s*\.\s*\w+)?/, lookbehind: !0, inside: { function: /\b\w+$/, keyword: /^self\b/, "class-name": /^\w+/, punctuation: /\./ } } }), e.languages.insertBefore("ruby", "string", { "string-literal": [{ pattern: RegExp("%[qQiIwWs]?" + t), greedy: !0, inside: { interpolation: n, string: /[\s\S]+/ } }, { pattern: /("|')(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|(?!\1)[^\\#\r\n])*\1/, greedy: !0, inside: { interpolation: n, string: /[\s\S]+/ } }, { pattern: /<<[-~]?([a-z_]\w*)[\r\n](?:.*[\r\n])*?[\t ]*\1/i, alias: "heredoc-string", greedy: !0, inside: { delimiter: { pattern: /^<<[-~]?[a-z_]\w*|\b[a-z_]\w*$/i, inside: { symbol: /\b\w+/, punctuation: /^<<[-~]?/ } }, interpolation: n, string: /[\s\S]+/ } }, { pattern: /<<[-~]?'([a-z_]\w*)'[\r\n](?:.*[\r\n])*?[\t ]*\1/i, alias: "heredoc-string", greedy: !0, inside: { delimiter: { pattern: /^<<[-~]?'[a-z_]\w*'|\b[a-z_]\w*$/i, inside: { symbol: /\b\w+/, punctuation: /^<<[-~]?'|'$/ } }, string: /[\s\S]+/ } }], "command-literal": [{ pattern: RegExp("%x" + t), greedy: !0, inside: { interpolation: n, command: { pattern: /[\s\S]+/, alias: "string" } } }, { pattern: /`(?:#\{[^}]+\}|#(?!\{)|\\(?:\r\n|[\s\S])|[^\\`#\r\n])*`/, greedy: !0, inside: { interpolation: n, command: { pattern: /[\s\S]+/, alias: "string" } } }] }), delete e.languages.ruby.string, e.languages.insertBefore("ruby", "number", { builtin: /\b(?:Array|Bignum|Binding|Class|Continuation|Dir|Exception|FalseClass|File|Fixnum|Float|Hash|IO|Integer|MatchData|Method|Module|NilClass|Numeric|Object|Proc|Range|Regexp|Stat|String|Struct|Symbol|TMS|Thread|ThreadGroup|Time|TrueClass)\b/, constant: /\b[A-Z][A-Z0-9_]*(?:[?!]|\b)/ }), e.languages.rb = e.languages.ruby }(Prism); +!function (e) { for (var a = "/\\*(?:[^*/]|\\*(?!/)|/(?!\\*)|)*\\*/", t = 0; t < 2; t++)a = a.replace(//g, (function () { return a })); a = a.replace(//g, (function () { return "[^\\s\\S]" })), e.languages.rust = { comment: [{ pattern: RegExp("(^|[^\\\\])" + a), lookbehind: !0, greedy: !0 }, { pattern: /(^|[^\\:])\/\/.*/, lookbehind: !0, greedy: !0 }], string: { pattern: /b?"(?:\\[\s\S]|[^\\"])*"|b?r(#*)"(?:[^"]|"(?!\1))*"\1/, greedy: !0 }, char: { pattern: /b?'(?:\\(?:x[0-7][\da-fA-F]|u\{(?:[\da-fA-F]_*){1,6}\}|.)|[^\\\r\n\t'])'/, greedy: !0 }, attribute: { pattern: /#!?\[(?:[^\[\]"]|"(?:\\[\s\S]|[^\\"])*")*\]/, greedy: !0, alias: "attr-name", inside: { string: null } }, "closure-params": { pattern: /([=(,:]\s*|\bmove\s*)\|[^|]*\||\|[^|]*\|(?=\s*(?:\{|->))/, lookbehind: !0, greedy: !0, inside: { "closure-punctuation": { pattern: /^\||\|$/, alias: "punctuation" }, rest: null } }, "lifetime-annotation": { pattern: /'\w+/, alias: "symbol" }, "fragment-specifier": { pattern: /(\$\w+:)[a-z]+/, lookbehind: !0, alias: "punctuation" }, variable: /\$\w+/, "function-definition": { pattern: /(\bfn\s+)\w+/, lookbehind: !0, alias: "function" }, "type-definition": { pattern: /(\b(?:enum|struct|trait|type|union)\s+)\w+/, lookbehind: !0, alias: "class-name" }, "module-declaration": [{ pattern: /(\b(?:crate|mod)\s+)[a-z][a-z_\d]*/, lookbehind: !0, alias: "namespace" }, { pattern: /(\b(?:crate|self|super)\s*)::\s*[a-z][a-z_\d]*\b(?:\s*::(?:\s*[a-z][a-z_\d]*\s*::)*)?/, lookbehind: !0, alias: "namespace", inside: { punctuation: /::/ } }], keyword: [/\b(?:Self|abstract|as|async|await|become|box|break|const|continue|crate|do|dyn|else|enum|extern|final|fn|for|if|impl|in|let|loop|macro|match|mod|move|mut|override|priv|pub|ref|return|self|static|struct|super|trait|try|type|typeof|union|unsafe|unsized|use|virtual|where|while|yield)\b/, /\b(?:bool|char|f(?:32|64)|[ui](?:8|16|32|64|128|size)|str)\b/], function: /\b[a-z_]\w*(?=\s*(?:::\s*<|\())/, macro: { pattern: /\b\w+!/, alias: "property" }, constant: /\b[A-Z_][A-Z_\d]+\b/, "class-name": /\b[A-Z]\w*\b/, namespace: { pattern: /(?:\b[a-z][a-z_\d]*\s*::\s*)*\b[a-z][a-z_\d]*\s*::(?!\s*<)/, inside: { punctuation: /::/ } }, number: /\b(?:0x[\dA-Fa-f](?:_?[\dA-Fa-f])*|0o[0-7](?:_?[0-7])*|0b[01](?:_?[01])*|(?:(?:\d(?:_?\d)*)?\.)?\d(?:_?\d)*(?:[Ee][+-]?\d+)?)(?:_?(?:f32|f64|[iu](?:8|16|32|64|size)?))?\b/, boolean: /\b(?:false|true)\b/, punctuation: /->|\.\.=|\.{1,3}|::|[{}[\];(),:]/, operator: /[-+*\/%!^]=?|=[=>]?|&[&=]?|\|[|=]?|<>?=?|[@?]/ }, e.languages.rust["closure-params"].inside.rest = e.languages.rust, e.languages.rust.attribute.inside.string = e.languages.rust.string }(Prism); +!function (e) { e.languages.typescript = e.languages.extend("javascript", { "class-name": { pattern: /(\b(?:class|extends|implements|instanceof|interface|new|type)\s+)(?!keyof\b)(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*(?:\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>)?/, lookbehind: !0, greedy: !0, inside: null }, builtin: /\b(?:Array|Function|Promise|any|boolean|console|never|number|string|symbol|unknown)\b/ }), e.languages.typescript.keyword.push(/\b(?:abstract|declare|is|keyof|readonly|require)\b/, /\b(?:asserts|infer|interface|module|namespace|type)\b(?=\s*(?:[{_$a-zA-Z\xA0-\uFFFF]|$))/, /\btype\b(?=\s*(?:[\{*]|$))/), delete e.languages.typescript.parameter, delete e.languages.typescript["literal-property"]; var s = e.languages.extend("typescript", {}); delete s["class-name"], e.languages.typescript["class-name"].inside = s, e.languages.insertBefore("typescript", "function", { decorator: { pattern: /@[$\w\xA0-\uFFFF]+/, inside: { at: { pattern: /^@/, alias: "operator" }, function: /^[\s\S]+/ } }, "generic-function": { pattern: /#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*\s*<(?:[^<>]|<(?:[^<>]|<[^<>]*>)*>)*>(?=\s*\()/, greedy: !0, inside: { function: /^#?(?!\s)[_$a-zA-Z\xA0-\uFFFF](?:(?!\s)[$\w\xA0-\uFFFF])*/, generic: { pattern: /<[\s\S]+/, alias: "class-name", inside: s } } } }), e.languages.ts = e.languages.typescript }(Prism); +!function (E) { var n = /\b(?:ACT|ACTIFSUB|CARRAY|CASE|CLEARGIF|COA|COA_INT|CONSTANTS|CONTENT|CUR|EDITPANEL|EFFECT|EXT|FILE|FLUIDTEMPLATE|FORM|FRAME|FRAMESET|GIFBUILDER|GMENU|GMENU_FOLDOUT|GMENU_LAYERS|GP|HMENU|HRULER|HTML|IENV|IFSUB|IMAGE|IMGMENU|IMGMENUITEM|IMGTEXT|IMG_RESOURCE|INCLUDE_TYPOSCRIPT|JSMENU|JSMENUITEM|LLL|LOAD_REGISTER|NO|PAGE|RECORDS|RESTORE_REGISTER|TEMPLATE|TEXT|TMENU|TMENUITEM|TMENU_LAYERS|USER|USER_INT|_GIFBUILDER|global|globalString|globalVar)\b/; E.languages.typoscript = { comment: [{ pattern: /(^|[^\\])\/\*[\s\S]*?(?:\*\/|$)/, lookbehind: !0 }, { pattern: /(^|[^\\:= \t]|(?:^|[^= \t])[ \t]+)\/\/.*/, lookbehind: !0, greedy: !0 }, { pattern: /(^|[^"'])#.*/, lookbehind: !0, greedy: !0 }], function: [{ pattern: //, inside: { string: { pattern: /"[^"\r\n]*"|'[^'\r\n]*'/, inside: { keyword: n } }, keyword: { pattern: /INCLUDE_TYPOSCRIPT/ } } }, { pattern: /@import\s*(?:"[^"\r\n]*"|'[^'\r\n]*')/, inside: { string: /"[^"\r\n]*"|'[^'\r\n]*'/ } }], string: { pattern: /^([^=]*=[< ]?)(?:(?!\]\n).)*/, lookbehind: !0, inside: { function: /\{\$.*\}/, keyword: n, number: /^\d+$/, punctuation: /[,|:]/ } }, keyword: n, number: { pattern: /\b\d+\s*[.{=]/, inside: { operator: /[.{=]/ } }, tag: { pattern: /\.?[-\w\\]+\.?/, inside: { punctuation: /\./ } }, punctuation: /[{}[\];(),.:|]/, operator: /[<>]=?|[!=]=?=?|--?|\+\+?|&&?|\|\|?|[?*/~^%]/ }, E.languages.tsconfig = E.languages.typoscript }(Prism); diff --git a/frontend/scripts/cache.js b/frontend/scripts/cache.js new file mode 100644 index 0000000..a0cc5f8 --- /dev/null +++ b/frontend/scripts/cache.js @@ -0,0 +1,25 @@ +export function hashCode(str) { + let hash = 0; + for (let i = 0, len = str.length; i < len; i++) { + let chr = str.charCodeAt(i); + hash = (hash << 5) - hash + chr; + hash |= 0; + } + return hash; +} + +export async function saveToCache(key, data, daysToExpire) { + const expirationMs = daysToExpire * 24 * 60 * 60 * 1000; + const cacheItem = { data: data, expiresAt: Date.now() + expirationMs }; + await chrome.storage.local.set({ [key]: cacheItem }); +} + +export async function getFromCache(key) { + const result = await chrome.storage.local.get(key); + const cacheItem = result[key]; + if (!cacheItem || Date.now() > cacheItem.expiresAt) { + if (cacheItem) await chrome.storage.local.remove(key); + return null; + } + return cacheItem.data; +} \ No newline at end of file diff --git a/frontend/scripts/content.js b/frontend/scripts/content.js new file mode 100644 index 0000000..709555f --- /dev/null +++ b/frontend/scripts/content.js @@ -0,0 +1,57 @@ +import { enablePicker } from './picker.js'; +import { hashCode, getFromCache, saveToCache } from './cache.js'; +import { injectOrUpdateTranslations } from './ui.js'; + +chrome.runtime.onMessage.addListener((message) => { + + if (message.type === "ENABLE_PICKER") { + enablePicker(handleElementClick); + } + return true; +}); + +async function handleElementClick(e) { + e.preventDefault(); + e.stopPropagation(); + + const clickedElement = e.target; + const selectedCode = clickedElement.textContent?.trim(); + + if (!selectedCode) return; + + const cacheKey = `translation_${hashCode(selectedCode)}`; + const originalWidth = clickedElement.getBoundingClientRect().width; + const { targetLanguage } = await chrome.storage.sync.get('targetLanguage'); + const lang = targetLanguage || 'Java'; + const cachedData = await getFromCache(cacheKey); + + if (cachedData && cachedData[lang]) { + injectOrUpdateTranslations(cachedData, clickedElement, originalWidth); + return; + } + + const loadingDiv = document.createElement('div'); + loadingDiv.className = 'translator-loading'; + loadingDiv.textContent = `Translating to ${lang}...`; + loadingDiv.style.width = `${originalWidth}px`; + clickedElement.parentNode.insertBefore(loadingDiv, clickedElement.nextSibling); + + chrome.runtime.sendMessage({ type: "TRANSLATE_CODE", code: selectedCode }, async (response) => { + loadingDiv.remove(); + if (chrome.runtime.lastError || !response) { + alert(`Error: Could not connect to the translation service.`); + console.error(chrome.runtime.lastError?.message); + return; + } + + if (response.error) { + alert(`Error: ${response.error}`); + } else if (response.translation) { + const cleaned = response.translation.replace(/```[a-z]*\n/g, '').replace(/```/g, '').trim(); + const newData = cachedData || {}; + newData[lang] = cleaned; + await saveToCache(cacheKey, newData, 10); + injectOrUpdateTranslations(newData, clickedElement, originalWidth); + } + }); +} \ No newline at end of file diff --git a/frontend/scripts/picker.js b/frontend/scripts/picker.js new file mode 100644 index 0000000..bfd9012 --- /dev/null +++ b/frontend/scripts/picker.js @@ -0,0 +1,35 @@ +let hoveredElement = null; +let currentClickHandler = null; + +function onMouseOver(e) { + hoveredElement = e.target; + hoveredElement.classList.add('translator-highlight'); +} + +function onMouseOut(e) { + e.target.classList.remove('translator-highlight'); + hoveredElement = null; +} + +function disablePicker() { + document.body.style.cursor = 'default'; + if (hoveredElement) { + hoveredElement.classList.remove('translator-highlight'); + } + document.removeEventListener('mouseover', onMouseOver); + document.removeEventListener('mouseout', onMouseOut); + if (currentClickHandler) { + document.removeEventListener('click', currentClickHandler, true); + } +} + +export function enablePicker(onClickCallback) { + document.body.style.cursor = 'crosshair'; + currentClickHandler = (e) => { + disablePicker(); + onClickCallback(e); + }; + document.addEventListener('mouseover', onMouseOver); + document.addEventListener('mouseout', onMouseOut); + document.addEventListener('click', currentClickHandler, true); +} \ No newline at end of file diff --git a/frontend/scripts/ui.js b/frontend/scripts/ui.js new file mode 100644 index 0000000..e01a36c --- /dev/null +++ b/frontend/scripts/ui.js @@ -0,0 +1,110 @@ +export function injectOrUpdateTranslations(translations, originalElement, width) { + const componentStyles = ` + .tab-nav { + display: flex; + border-bottom: 1px solid #ccc; + background-color: #f0f0f0; + } + .tab-link { + padding: 10px 15px; + cursor: pointer; + border: none; + background-color: transparent; + font-size: 1em; + font-weight: 500; + color: #555; + border-bottom: 3px solid transparent; + } + .tab-link:hover { + background-color: #e5e5e5; + } + .tab-link.active { + color: #007bff; + font-weight: 600; + border-bottom: 3px solid #007bff; + } + .tab-content { + display: none; + } + .tab-content.active { + display: block; + } + pre { + margin: 0; + white-space: pre-wrap; + word-wrap: break-word; + } + code { + font-family: monospace; + font-size: 0.8em; + } + `; + + let container = originalElement.nextElementSibling; + + if (!container || container.id !== 'my-code-translator-container') { + container = document.createElement('div'); + container.id = 'my-code-translator-container'; + const shadowRoot = container.attachShadow({ mode: 'open' }); + const prismTheme = document.createElement('link'); + prismTheme.rel = 'stylesheet'; + prismTheme.href = chrome.runtime.getURL('packages/prism.css'); + shadowRoot.appendChild(prismTheme); + const styleElement = document.createElement('style'); + styleElement.textContent = componentStyles; + shadowRoot.appendChild(styleElement); + const uiWrapper = document.createElement('div'); + uiWrapper.className = 'ui-wrapper'; + shadowRoot.appendChild(uiWrapper); + originalElement.parentNode.insertBefore(container, originalElement.nextSibling); + } + + container.style.width = `${width}px`; + container.style.boxSizing = 'border-box'; + const shadowRoot = container.shadowRoot; + const uiWrapper = shadowRoot.querySelector('.ui-wrapper'); + uiWrapper.innerHTML = ''; + const tabNav = document.createElement('div'); + tabNav.className = 'tab-nav'; + const contentArea = document.createElement('div'); + contentArea.className = 'tab-content-area'; + uiWrapper.appendChild(tabNav); + uiWrapper.appendChild(contentArea); + Object.keys(translations).forEach(lang => { + const contentPanel = document.createElement('div'); + contentPanel.className = 'tab-content'; + contentPanel.dataset.lang = lang; + const langClass = `language-${lang.toLowerCase()}`; + const pre = document.createElement('pre'); + pre.className = langClass; + const code = document.createElement('code'); + code.className = langClass; + code.textContent = translations[lang]; + pre.appendChild(code); + contentPanel.appendChild(pre); + contentArea.appendChild(contentPanel); + }); + + Object.keys(translations).forEach((lang, index) => { + const tabButton = document.createElement('button'); + tabButton.className = 'tab-link'; + tabButton.textContent = lang; + tabButton.addEventListener('click', () => { + shadowRoot.querySelectorAll('.tab-link').forEach(btn => btn.classList.remove('active')); + shadowRoot.querySelectorAll('.tab-content').forEach(panel => panel.classList.remove('active')); + tabButton.classList.add('active'); + shadowRoot.querySelector(`.tab-content[data-lang="${lang}"]`).classList.add('active'); + }); + tabNav.appendChild(tabButton); + if (index === 0) { + tabButton.click(); + } + }); + try { + if (window.Prism) { + contentArea.querySelectorAll(`pre[class*="language-"]`).forEach(element => window.Prism.highlightElement(element)); + } + } catch (e) { + console.error('CodeTranslateAI: Error highlighting syntax.', e); + } +} \ No newline at end of file