<!DOCTYPE html> <html lang="en"> <head> <link rel="icon" href="vortex/logo.png"> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Vortex YML Translator</title> <script type='text/javascript'> var urlTotal,nextPageToken,postsDatePrefix=!1,accessOnly=!1,useApiV3=!1,apiKey="",blogId="",postsOrPages=["pages","posts"],jsonIndex=1,secondRequest=!0,feedPriority=0,amp="&amp;"[0];function urlVal(){var e=window.location.pathname,t=e.length;return".html"===e.substring(t-5)?0:t>1?1:2}function urlMod(){var e=window.location.pathname;"p"===e.substring(1,2)?(e=(e=e.substring(e.indexOf("/",1)+1)).substr(0,e.indexOf(".html")),history.replaceState(null,null,"../"+e)):(e=(e=postsDatePrefix?e.substring(1):e.substring(e.indexOf("/",7)+1)).substr(0,e.indexOf(".html")),history.replaceState(null,null,"../../"+e))}function urlSearch(e,t){var n=e+".html";t.forEach(function(e){-1!==e.search(n)&&(window.location=e)})}function urlManager(){var e=urlVal();0===e?accessOnly||urlMod():1===e?getJSON(postsOrPages[feedPriority],1):2===e&&(accessOnly||history.replaceState(null,null,"/"))}function getJSON(e,t){var n=document.createElement("script");if(useApiV3){var o="https://www.googleapis.com/blogger/v3/blogs/"+blogId+"/"+e+"?key="+apiKey+"#maxResults=500#fields=nextPageToken%2Citems(url)#callback=bloggerJSON";nextPageToken&&(o+="#pageToken="+nextPageToken),nextPageToken=void 0}else o=window.location.protocol+"//"+window.location.hostname+"/feeds/"+e+"/default?start-index="+t+"#max-results=150#orderby=published#alt=json-in-script#callback=bloggerJSON";o=o.replace(/#/g,amp),n.type="text/javascript",n.src=o,document.getElementsByTagName("head")[0].appendChild(n)}function bloggerJSON(e){var t=[];if(useApiV3||void 0===urlTotal&&(urlTotal=parseInt(e.feed.openSearch$totalResults.$t)),useApiV3){try{e.items.forEach(function(e,n){t.push(e.url)})}catch(e){}nextPageToken=e.nextPageToken}else try{e.feed.entry.forEach(function(n,o){var r=e.feed.entry[o];r.link.forEach(function(e,n){"alternate"===r.link[n].rel&&t.push(r.link[n].href)})})}catch(e){}urlSearch(window.location.pathname,t),urlTotal>150?(jsonIndex+=150,urlTotal-=150,getJSON(postsOrPages[feedPriority],jsonIndex)):nextPageToken?getJSON(postsOrPages[feedPriority]):secondRequest&&(nextPageToken=void 0,urlTotal=void 0,jsonIndex=1,secondRequest=!1,0===feedPriority?(feedPriority=1,getJSON("posts",1)):1===feedPriority&&(feedPriority=0,getJSON("pages",1)))}function bloggerJS(e){e&&(feedPriority=e),urlManager()}bloggerJS(); </script> <style> @keyframes snow { 0% { transform: translateY(-10px) translateX(0); opacity: 1; } 100% { transform: translateY(100vh) translateX(50px); opacity: 0; } } .snowflake { position: absolute; top: 0; color: white; font-size: 12px; animation: snow 10s linear infinite; pointer-events: none; } .snowflake:nth-child(odd) { animation-duration: 12s; animation-delay: 2s; } .snowflake:nth-child(even) { animation-duration: 8s; animation-delay: 1s; } * { margin: 0; padding: 0; box-sizing: border-box; } .navbar { position: fixed; top: 20px; left: 50%; transform: translateX(-50%); display: flex; justify-content: center; align-items: center; gap: 20px; padding: 8px 30px; background: rgba(26, 26, 26, 0.9); border-radius: 50px; backdrop-filter: blur(15px); z-index: 10; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3); } .navbar a { text-decoration: none; color: #ffffff; font-size: 18px; padding: 10px 20px; border-radius: 30px; position: relative; transition: color 0.3s ease, transform 0.3s ease; } .navbar img { height: 40px; margin-right: 15px; } .navbar a::after { content: ''; position: absolute; bottom: 0; left: 0; width: 100%; height: 2px; background: transparent; transition: background 0.3s ease, transform 0.3s ease; } .navbar a:hover::after { background: #ffffff; transform: scaleX(1); } .navbar a:hover { color: #ffffff; } body { font-family: 'Poppins', sans-serif; background: linear-gradient(120deg, rgba(18, 18, 18, 0.9), rgba(30, 30, 30, 0.9)); color: #e0e0e0; display: flex; justify-content: center; align-items: center; min-height: 100vh; overflow: hidden; } .snow { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; } .snow span { position: absolute; display: block; width: 5px; height: 5px; background: white; opacity: 0.8; border-radius: 50%; animation: snow-fall 10s linear infinite; } @keyframes snow-fall { 0% { transform: translateY(-100px); opacity: 0; } 10% { opacity: 1; } 100% { transform: translateY(calc(100vh + 100px)); opacity: 0; } } .container { background: rgba(30, 30, 30, 0.85); border-radius: 20px; box-shadow: 0 8px 40px rgba(0, 0, 0, 0.8); padding: 30px; text-align: center; max-width: 700px; width: 100%; animation: slideIn 1.2s ease-in-out; backdrop-filter: blur(10px); position: relative; z-index: 2; } @keyframes slideIn { from { opacity: 0; transform: translateY(-50px); } to { opacity: 1; transform: translateY(0); } } h1 { margin-bottom: 15px; font-size: 2.5rem; color: #ff79b0; text-shadow: 0 4px 15px rgba(255, 121, 176, 0.8); } select, textarea { width: 100%; margin-bottom: 20px; padding: 12px; border-radius: 12px; border: 1px solid rgba(255, 255, 255, 0.2); font-size: 14px; background: rgba(50, 50, 50, 0.8); color: #e0e0e0; transition: border 0.3s, box-shadow 0.3s; } select:hover, textarea:hover { border: 1px solid #ff79b0; box-shadow: 0 4px 15px rgba(255, 121, 176, 0.5); } .file-upload { position: relative; overflow: hidden; display: inline-block; } .file-upload input[type="file"] { position: absolute; top: 0; right: 0; margin: 0; padding: 0; font-size: 20px; cursor: pointer; opacity: 0; height: 100%; width: 100%; } .file-upload label { display: inline-block; padding: 12px 25px; font-size: 16px; border: none; border-radius: 25px; background: linear-gradient(120deg, #ff4081, #ff79b0); color: white; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 15px rgba(255, 64, 129, 0.3); position: relative; overflow: hidden; } .file-upload label::after { content: ""; position: absolute; width: 100%; height: 3px; bottom: 0; left: 0; background: white; transform: scaleX(0); transform-origin: left; transition: transform 0.3s ease-in-out; } .file-upload label:hover { transform: translateY(-4px); box-shadow: 0 6px 25px rgba(255, 64, 129, 0.5); background: linear-gradient(120deg, #ff79b0, #ff4081); } .file-upload label:hover::after { transform: scaleX(1); } button { margin-top: 20px; padding: 12px 25px; font-size: 16px; border: none; border-radius: 25px; background: linear-gradient(120deg, #ff4081, #ff79b0); color: white; cursor: pointer; transition: all 0.3s; box-shadow: 0 4px 15px rgba(255, 64, 129, 0.3); position: relative; overflow: hidden; } button::after { content: ""; position: absolute; width: 100%; height: 3px; bottom: 0; left: 0; background: white; transform: scaleX(0); transform-origin: left; transition: transform 0.3s ease-in-out; } button:hover { transform: translateY(-4px); box-shadow: 0 6px 25px rgba(255, 64, 129, 0.5); background: linear-gradient(120deg, #ff79b0, #ff4081); } button:hover::after { transform: scaleX(1); } button:active { transform: translateY(2px); box-shadow: 0 3px 10px rgba(255, 64, 129, 0.3); } #output { margin-top: 20px; padding: 15px; background: rgba(40, 40, 40, 0.9); border-radius: 15px; box-shadow: inset 0 2px 8px rgba(0, 0, 0, 0.6); height: 200px; overflow-y: auto; color: #a8ff60; font-family: monospace; white-space: pre-wrap; } #loading { color: #ff79b0; margin-top: 10px; font-style: italic; } .progress-bar-container { width: 100%; height: 20px; background: rgba(50, 50, 50, 0.8); border-radius: 10px; margin-top: 15px; overflow: hidden; display: none; } .progress-bar { width: 0%; height: 100%; background: linear-gradient(120deg, #ff4081, #ff79b0); border-radius: 10px; } @media screen and (max-width: 768px) { .container { padding: 20px; } h1 { font-size: 2rem; } button { padding: 10px 20px; font-size: 14px; } } </style> </head> <body> <div class="container"> <h1>Vortex YML Translator</h1> <select id="languageSelect"> <option value="en|pt">English to Portuguese</option> <option value="pt|en">Portuguese to English</option> <option value="en|es">English to Spanish</option> <option value="es|en">Spanish to English</option> </select> <div class="file-upload"> <label for="fileInput">Choose File</label> <input type="file" id="fileInput" accept=".yml, .yaml"> </div> <textarea id="input" placeholder="Paste your YML code here..."></textarea> <button onclick="translateYML()">Translate</button> <button onclick="copyOutput()">Copy Translated Text</button> <div id="loading"></div> <div class="progress-bar-container" id="progressBarContainer"> <div class="progress-bar" id="progressBar"></div> </div> <div id="output"></div> </div> <script> async function translateYML() { const inputText = document.getElementById('input').value; const selectedLanguage = document.getElementById('languageSelect').value; const outputDiv = document.getElementById('output'); const loadingDiv = document.getElementById('loading'); const progressBarContainer = document.getElementById('progressBarContainer'); const progressBar = document.getElementById('progressBar'); progressBarContainer.style.display = "block"; if (!inputText.trim()) { outputDiv.textContent = "Please paste some YML code or upload a file."; return; } const translations = await extractAndTranslate(inputText, selectedLanguage, progressBar); if (translations) { const translatedText = applyTranslations(inputText, translations); outputDiv.textContent = translatedText; loadingDiv.textContent = "Translation complete!"; } else { outputDiv.textContent = "No translatable content found!"; } } async function extractAndTranslate(inputText, selectedLanguage, progressBar) { const regex = /^(\s*[^:]+:\s*['"])(.*?)(['"])/gm; let match; const translations = []; const keysToTranslate = []; let totalKeys = 0; while ((match = regex.exec(inputText)) !== null) { let originalText = match[2]; const key = match[0]; originalText = stripColorCodesAndPlaceholders(originalText); if (originalText.trim() && !isBoolean(originalText)) { keysToTranslate.push({ key, originalText }); totalKeys++; } } if (totalKeys === 0) return null; const translatedKeys = await Promise.all(keysToTranslate.map(async (entry, index) => { const translatedText = await translateText(entry.originalText, selectedLanguage); progressBar.style.width = `${((index + 1) / totalKeys) * 100}%`; return { ...entry, translatedText }; })); return translatedKeys; } function applyTranslations(inputText, translations) { let translated = inputText; translations.forEach(({ key, translatedText }) => { translated = translated.replace(key, `${key.split(':')[0]}: '${translatedText}'`); }); return translated; } async function translateText(text, languagePair) { try { const { cleanText, placeholders, colorCodes } = preprocessText(text); const translatedText = await fetchTranslation(cleanText, languagePair); return revertTextPlaceholdersAndColors(translatedText, placeholders, colorCodes); } catch (error) { console.error('Error in translation:', error); throw error; } } function preprocessText(text) { const regexColorCodes = /&[a-f0-9k-or]/g; const regexPlaceholders = /%\w+%/g; const colorCodes = (text.match(regexColorCodes) || []).map(match => match); const placeholders = {}; const cleanText = text.replace(regexColorCodes, match => { const tempKey = `%%COLOR_${match}%%`; return tempKey; }).replace(regexPlaceholders, match => { const tempKey = `%%PLACEHOLDER_${match}%%`; placeholders[tempKey] = match; return tempKey; }); return { cleanText, placeholders, colorCodes }; } function stripColorCodesAndPlaceholders(text) { const regexColorCodes = /&[a-f0-9k-or]/g; const regexPlaceholders = /%\w+%/g; return text.replace(regexColorCodes, '').replace(regexPlaceholders, ''); } async function fetchTranslation(cleanText, languagePair) { const url = `https://api.mymemory.translated.net/get?q=${encodeURIComponent(cleanText)}&langpair=${languagePair}`; const response = await fetch(url); if (!response.ok) throw new Error('Failed to get translation'); const data = await response.json(); if (!data.responseData || !data.responseData.translatedText) { throw new Error('Invalid translation API response'); } return data.responseData.translatedText; } function revertTextPlaceholdersAndColors(translatedText, placeholders, colorCodes) { Object.keys(placeholders).forEach(key => { translatedText = translatedText.replace(key, placeholders[key]); }); colorCodes.forEach(code => { translatedText = translatedText.replace(`%%COLOR_${code}%%`, code); }); return translatedText; } function isPlaceholder(text) { return text.includes('%') && text.includes('%'); } function isBoolean(text) { return text === 'true' || text === 'false'; } function copyOutput() { const outputText = document.getElementById('output').textContent; navigator.clipboard.writeText(outputText) .then(() => alert('Translated text copied to clipboard!')) .catch(err => console.error('Error copying text: ', err)); } document.getElementById('fileInput').addEventListener('change', async (event) => { const file = event.target.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { document.getElementById('input').value = e.target.result; }; reader.readAsText(file); }); </script> </body> </html>