Meningkatkan antarmuka dan fungsionalitas Greasy Fork: menambahkan ikon skrip, bilah alat HTML lengkap untuk menulis deskripsi dan komentar, tombol unduhan skrip langsung, dan penerjemah AI.
// ==UserScript==
// @name Better Greasy Fork
// @name:pt-BR Greasy Fork Aprimorado
// @name:zh-CN Greasy Fork 增强
// @name:zh-TW Greasy Fork 增強
// @name:fr-CA Greasy Fork Amélioré
// @name:ckb Greasy Fork ـی باشتر
// @name:ar Greasy Fork محسن
// @name:be Палепшаны Greasy Fork
// @name:bg Подобрен Greasy Fork
// @name:cs Vylepšený Greasy Fork
// @name:da Forbedret Greasy Fork
// @name:de Verbesserter Greasy Fork
// @name:el Βελτιωμένο Greasy Fork
// @name:en Better Greasy Fork
// @name:eo Pli bona Greasy Fork
// @name:es Greasy Fork Mejorado
// @name:fi Parannettu Greasy Fork
// @name:fr Greasy Fork Amélioré
// @name:he Greasy Fork משופר
// @name:hr Poboljšani Greasy Fork
// @name:hu Jobb Greasy Fork
// @name:id Greasy Fork Ditingkatkan
// @name:it Greasy Fork Migliorato
// @name:ja Greasy Fork 強化
// @name:ka გაუმჯობესებული Greasy Fork
// @name:ko Greasy Fork 향상
// @name:mr सुधारित Greasy Fork
// @name:nb Forbedret Greasy Fork
// @name:nl Verbeterde Greasy Fork
// @name:pl Ulepszony Greasy Fork
// @name:ro Greasy Fork Îmbunătățit
// @name:ru Улучшенный Greasy Fork
// @name:sk Vylepšený Greasy Fork
// @name:sr Побољшани Greasy Fork
// @name:sv Förbättrad Greasy Fork
// @name:th Greasy Fork ปรับปรุง
// @name:tr Geliştirilmiş Greasy Fork
// @name:uk Покращений Greasy Fork
// @name:ug ئەلالاشتۇرۇلغان Greasy Fork
// @name:vi Greasy Fork Nâng cao
// @namespace https://github.com/0H4S
// @version 1.9
// @description Enhances Greasy Fork's interface and functionality: adds script icons, a full HTML toolbar for writing descriptions and comments, a direct script download button, metadata customization, and an AI translator.
// @description:pt-BR Torna o Greasy Fork mais funcional e visualmente agradável: adiciona ícones de scripts, barra de ferramentas HTML completa para editar descrições e comentários, botão de download direto de scripts, personalização via metadados e tradutor IA.
// @description:zh-CN 美化 Greasy Fork 并增强功能:添加脚本图标、用于编写描述和评论的完整 HTML 工具栏、直接脚本下载按钮、元数据自定义以及 AI 翻译功能。
// @description:zh-TW 美化 Greasy Fork 並增強功能:添加腳本圖示、用於編寫描述和評論的完整 HTML 工具列、直接腳本下載按鈕、元數據自定義以及 AI 翻譯功能。
// @description:fr-CA Améliore l'interface et les fonctionnalités de Greasy Fork : ajoute des icônes de scripts, une barre d'outils HTML complète pour rédiger des descriptions et des commentaires, un bouton de téléchargement direct de scripts, la personnalisation des métadonnées et un traducteur IA.
// @description:ckb ڕووکار وasîteyi Greasy Fork باشتر دەکات: ئایکۆنی سکریپت زیاد دەکات، تووڵباری HTML تەواو بۆ نووسینی وەسف و لێدوانەکان، دوگمەی داگرتنی ڕاستەوخۆی سکریپت، وەرگێڕی زیرەکی دەستکرد.
// @description:ar يحسن واجهة Greasy Fork ووظائفه: يضيف أيقونات للسكربتات، وشريط أدوات HTML كامل لكتابة الأوصاف والتعليقات، وزر تحميل مباشر للسكربت، وتخصيص البيانات الوصفية، ومترجم ذكاء اصطناعي.
// @description:be Паляпшае інтэрфейс і функцыянальнасць Greasy Fork: дадае значкі скрыптоў, поўную панэль інструментаў HTML для напісання апісанняў і каментарыяў, кнопку прамой загрузкі скрыптоў і перакладчык AI.
// @description:bg Подобрява интерфейса и функционалността на Greasy Fork: добавя икони на скриптове, пълна HTML лента с инструменти за писане на описания и коментари, бутон за директно изтегляне на скриптове и AI преводач.
// @description:cs Vylepšuje rozhraní a funkčnost Greasy Fork: přidává ikony skriptů, plný panel nástrojů HTML pro psaní popisů a komentářů, tlačítko přímého stahování skriptů, úpravy metadat a AI překladač.
// @description:da Forbedrer Greasy Forks grænseflade og funktionalitet: tilføjer skriptikoner, en komplet HTML-værktøjslinje til at skrive beskrivelser og kommentarer, en knap til direkte download af skripter og en AI-oversætter.
// @description:de Verbessert die Benutzeroberfläche und Funktionalität von Greasy Fork: Fügt Skript-Symbole, eine vollständige HTML-Symbolleiste zum Schreiben von Beschreibungen und Kommentaren, einen direkten Skript-Download-Button und einen KI-Übersetzer hinzu.
// @description:el Βελτιώνει τη διεπαφή και τη λειτουργικότητα του Greasy Fork: προσθέτει εικονίδια σεναρίων, πλήρη γραμμή εργαλείων HTML για τη σύνταξη περιγραφών και σχολίων, κουμπί άμεσης λήψης σεναρίων και μεταφραστή AI.
// @description:en Enhances Greasy Fork's interface and functionality: adds script icons, a full HTML toolbar for writing descriptions and comments, a direct script download button, metadata customization, and an AI translator.
// @description:eo Plibonigas la interfacon kaj funkciecon de Greasy Fork: aldonas skriptajn ikonojn, plenan HTML-ilobreton por verki priskribojn kaj komentojn, rektan skript-elŝutbutonon kaj AI-tradukilon.
// @description:es Mejora la interfaz y la funcionalidad de Greasy Fork: añade iconos de scripts, barra de herramientas HTML completa para escribir descripciones y comentarios, botón de descarga directa de scripts y traductor IA.
// @description:fi Parantaa Greasy Forkin käyttöliittymää ja toiminnallisuutta: lisää skriptikuvakkeet, täydellisen HTML-työkalurivin kuvausten ja kommenttien kirjoittamiseen, suoran skriptien latauspainikkeen ja tekoälykääntäjän.
// @description:fr Améliore l'interface et les fonctionnalités de Greasy Fork : ajoute des icônes de scripts, une barre d'outils HTML complète pour rédiger des descriptions et des commentaires, un bouton de téléchargement direct de scripts, la personnalisation des métadonnées et un traducteur IA.
// @description:he משפר את הממשק והפונקציונליות של Greasy Fork: מוסיף סמלי סקריפט, סרגל כלים HTML מלא לכתיבת תיאורים והערות, כפתור להורדה ישירה של סקריפטים, התאמה אישית של מטא נתונים ומתרגם AI.
// @description:hr Poboljšava sučelje i funkcionalnost Greasy Forka: dodaje ikone skripti, punu HTML alatnu traku za pisanje opisa i komentara, gumb za izravno preuzimanje skripti, prilagodbu metapodataka i AI prevoditelj.
// @description:hu Javítja a Greasy Fork felületét és funkcionalitását: szkriptikonokat, teljes HTML eszköztárat leírások és megjegyzések írásához, közvetlen szkriptletöltési gombot és MI fordítót ad hozzá.
// @description:id Meningkatkan antarmuka dan fungsionalitas Greasy Fork: menambahkan ikon skrip, bilah alat HTML lengkap untuk menulis deskripsi dan komentar, tombol unduhan skrip langsung, dan penerjemah AI.
// @description:it Migliora l'interfaccia e la funzionalità di Greasy Fork: aggiunge icone agli script, una barra degli strumenti HTML completa per scrivere descrizioni e commenti, un pulsante di download diretto degli script e un traduttore AI.
// @description:ja Greasy Forkのインターフェースと機能を強化します。スクリプトアイコン、説明やコメントを記述するための完全なHTMLツールバー、スクリプトの直接ダウンロードボタン、AI翻訳機能などを追加します。
// @description:ka აუმჯობესებს Greasy Fork-ის ინტერფეისს და ფუნქციონალს: ამატებს სკრიპტის ხატულებს, სრულ HTML ხელსაწყოთა ზოლს აღწერილობებისა და კომენტარების დასაწერად, სკრიპტების პირდაპირი ჩამოტვირთვის ღილაკს და AI თარჯიმანს.
// @description:ko Greasy Fork의 인터페이스와 기능을 향상시킵니다. 스크립트 아이콘, 설명 및 설명을 작성하기 위한 전체 HTML 툴바, 직접 스크립트 다운로드 버튼 및 AI 번역기를 추가합니다.
// @description:mr Greasy Fork चे इंटरफेस आणि कार्यक्षमता वाढवते: स्क्रिप्ट चिन्हे, वर्णन आणि टिप्पण्या लिहिण्यासाठी संपूर्ण HTML टूलबार, थेट स्क्रिप्ट डाउनलोड बटण आणि AI अनुवादक जोडते.
// @description:nb Forbedrer Greasy Forks grensesnitt og funksjonalitet: legger til skriptikoner, en full HTML-verktøylinje for å skrive beskrivelser og kommentarer, en knapp for direkte nedlasting av skript og en AI-oversetter.
// @description:nl Verbetert de interface en functionaliteit van Greasy Fork: voegt scriptpictogrammen, een volledige HTML-werkbalk voor het schrijven van beschrijvingen en opmerkingen, een knop voor het direct downloaden van scripts en een AI-vertaler toe.
// @description:pl Ulepsza interfejs i funkcjonalność Greasy Fork: dodaje ikony skryptów, pełny pasek narzędzi HTML do pisania opisów i komentarzy, przycisk bezpośredniego pobierania skryptów oraz tłumacz AI.
// @description:ro Îmbunătățește interfața și funcționalitatea Greasy Fork: adaugă pictograme de script, o bară de instrumente HTML completă pentru scrierea de descrieri și comentarii, buton de descărcare directă a scripturilor și traducător AI.
// @description:ru Улучшает интерфейс и функциональность Greasy Fork: добавляет значки скриптов, полную HTML-панель для написания описаний и комментариев, кнопку прямой загрузки скриптов и ИИ-переводчик.
// @description:sk Vylepšuje rozhranie a funkčnosť Greasy Fork: pridáva ikony skriptov, plný panel nástrojov HTML na písanie popisov a komentárov, tlačidlo priameho sťahovania skriptov a prekladač AI.
// @description:sr Побољшава интерфејс и функционалност Греаси Форк-а: додаје иконе скрипти, пуну ХТМЛ траку са алаткама за писање описа и коментара, дугме за директно преузимање скрипти и АИ преводиоца.
// @description:sv Förbättrar Greasy Forks gränssnitt och funktionalitet: lägger till skriptikoner, ett fullständigt HTML-verktygsfält för att skriva beskrivningar och kommentarer, en knapp för direkt nedladdning av skript och en AI-översättare.
// @description:th ปรับปรุงอินเทอร์เฟซและฟังก์ชันการทำงานของ Greasy Fork: เพิ่มไอคอนสคริปต์, แถบเครื่องมือ HTML เต็มรูปแบบสำหรับเขียนคำอธิบายและแสดงความคิดเห็น, ปุ่มดาวน์โหลดสคริปต์โดยตรง และตัวแปล AI
// @description:tr Greasy Fork'un arayüzünü ve işlevselliğini geliştirir: komut dosyası simgeleri, açıklamalar ve yorumlar yazmak için tam bir HTML araç çubuğu, doğrudan komut dosyası indirme düğmesi ve AI çevirmeni ekler.
// @description:uk Покращує інтерфейс та функціональність Greasy Fork: додає значки скриптів, повну панель інструментів HTML для написання описів та коментарів, кнопку прямого завантаження скриптів та перекладач ШІ.
// @description:ug Greasy Fork نىڭ كۆرۈنمە يۈزى ۋە ئىقتىدارىنى ئۆستۈرىدۇ: قوليازما سىنبەلگىسى ، چۈشەندۈرۈش ۋە باھا يېزىش ئۈچۈن تولۇق HTML قورال بالدىقى ، بىۋاسىتە قوليازما چۈشۈرۈش كۇنۇپكىسى ۋە AI تەرجىمانى قوشىدۇ.
// @description:vi Cải thiện giao diện và tính năng của Greasy Fork: thêm biểu tượng script, thanh công cụ HTML đầy đủ để viết mô tả và bình luận, nút tải xuống script trực tiếp và dịch giả AI.
// @author OHAS
// @license CC-BY-NC-ND-4.0
// @copyright 2025 OHAS. All Rights Reserved.
// @match https://greasyfork.org/*
// @match https://cn-greasyfork.org/*
// @match https://sleazyfork.org/*
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMjAwIiB2aWV3Qm94PSIwIDAgMjQgMjQiIG92ZXJmbG93PSJ2aXNpYmxlIj4KICAgIDxzdHlsZT4KICAgICAgICAuZGVzZW5oYXItY29udG9ybm8gewogICAgICAgICAgICBzdHJva2UtZGFzaGFycmF5OiAxMDA7CiAgICAgICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAxMDA7CiAgICAgICAgICAgIGFuaW1hdGlvbjogZGVzZW5oYXItY29udG9ybm8gMTBzIGVhc2UtaW4tb3V0IGluZmluaXRlOwogICAgICAgIH0KCiAgICAgICAgLm9uZGEtcHJlZW5jaGltZW50byB7CiAgICAgICAgICAgIGFuaW1hdGlvbjogb25kYS1wcmVlbmNoaW1lbnRvIDEwcyBlYXNlLWluLW91dCBpbmZpbml0ZTsKICAgICAgICB9CgogICAgICAgIEBrZXlmcmFtZXMgZGVzZW5oYXItY29udG9ybm8gewogICAgICAgICAgICAwJSB7CiAgICAgICAgICAgICAgICBzdHJva2UtZGFzaG9mZnNldDogMTAwOwogICAgICAgICAgICAgICAgc3Ryb2tlOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgMjUlIHsKICAgICAgICAgICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAwOwogICAgICAgICAgICAgICAgc3Ryb2tlOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgNzUlIHsKICAgICAgICAgICAgICAgIHN0cm9rZS1kYXNob2Zmc2V0OiAwOwogICAgICAgICAgICAgICAgc3Ryb2tlOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgMTAwJSB7CiAgICAgICAgICAgICAgICBzdHJva2UtZGFzaG9mZnNldDogLTEwMDsKICAgICAgICAgICAgICAgIHN0cm9rZTogI2ZmZmZmZmZmOwogICAgICAgICAgICB9CiAgICAgICAgfQoKICAgICAgICBAa2V5ZnJhbWVzIG9uZGEtcHJlZW5jaGltZW50byB7CiAgICAgICAgICAgIDAlIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgyNHB4KTsKICAgICAgICAgICAgICAgIGZpbGw6ICNmZmZmZmZmZjsKICAgICAgICAgICAgfQogICAgICAgICAgICAyNSUgewogICAgICAgICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDBweCk7CiAgICAgICAgICAgICAgICBmaWxsOiAjZmZmZmZmZmY7CiAgICAgICAgICAgIH0KICAgICAgICAgICAgNzUlIHsKICAgICAgICAgICAgICAgIHRyYW5zZm9ybTogdHJhbnNsYXRlWSgwcHgpOwogICAgICAgICAgICAgICAgZmlsbDogI2ZmZmZmZmZmOwogICAgICAgICAgICB9CiAgICAgICAgICAgIDEwMCUgewogICAgICAgICAgICAgICAgdHJhbnNmb3JtOiB0cmFuc2xhdGVZKDI0cHgpOwogICAgICAgICAgICAgICAgZmlsbDogI2ZmZmZmZmZmOwogICAgICAgICAgICB9CiAgICAgICAgfQogICAgPC9zdHlsZT4KICAgIDxkZWZzPgogICAgICAgIDxjbGlwUGF0aCBpZD0ibW9sZGUtZ2FyZm8iPgogICAgICAgICAgICA8cGF0aCBkPSJNNS44OSAyLjIyN2EuMjguMjggMCAwIDEgLjI2Ni4wNzZsNS4wNjMgNS4wNjJjLjU0LjU0LjUwOSAxLjY1Mi0uMDMxIDIuMTkybDguNzcxIDguNzdjMS4zNTYgMS4zNTUtLjM2IDMuMDk3LTEuNzMgMS43MjhsLTguNzcyLTguNzdjLS41NC41NC0xLjY1MS41NzEtMi4xOTEuMDMxbC01LjA2My01LjA2Yy0uMzA0LS4zMDQuMzA0LS45MTEuNjA4LS42MDhsMy43MTQgMy43MTNMNy41OSA4LjI5N0wzLjg3NSA0LjU4MmMtLjMwNC0uMzA0LjMwNC0uOTExLjYwNy0uNjA3bDMuNzE1IDMuNzE0bDEuMDY3LTEuMDY2TDUuNTQ5IDIuOTFjLS4yMjgtLjIyOC4wNTctLjYyNi4zNDItLjY4M1oiLz4KICAgICAgICA8L2NsaXBQYXRoPgogICAgPC9kZWZzPgogICAgPGc+CiAgICAgICAgPHBhdGggZmlsbD0iIzAwMDAwMGZmIiBkPSJNMTIgMEM1LjM3NCAwIDAgNS4zNzUgMCAxMnM1LjM3NCAxMiAxMiAxMmM2LjYyNSAwIDEyLTUuMzc1IDEyLTEyUzE4LjYyNSAwIDEyIDAiLz4KICAgICAgICA8ZyBjbGlwLXBhdGg9InVybCgjbW9sZGUtZ2FyZm8pIj4KICAgICAgICAgICAgPHBhdGggY2xhc3M9Im9uZGEtcHJlZW5jaGltZW50byIgCiAgICAgICAgICAgICAgICAgIGQ9Ik0gLTIsMjQgTCAtMiwwIGMgNiwtNSA2LDUgMTIsMCBzIDYsLTUgMTIsMCBzIDYsLTUgMTIsMCBMIDMwLDI0IFoiLz4KICAgICAgICA8L2c+CiAgICAgICAgPHBhdGggY2xhc3M9ImRlc2VuaGFyLWNvbnRvcm5vIiBmaWxsPSJub25lIiBzdHJva2Utd2lkdGg9IjAuNSIgZD0iTTUuODkgMi4yMjdhLjI4LjI4IDAgMCAxIC4yNjYuMDc2bDUuMDYzIDUuMDYyYy41NC41NC41MDkgMS42NTItLjAzMSAyLjE5Mmw4Ljc3MSA4Ljc3YzEuMzU2IDEuMzU1LS4zNiAzLjA5Ny0xLjczIDEuNzI4bC04Ljc3Mi04Ljc3Yy0uNTQuNTQtMS42NTEuNTcxLTIuMTkxLjAzMWwtNS4wNjMtNS4wNmMtLjMwNC0uMzA0LjMwNC0uOTExLjYwOC0uNjA4bDMuNzE0IDMuNzEzTDcuNTkgOC4yOTdMMy44NzUgNC41ODJjLS4zMDQtLjMwNC4zMDQtLjkxMS42MDctLjYwN2wzLjcxNSAzLjcxNGwxLjA2Ny0xLjA2Nkw1LjU0OSAyLjkxYy0uMjI4LS4yMjguMDU3LS42MjYuMzQyLS42ODNaIi8+CiAgICA8L2c+Cjwvc3ZnPg==
// @resource customCSS https://cdn.jsdelivr.net/gh/0H4S/Better-Greasy-Fork/custom.css
// @resource iconsJSON https://cdn.jsdelivr.net/gh/0H4S/Better-Greasy-Fork/icons.json
// @require https://update.greasyfork.org/scripts/549920.js
// @connect gist.github.com
// @connect update.greasyfork.org
// @connect generativelanguage.googleapis.com
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_deleteValue
// @grant GM_xmlhttpRequest
// @grant GM_getResourceText
// @grant GM_registerMenuCommand
// @run-at document-idle
// @noframes
// @compatible chrome
// @compatible firefox
// @compatible edge
// @compatible opera
// @bgf-colorLT #0059ffff
// @bgf-colorDT #ffffffff
// @bgf-copyright [2025 OHAS. All Rights Reserved.](https://gist.github.com/0H4S/ae2fa82957a089576367e364cbf02438)
// @bgf-compatible brave, mobile
// @bgf-social https://github.com/0H4S
// @contributionURL https://linktr.ee/0H4S
// ==/UserScript==
(function () {
'use strict';
// ================
// #region GLOBAL
// ================
const allTranslations = {
'pt-BR': {
langName: 'Português (BR)',
languageSettings: '🌐 Idioma',
close: 'Fechar',
confirm: 'Confirmar',
cancel: 'Cancelar',
download: 'Baixar',
compatible_with: 'Compatível com',
force_update: '🔄️ Forçar Atualização',
force_update_alert: 'O cache foi limpo. A página será recarregada para buscar os dados atualizados.',
titles: 'Títulos',
title_placeholder: 'Título',
bold: 'Negrito',
bold_placeholder: 'negrito',
italic: 'Itálico',
italic_placeholder: 'itálico',
underline: 'Sublinhado',
underline_placeholder: 'sublinhado',
strikethrough: 'Riscado',
strikethrough_placeholder: 'riscado',
unordered_list: 'Lista não ordenada',
ordered_list: 'Lista ordenada',
list_item_placeholder: 'Item',
quote: 'Citação',
inline_code: 'Código Inline',
inline_code_placeholder: 'código',
code_block: 'Bloco de Código',
code_block_placeholder: 'código aqui',
horizontal_line: 'Linha Horizontal',
horizontal_line_style: 'Estilo da Linha Horizontal',
prompt_hr_size: 'Tamanho (px)',
prompt_hr_color: 'Cor',
link: 'Link',
prompt_insert_url: 'Insira a URL:',
link_text_placeholder: 'texto do link',
image: 'Imagem',
prompt_insert_image_url: 'Insira a URL da imagem (https):',
prompt_image_title: 'Título da imagem (opcional):',
image_title_placeholder: 'ex: Minha bela imagem',
prompt_image_width: 'Largura (opcional):',
prompt_image_height: 'Altura (opcional):',
video: 'Vídeo',
prompt_video_type: 'Tipo de Vídeo',
video_type_embed: 'Incorporado (YouTube, Bilibili)',
video_type_html5: 'Vídeo HTML5 (URL direta)',
prompt_video_poster_url: 'URL da Imagem de Capa (poster)',
prompt_insert_video_url: 'Insira a URL do vídeo:',
prompt_video_width: 'Largura (opcional):',
prompt_video_height: 'Altura (opcional):',
alert_invalid_video_url: 'URL de vídeo inválida ou não suportada.',
table: 'Tabela',
prompt_columns: 'Número de colunas:',
prompt_rows: 'Número de linhas:',
table_header_placeholder: 'Cabeçalho',
table_cell_placeholder: 'Célula',
subscript: 'Subscrito',
subscript_placeholder: 'sub',
superscript: 'Sobrescrito',
superscript_placeholder: 'sup',
highlight: 'Marcação',
highlight_placeholder: 'marcado',
keyboard: 'Teclado',
keyboard_placeholder: 'Ctrl+C',
abbreviation: 'Abreviação',
prompt_abbreviation_meaning: 'Qual o significado da abreviação?',
abbreviation_placeholder: 'HTML',
text_color: 'Cor do Texto',
colored_text_placeholder: 'texto colorido',
background_color: 'Cor de Fundo',
colored_background_placeholder: 'fundo colorido',
details: 'Seção Recolhível',
details_summary_placeholder: 'Resumo ou Título',
details_content_placeholder: 'Conteúdo a ser ocultado...',
center: 'Centralizar',
center_placeholder: 'texto centralizado',
notFound: 'Código não encontrado!',
scriptIdNotFound: 'Não foi possível identificar o ID do script.',
downloading: 'Baixando...',
downloadError: 'Ocorreu um erro ao baixar o script.',
downloadTimeout: 'O tempo para baixar o script esgotou.',
info_tooltip: 'Atalhos',
info_shortcuts_title: 'Atalhos do Teclado',
info_header_shortcut: 'Atalho',
info_header_action: 'Ação',
info_shortcut_tab: 'Insere um espaço de tabulação',
info_shortcut_shift_enter: 'Insere uma quebra de linha <span style="color: #d21934;"><strong><br></strong></span>',
info_shortcut_ctrl_d: 'Envolve a seleção em uma tag <span style="color: #d21934;"><strong><div></strong></span>',
info_shortcut_ctrl_p: 'Envolve a seleção em um parágrafo <span style="color: #d21934;"><strong><p></strong></span>',
info_shortcut_ctrl_m: 'Envolve a seleção em um bloco de código markdown.',
info_shortcut_ctrl_space: 'Insere um espaço não separável <span style="color: #d21934;"><strong>;nbsp</strong></span>',
prompt_link_text: 'Texto do link:',
prompt_abbreviation_text: 'Texto da abreviação:',
border_style: 'Estilo da Borda',
prompt_border_size: 'Tamanho da borda (px)',
prompt_border_color: 'Cor da borda',
prompt_border_text: 'Texto',
prompt_border_tag_type: 'Tipo de Tag',
border_text_placeholder: 'Texto',
ai_translate: 'Traduzir com IA',
alert_text_empty: 'A caixa de texto está vazia.',
prompt_translate_to: 'Traduzir para:',
prompt_ai_model: 'Modelo IA:',
prompt_api_key: 'Chave API (Gemini API):',
placeholder_api_key: 'Cole sua chave AIza... aqui',
alert_translation_error: 'Erro na tradução: ',
error_no_text: 'A IA não retornou texto válido.',
error_api_processing: 'Erro ao processar resposta da API.',
error_connection: 'Erro de conexão com a internet.',
error_generic: 'Erro',
selection: 'Seleção',
api_help_title: 'Como obter uma chave API Gratuita',
api_help_text: 'O Google oferece uma cota gratuita generosa. Para usar, acesse o Google AI Studio no link abaixo, faça login com sua conta e clique em "Create API key". Basta copiar a chave gerada e colá-la no script.',
api_help_link_text: 'Obter Chave API',
api_help_tooltip: 'Ajuda: Como obter uma chave',
lang_en: 'Inglês',
lang_pt_br: 'Português (Brasil)',
lang_zh_cn: 'Chinês (Simplificado)',
lang_zh_tw: 'Chinês (Tradicional)',
lang_es: 'Espanhol',
lang_fr: 'Francês',
lang_ru: 'Russo',
lang_de: 'Alemão',
lang_ja: 'Japonês',
lang_ko: 'Coreano',
lang_ckb: 'Curdo (Sorani)',
lang_ar: 'Árabe',
lang_be: 'Bielorrusso',
lang_bg: 'Búlgaro',
lang_cs: 'Tcheco',
lang_da: 'Dinamarquês',
lang_el: 'Grego',
lang_eo: 'Esperanto',
lang_fi: 'Finlandês',
lang_fr_ca: 'Francês (Canadá)',
lang_he: 'Hebraico',
lang_hr: 'Croata',
lang_hu: 'Húngaro',
lang_id: 'Indonésio',
lang_it: 'Italiano',
lang_ka: 'Georgiano',
lang_mr: 'Marati',
lang_nb: 'Norueguês (Bokmål)',
lang_nl: 'Holandês',
lang_pl: 'Polonês',
lang_ro: 'Romeno',
lang_sk: 'Eslovaco',
lang_sr: 'Sérvio',
lang_sv: 'Sueco',
lang_th: 'Tailandês',
lang_tr: 'Turco',
lang_uk: 'Ucraniano',
lang_ug: 'Uigur',
lang_vi: 'Vietnamita'
},
'en': {
langName: 'English',
languageSettings: '🌐 Language',
close: 'Close',
confirm: 'Confirm',
cancel: 'Cancel',
download: 'Download',
compatible_with: 'Compatible with',
force_update: '🔄️ Force Update',
force_update_alert: 'Cache cleared. The page will reload to fetch the updated data.',
titles: 'Headings',
title_placeholder: 'Heading',
bold: 'Bold',
bold_placeholder: 'bold text',
italic: 'Italic',
italic_placeholder: 'italic text',
underline: 'Underline',
underline_placeholder: 'underlined text',
strikethrough: 'Strikethrough',
strikethrough_placeholder: 'strikethrough text',
unordered_list: 'Unordered List',
ordered_list: 'Ordered List',
list_item_placeholder: 'Item',
quote: 'Quote',
inline_code: 'Inline Code',
inline_code_placeholder: 'code',
code_block: 'Code Block',
code_block_placeholder: 'code here',
horizontal_line: 'Horizontal Line',
horizontal_line_style: 'Horizontal Line Style',
prompt_hr_size: 'Size (px)',
prompt_hr_color: 'Color',
link: 'Link',
prompt_insert_url: 'Enter the URL:',
link_text_placeholder: 'link text',
image: 'Image',
prompt_insert_image_url: 'Enter the image URL (https):',
prompt_image_title: 'Image title (optional):',
image_title_placeholder: 'e.g. My beautiful image',
prompt_image_width: 'Width (optional):',
prompt_image_height: 'Height (optional):',
video: 'Video',
prompt_video_type: 'Video Type',
video_type_embed: 'Embed (YouTube, Bilibili)',
video_type_html5: 'HTML5 Video (Direct URL)',
prompt_video_poster_url: 'Poster Image URL',
prompt_insert_video_url: 'Enter the video URL:',
prompt_video_width: 'Width (optional):',
prompt_video_height: 'Height (optional):',
alert_invalid_video_url: 'Invalid or unsupported video URL.',
table: 'Table',
prompt_columns: 'Number of columns:',
prompt_rows: 'Number of rows:',
table_header_placeholder: 'Header',
table_cell_placeholder: 'Cell',
subscript: 'Subscript',
subscript_placeholder: 'sub',
superscript: 'Superscript',
superscript_placeholder: 'sup',
highlight: 'Highlight',
highlight_placeholder: 'highlighted',
keyboard: 'Keyboard',
keyboard_placeholder: 'Ctrl+C',
abbreviation: 'Abbreviation',
prompt_abbreviation_meaning: 'What does the abbreviation stand for?',
abbreviation_placeholder: 'HTML',
text_color: 'Text Color',
colored_text_placeholder: 'colored text',
background_color: 'Background Color',
colored_background_placeholder: 'colored background',
details: 'Collapsible section',
details_summary_placeholder: 'Summary or Title',
details_content_placeholder: 'Content to be hidden...',
center: 'Center Align',
center_placeholder: 'centered text',
notFound: 'Code not found!',
scriptIdNotFound: 'Could not identify the script ID.',
downloading: 'Downloading...',
downloadError: 'An error occurred while downloading the script.',
downloadTimeout: 'The script download timed out.',
info_tooltip: 'Shortcuts',
info_shortcuts_title: 'Keyboard Shortcuts',
info_header_shortcut: 'Shortcut',
info_header_action: 'Action',
info_shortcut_tab: 'Inserts a tab space',
info_shortcut_shift_enter: 'Inserts a line break <span style="color: #d21934;"><strong><br></strong></span>',
info_shortcut_ctrl_d: 'Wraps the selection in a <span style="color: #d21934;"><strong><div></strong></span> tag',
info_shortcut_ctrl_p: 'Wraps the selection in a <span style="color: #d21934;"><strong><p></strong></span> paragraph',
info_shortcut_ctrl_m: 'Wraps the selection in a markdown code block',
info_shortcut_ctrl_space: 'Inserts a non-breaking space <span style="color: #d21934;"><strong>;nbsp</strong></span>',
prompt_link_text: 'Link text:',
prompt_abbreviation_text: 'Abbreviation text:',
border_style: 'Border Style',
prompt_border_size: 'Border size (px)',
prompt_border_color: 'Border color',
prompt_border_text: 'Text',
prompt_border_tag_type: 'Tag Type',
border_text_placeholder: 'Text',
ai_translate: 'Translate with AI',
alert_text_empty: 'The text box is empty.',
prompt_translate_to: 'Translate to:',
prompt_ai_model: 'AI Model:',
prompt_api_key: 'API Key (Gemini API):',
placeholder_api_key: 'Paste your AIza key... here',
alert_translation_error: 'Translation error: ',
error_no_text: 'AI did not return valid text.',
error_api_processing: 'Error processing API response.',
error_connection: 'Internet connection error.',
error_generic: 'Error',
selection: 'Selection',
api_help_title: 'How to get a free API Key',
api_help_text: 'Google offers a generous free quota. To use it, access Google AI Studio via the link below, log in with your account, and click "Create API key". Just copy the generated key and paste it into the script.',
api_help_link_text: 'Get API Key',
api_help_tooltip: 'Help: How to get a key',
lang_en: 'English',
lang_pt_br: 'Portuguese (Brazil)',
lang_zh_cn: 'Chinese (Simplified)',
lang_zh_tw: 'Chinese (Traditional)',
lang_es: 'Spanish',
lang_fr: 'French',
lang_ru: 'Russian',
lang_de: 'German',
lang_ja: 'Japanese',
lang_ko: 'Korean',
lang_ckb: 'Kurdish (Sorani)',
lang_ar: 'Arabic',
lang_be: 'Belarusian',
lang_bg: 'Bulgarian',
lang_cs: 'Czech',
lang_da: 'Danish',
lang_el: 'Greek',
lang_eo: 'Esperanto',
lang_fi: 'Finnish',
lang_fr_ca: 'French (Canada)',
lang_he: 'Hebrew',
lang_hr: 'Croatian',
lang_hu: 'Hungarian',
lang_id: 'Indonesian',
lang_it: 'Italian',
lang_ka: 'Georgian',
lang_mr: 'Marathi',
lang_nb: 'Norwegian (Bokmål)',
lang_nl: 'Dutch',
lang_pl: 'Polish',
lang_ro: 'Romanian',
lang_sk: 'Slovak',
lang_sr: 'Serbian',
lang_sv: 'Swedish',
lang_th: 'Thai',
lang_tr: 'Turkish',
lang_uk: 'Ukrainian',
lang_ug: 'Uyghur',
lang_vi: 'Vietnamese'
},
'es': {
langName: 'Español',
languageSettings: '🌐 Idioma',
close: 'Cerrar',
confirm: 'Confirmar',
cancel: 'Cancelar',
download: 'Descargar',
compatible_with: 'Compatible con',
force_update: '🔄️ Forzar actualización',
force_update_alert: 'La caché se limpió. La página se recargará para obtener los datos actualizados.',
titles: 'Títulos',
title_placeholder: 'Título',
bold: 'Negrita',
bold_placeholder: 'texto en negrita',
italic: 'Cursiva',
italic_placeholder: 'texto en cursiva',
underline: 'Subrayado',
underline_placeholder: 'texto subrayado',
strikethrough: 'Tachado',
strikethrough_placeholder: 'texto tachado',
unordered_list: 'Lista sin ordenar',
ordered_list: 'Lista ordenada',
list_item_placeholder: 'Elemento',
quote: 'Cita',
inline_code: 'Código en línea',
inline_code_placeholder: 'código',
code_block: 'Bloque de código',
code_block_placeholder: 'código aquí',
horizontal_line: 'Línea horizontal',
horizontal_line_style: 'Estilo de Línea Horizontal',
prompt_hr_size: 'Tamaño (px)',
prompt_hr_color: 'Color',
link: 'Enlace',
prompt_insert_url: 'Introduzca la URL:',
link_text_placeholder: 'texto del enlace',
image: 'Imagen',
prompt_insert_image_url: 'Introduzca la URL de la imagen (https):',
prompt_image_title: 'Título de la imagen (opcional):',
image_title_placeholder: 'ej: Mi hermosa imagen',
prompt_image_width: 'Ancho (opcional):',
prompt_image_height: 'Alto (opcional):',
video: 'Video',
prompt_video_type: 'Tipo de Video',
video_type_embed: 'Incrustado (YouTube, Bilibili)',
video_type_html5: 'Video HTML5 (URL directa)',
prompt_video_poster_url: 'URL de la imagen de portada (póster)',
prompt_insert_video_url: 'Introduzca la URL del video:',
prompt_video_width: 'Ancho (opcional):',
prompt_video_height: 'Alto (opcional):',
alert_invalid_video_url: 'URL de video no válida o no compatible.',
table: 'Tabla',
prompt_columns: 'Número de columnas:',
prompt_rows: 'Número de filas:',
table_header_placeholder: 'Encabezado',
table_cell_placeholder: 'Celda',
subscript: 'Subíndice',
subscript_placeholder: 'sub',
superscript: 'Superíndice',
superscript_placeholder: 'sup',
highlight: 'Resaltado',
highlight_placeholder: 'resaltado',
keyboard: 'Teclado',
keyboard_placeholder: 'Ctrl+C',
abbreviation: 'Abreviatura',
prompt_abbreviation_meaning: '¿Qué significa la abreviação?',
abbreviation_placeholder: 'HTML',
text_color: 'Color del texto',
colored_text_placeholder: 'texto coloreado',
background_color: 'Color de fondo',
colored_background_placeholder: 'fondo coloreado',
details: 'Sección Plegable',
details_summary_placeholder: 'Resumen o Título',
details_content_placeholder: 'Contenido a ocultar...',
center: 'Centrar',
center_placeholder: 'texto centrado',
notFound: '¡Código no encontrado!',
scriptIdNotFound: 'No se pudo identificar el ID del script.',
downloading: 'Descargando...',
downloadError: 'Ocurrió un error al descargar el script.',
downloadTimeout: 'Se agotó el tiempo de espera para la descarga del script.',
info_tooltip: 'Atajos',
info_shortcuts_title: 'Atajos de Teclado',
info_header_shortcut: 'Atajo',
info_header_action: 'Acción',
info_shortcut_tab: 'Inserta un espacio de tabulación',
info_shortcut_shift_enter: 'Inserta un salto de línea <span style="color: #d21934;"><strong><br></strong></span>',
info_shortcut_ctrl_d: 'Envuelve la selección en una etiqueta <span style="color: #d21934;"><strong><div></strong></span>',
info_shortcut_ctrl_p: 'Envuelve la selección en un párrafo <span style="color: #d21934;"><strong><p></strong></span>',
info_shortcut_ctrl_m: 'Envuelve la selección en un bloque de código markdown',
info_shortcut_ctrl_space: 'Inserta un espacio no separable <span style="color: #d21934;"><strong>;nbsp</strong></span>',
prompt_link_text: 'Texto del enlace:',
prompt_abbreviation_text: 'Texto de la abreviatura:',
border_style: 'Estilo de Borde',
prompt_border_size: 'Tamaño del borde (px)',
prompt_border_color: 'Color del borde',
prompt_border_text: 'Texto',
prompt_border_tag_type: 'Tipo de Etiqueta',
border_text_placeholder: 'Texto',
ai_translate: 'Traducir con IA',
alert_text_empty: 'El cuadro de texto está vacío.',
prompt_translate_to: 'Traducir a:',
prompt_ai_model: 'Modelo de IA:',
prompt_api_key: 'Clave API (Gemini API):',
placeholder_api_key: 'Pegue su clave AIza... aquí',
alert_translation_error: 'Error de traducción: ',
error_no_text: 'La IA no devolvió texto válido.',
error_api_processing: 'Error al procesar la respuesta de la API.',
error_connection: 'Error de conexión a internet.',
error_generic: 'Error',
selection: 'Selección',
api_help_title: 'Cómo obtener una clave API gratuita',
api_help_text: 'Google ofrece una generosa cuota gratuita. Para usarla, acceda a Google AI Studio en el enlace de abajo, inicie sesión con su cuenta y haga clic en "Create API key". Simplemente copie la clave generada y péguela en el script.',
api_help_link_text: 'Obtener clave API',
api_help_tooltip: 'Ayuda: Cómo obtener una clave',
lang_en: 'Inglés',
lang_pt_br: 'Portugués (Brasil)',
lang_zh_cn: 'Chino (Simplificado)',
lang_zh_tw: 'Chino (Tradicional)',
lang_es: 'Español',
lang_fr: 'Francés',
lang_ru: 'Ruso',
lang_de: 'Alemán',
lang_ja: 'Japonés',
lang_ko: 'Coreano',
lang_ckb: 'Kurdo (Sorani)',
lang_ar: 'Árabe',
lang_be: 'Bielorrusso',
lang_bg: 'Búlgaro',
lang_cs: 'Checo',
lang_da: 'Danés',
lang_el: 'Griego',
lang_eo: 'Esperanto',
lang_fi: 'Finlandés',
lang_fr_ca: 'Francés (Canadá)',
lang_he: 'Hebreo',
lang_hr: 'Croata',
lang_hu: 'Húngaro',
lang_id: 'Indonesio',
lang_it: 'Italiano',
lang_ka: 'Georgiano',
lang_mr: 'Maratí',
lang_nb: 'Noruego (Bokmål)',
lang_nl: 'Neerlandés',
lang_pl: 'Polaco',
lang_ro: 'Rumano',
lang_sk: 'Eslovaco',
lang_sr: 'Serbio',
lang_sv: 'Sueco',
lang_th: 'Tailandés',
lang_tr: 'Turco',
lang_uk: 'Ucraniano',
lang_ug: 'Uigur',
lang_vi: 'Vietnamita'
},
'fr': {
langName: 'Français',
languageSettings: '🌐 Langue',
close: 'Fermer',
confirm: 'Confirmer',
cancel: 'Annuler',
download: 'Télécharger',
compatible_with: 'Compatible avec',
force_update: '🔄️ Forcer la mise à jour',
force_update_alert: 'Cache vidé. La page va se recharger pour récupérer les données mises à jour.',
titles: 'Titres',
title_placeholder: 'Titre',
bold: 'Gras',
bold_placeholder: 'texte en gras',
italic: 'Italique',
italic_placeholder: 'texte en italique',
underline: 'Souligné',
underline_placeholder: 'texte souligné',
strikethrough: 'Barré',
strikethrough_placeholder: 'texte barré',
unordered_list: 'Liste non ordonnée',
ordered_list: 'Liste ordonnée',
list_item_placeholder: 'Élément',
quote: 'Citation',
inline_code: 'Code en ligne',
inline_code_placeholder: 'code',
code_block: 'Bloc de code',
code_block_placeholder: 'code ici',
horizontal_line: 'Ligne horizontale',
horizontal_line_style: 'Style de ligne horizontale',
prompt_hr_size: 'Taille (px)',
prompt_hr_color: 'Couleur',
link: 'Lien',
prompt_insert_url: 'Entrez l\'URL:',
link_text_placeholder: 'texte du lien',
image: 'Image',
prompt_insert_image_url: 'Entrez l\'URL de l\'image (https):',
prompt_image_title: 'Titre de l\'image (facultatif):',
image_title_placeholder: 'ex. Ma belle image',
prompt_image_width: 'Largeur (facultatif):',
prompt_image_height: 'Hauteur (facultatif):',
video: 'Vidéo',
prompt_video_type: 'Type de vidéo',
video_type_embed: 'Intégrée (YouTube, Bilibili)',
video_type_html5: 'Vidéo HTML5 (URL directe)',
prompt_video_poster_url: 'URL de l\'image d\'affiche',
prompt_insert_video_url: 'Entrez l\'URL de la vidéo:',
prompt_video_width: 'Largeur (facultatif):',
prompt_video_height: 'Hauteur (facultatif):',
alert_invalid_video_url: 'URL de vidéo invalide ou non prise en charge.',
table: 'Tableau',
prompt_columns: 'Nombre de colonnes:',
prompt_rows: 'Nombre de lignes:',
table_header_placeholder: 'En-tête',
table_cell_placeholder: 'Cellule',
subscript: 'Indice',
subscript_placeholder: 'ind',
superscript: 'Exposant',
superscript_placeholder: 'exp',
highlight: 'Surligner',
highlight_placeholder: 'surligné',
keyboard: 'Clavier',
keyboard_placeholder: 'Ctrl+C',
abbreviation: 'Abréviation',
prompt_abbreviation_meaning: 'Que signifie l\'abréviation?',
abbreviation_placeholder: 'HTML',
text_color: 'Couleur du texte',
colored_text_placeholder: 'texte coloré',
background_color: 'Couleur de fond',
colored_background_placeholder: 'fond coloré',
details: 'Section réductible',
details_summary_placeholder: 'Résumé ou Titre',
details_content_placeholder: 'Contenu à masquer...',
center: 'Aligner au centre',
center_placeholder: 'texte centré',
notFound: 'Code non trouvé !',
scriptIdNotFound: 'Impossible d\'identifier l\'ID du script.',
downloading: 'Téléchargement en cours...',
downloadError: 'Une erreur s\'est produite lors du téléchargement du script.',
downloadTimeout: 'Le téléchargement du script a expiré.',
info_tooltip: 'Raccourcis',
info_shortcuts_title: 'Raccourcis clavier',
info_header_shortcut: 'Raccourci',
info_header_action: 'Action',
info_shortcut_tab: 'Insère une tabulation.',
info_shortcut_shift_enter: 'Insère un saut de ligne <span style="color: #d21934;"><strong><br></strong></span>',
info_shortcut_ctrl_d: 'Enveloppe la sélection dans une balise <span style="color: #d21934;"><strong><div></strong></span>',
info_shortcut_ctrl_p: 'Enveloppe la sélection dans un paragraphe <span style="color: #d21934;"><strong><p></strong></span>',
info_shortcut_ctrl_m: 'Enveloppe la sélection dans un bloc de code markdown',
info_shortcut_ctrl_space: 'Insère un espace insécable <span style="color: #d21934;"><strong>;nbsp</strong></span>',
prompt_link_text: 'Texte du lien:',
prompt_abbreviation_text: 'Texte de l\'abréviation:',
border_style: 'Style de bordure',
prompt_border_size: 'Taille de la bordure (px)',
prompt_border_color: 'Couleur de la bordure',
prompt_border_text: 'Texte',
prompt_border_tag_type: 'Type de balise',
border_text_placeholder: 'Texte',
ai_translate: 'Traduire avec l\'IA',
alert_text_empty: 'La zone de texte est vide.',
prompt_translate_to: 'Traduire vers :',
prompt_ai_model: 'Modèle IA :',
prompt_api_key: 'Clé API (Gemini API) :',
placeholder_api_key: 'Collez votre clé AIza... ici',
alert_translation_error: 'Erreur de traduction : ',
error_no_text: 'L\'IA n\'a pas renvoyé de texte valide.',
error_api_processing: 'Erreur lors du traitement de la réponse API.',
error_connection: 'Erreur de connexion Internet.',
error_generic: 'Erreur',
selection: 'Sélection',
api_help_title: 'Comment obtenir une clé API gratuite',
api_help_text: 'Google offre un quota gratuit généreux. Pour l\'utiliser, accédez à Google AI Studio via le lien ci-dessous, connectez-vous avec votre compte et cliquez sur "Create API key". Il suffit de copier la clé générée et de la coller dans le script.',
api_help_link_text: 'Obtenir une clé API',
api_help_tooltip: 'Aide : Comment obtenir une clé',
lang_en: 'Anglais',
lang_pt_br: 'Portugais (Brésil)',
lang_zh_cn: 'Chinois (Simplifié)',
lang_zh_tw: 'Chinois (Traditionnel)',
lang_es: 'Espagnol',
lang_fr: 'Français',
lang_ru: 'Russe',
lang_de: 'Allemand',
lang_ja: 'Japonais',
lang_ko: 'Coréen',
lang_ckb: 'Kurde (Sorani)',
lang_ar: 'Arabe',
lang_be: 'Biélorusse',
lang_bg: 'Bulgare',
lang_cs: 'Tchèque',
lang_da: 'Danois',
lang_el: 'Grec',
lang_eo: 'Espéranto',
lang_fi: 'Finnois',
lang_fr_ca: 'Français (Canada)',
lang_he: 'Hébreu',
lang_hr: 'Croate',
lang_hu: 'Hongrois',
lang_id: 'Indonésien',
lang_it: 'Italien',
lang_ka: 'Géorgien',
lang_mr: 'Marathi',
lang_nb: 'Norvégien (Bokmål)',
lang_nl: 'Néerlandais',
lang_pl: 'Polonais',
lang_ro: 'Roumain',
lang_sk: 'Slovaque',
lang_sr: 'Serbe',
lang_sv: 'Suédois',
lang_th: 'Thaï',
lang_tr: 'Turc',
lang_uk: 'Ukrainien',
lang_ug: 'Ouïghour',
lang_vi: 'Vietnamien'
},
'ru': {
langName: 'Русский',
languageSettings: '🌐 Язык',
close: 'Закрыть',
confirm: 'Подтвердить',
cancel: 'Отмена',
download: 'Скачать',
compatible_with: 'Совместимо с',
force_update: '🔄️ Принудительное обновление',
force_update_alert: 'Кэш очищен. Страница будет перезагружена для получения обновленных данных.',
titles: 'Заголовки',
title_placeholder: 'Заголовок',
bold: 'Жирный',
bold_placeholder: 'жирный текст',
italic: 'Курсив',
italic_placeholder: 'курсивный текст',
underline: 'Подчёркнутый',
underline_placeholder: 'подчёркнутый текст',
strikethrough: 'Зачёркнутый',
strikethrough_placeholder: 'зачёркнутый текст',
unordered_list: 'Маркированный список',
ordered_list: 'Нумерованный список',
list_item_placeholder: 'Пункт',
quote: 'Цитата',
inline_code: 'Встроенный код',
inline_code_placeholder: 'код',
code_block: 'Блок кода',
code_block_placeholder: 'код здесь',
horizontal_line: 'Горизонтальная линия',
horizontal_line_style: 'Стиль линии',
prompt_hr_size: 'Размер (px)',
prompt_hr_color: 'Цвет',
link: 'Ссылка',
prompt_insert_url: 'Введите URL:',
link_text_placeholder: 'текст ссылки',
image: 'Изображение',
prompt_insert_image_url: 'Введите URL изображения (https):',
prompt_image_title: 'Заголовок изображения (необязательно):',
image_title_placeholder: 'напр. Мое красивое изображение',
prompt_image_width: 'Ширина (необязательно):',
prompt_image_height: 'Высота (необязательно):',
video: 'Видео',
prompt_video_type: 'Тип видео',
video_type_embed: 'Вставка (YouTube, Bilibili)',
video_type_html5: 'HTML5 видео (прямая ссылка)',
prompt_video_poster_url: 'URL постера',
prompt_insert_video_url: 'Введите URL видео:',
prompt_video_width: 'Ширина (необязательно):',
prompt_video_height: 'Высота (необязательно):',
alert_invalid_video_url: 'Неверный или неподдерживаемый URL видео.',
table: 'Таблица',
prompt_columns: 'Количество столбцов:',
prompt_rows: 'Количество строк:',
table_header_placeholder: 'Заголовок',
table_cell_placeholder: 'Ячейка',
subscript: 'Подстрочный',
subscript_placeholder: 'под',
superscript: 'Надстрочный',
superscript_placeholder: 'над',
highlight: 'Выделение',
highlight_placeholder: 'выделенный',
keyboard: 'Клавиши',
keyboard_placeholder: 'Ctrl+C',
abbreviation: 'Аббревиатура',
prompt_abbreviation_meaning: 'Что означает эта аббревиатура?',
abbreviation_placeholder: 'HTML',
text_color: 'Цвет текста',
colored_text_placeholder: 'цветной текст',
background_color: 'Цвет фона',
colored_background_placeholder: 'цветной фон',
details: 'Сворачиваемый блок',
details_summary_placeholder: 'Заголовок (спойлер)',
details_content_placeholder: 'Скрытый контент...',
center: 'По центру',
center_placeholder: 'текст по центру',
notFound: 'Код не найден!',
scriptIdNotFound: 'Не удалось определить ID скрипта.',
downloading: 'Загрузка...',
downloadError: 'Произошла ошибка при скачивании скрипта.',
downloadTimeout: 'Время ожидания скачивания скрипта истекло.',
info_tooltip: 'Горячие клавиши',
info_shortcuts_title: 'Сочетания клавиш',
info_header_shortcut: 'Сочетание',
info_header_action: 'Действие',
info_shortcut_tab: 'Вставляет отступ (Tab)',
info_shortcut_shift_enter: 'Вставляет перенос строки <span style="color: #d21934;"><strong><br></strong></span>',
info_shortcut_ctrl_d: 'Оборачивает выделение в тег <span style="color: #d21934;"><strong><div></strong></span>',
info_shortcut_ctrl_p: 'Оборачивает выделение в параграф <span style="color: #d21934;"><strong><p></strong></span>',
info_shortcut_ctrl_m: 'Оборачивает выделение в блок кода Markdown',
info_shortcut_ctrl_space: 'Вставляет неразрывный пробел <span style="color: #d21934;"><strong>;nbsp</strong></span>',
prompt_link_text: 'Текст ссылки:',
prompt_abbreviation_text: 'Текст аббревиатуры:',
border_style: 'Стиль границы',
prompt_border_size: 'Толщина границы (px)',
prompt_border_color: 'Цвет границы',
prompt_border_text: 'Текст',
prompt_border_tag_type: 'Тип тега',
border_text_placeholder: 'Текст',
ai_translate: 'Перевод с помощью ИИ',
alert_text_empty: 'Текстовое поле пусто.',
prompt_translate_to: 'Перевести на:',
prompt_ai_model: 'Модель ИИ:',
prompt_api_key: 'Ключ API (Gemini API):',
placeholder_api_key: 'Вставьте ключ AIza... сюда',
alert_translation_error: 'Ошибка перевода: ',
error_no_text: 'ИИ не вернул корректный текст.',
error_api_processing: 'Ошибка обработки ответа API.',
error_connection: 'Ошибка подключения к интернету.',
error_generic: 'Ошибка',
selection: 'Выделение',
api_help_title: 'Как получить бесплатный API-ключ',
api_help_text: 'Google предлагает щедрую бесплатную квоту. Чтобы воспользоваться ею, перейдите в Google AI Studio по ссылке ниже, войдите в свой аккаунт и нажмите «Create API key». Просто скопируйте сгенерированный ключ и вставьте его в скрипт.',
api_help_link_text: 'Получить API-ключ',
api_help_tooltip: 'Помощь: Как получить ключ',
lang_en: 'Английский',
lang_pt_br: 'Португальский (Бразилия)',
lang_zh_cn: 'Китайский (Упрощенный)',
lang_zh_tw: 'Китайский (Традиционный)',
lang_es: 'Испанский',
lang_fr: 'Французский',
lang_ru: 'Русский',
lang_de: 'Немецкий',
lang_ja: 'Японский',
lang_ko: 'Корейский',
lang_ckb: 'Курдский (Сорани)',
lang_ar: 'Арабский',
lang_be: 'Белорусский',
lang_bg: 'Болгарский',
lang_cs: 'Чешский',
lang_da: 'Датский',
lang_el: 'Греческий',
lang_eo: 'Эсперанто',
lang_fi: 'Финский',
lang_fr_ca: 'Французский (Канада)',
lang_he: 'Иврит',
lang_hr: 'Хорватский',
lang_hu: 'Венгерский',
lang_id: 'Индонезийский',
lang_it: 'Итальянский',
lang_ka: 'Грузинский',
lang_mr: 'Маратхи',
lang_nb: 'Норвежский (Букмол)',
lang_nl: 'Голландский',
lang_pl: 'Польский',
lang_ro: 'Румынский',
lang_sk: 'Словацкий',
lang_sr: 'Сербский',
lang_sv: 'Шведский',
lang_th: 'Тайский',
lang_tr: 'Турецкий',
lang_uk: 'Украинский',
lang_ug: 'Уйгурский',
lang_vi: 'Вьетнамский'
},
'zh-CN': {
langName: '简体中文',
languageSettings: '🌐 语言',
close: '关闭',
confirm: '确认',
cancel: '取消',
download: '下载',
compatible_with: '兼容',
force_update: '🔄️ 强制更新',
force_update_alert: '缓存已清除。页面将重新加载以获取最新数据。',
titles: '标题',
title_placeholder: '标题',
bold: '粗体',
bold_placeholder: '粗体文本',
italic: '斜体',
italic_placeholder: '斜体文本',
underline: '下划线',
underline_placeholder: '下划线文本',
strikethrough: '删除线',
strikethrough_placeholder: '删除线文本',
unordered_list: '无序列表',
ordered_list: '有序列表',
list_item_placeholder: '项目',
quote: '引用',
inline_code: '行内代码',
inline_code_placeholder: '代码',
code_block: '代码块',
code_block_placeholder: '在此处编写代码',
horizontal_line: '水平线',
horizontal_line_style: '水平线样式',
prompt_hr_size: '大小 (px)',
prompt_hr_color: '颜色',
link: '链接',
prompt_insert_url: '请输入网址:',
link_text_placeholder: '链接文本',
image: '图片',
prompt_insert_image_url: '请输入图片网址 (https):',
prompt_image_title: '图片标题(可选):',
image_title_placeholder: '例如:我美丽的图片',
prompt_image_width: '宽度(可选):',
prompt_image_height: '高度(可选):',
video: '视频',
prompt_video_type: '视频类型',
video_type_embed: '嵌入 (YouTube, 哔哩哔哩)',
video_type_html5: 'HTML5 视频 (直接链接)',
prompt_video_poster_url: '封面图片链接 (poster)',
prompt_insert_video_url: '请输入视频网址:',
prompt_video_width: '宽度(可选):',
prompt_video_height: '高度(可选):',
alert_invalid_video_url: '无效或不支持的视频网址。',
table: '表格',
prompt_columns: '列数:',
prompt_rows: '行数:',
table_header_placeholder: '标题',
table_cell_placeholder: '单元格',
subscript: '下标',
subscript_placeholder: '下标',
superscript: '上标',
superscript_placeholder: '上标',
highlight: '标记',
highlight_placeholder: '标记',
keyboard: '键盘',
keyboard_placeholder: 'Ctrl+C',
abbreviation: '缩写',
prompt_abbreviation_meaning: '缩写的含义是什么?',
abbreviation_placeholder: 'HTML',
text_color: '文字颜色',
colored_text_placeholder: '彩色文本',
background_color: '背景颜色',
colored_background_placeholder: '彩色背景',
details: '可折叠部分',
details_summary_placeholder: '摘要或标题',
details_content_placeholder: '要隐藏的内容...',
center: '居中',
center_placeholder: '居中文字',
notFound: '未找到代码!',
scriptIdNotFound: '无法识别脚本 ID。',
downloading: '下载中...',
downloadError: '下载脚本时发生错误。',
downloadTimeout: '脚本下载超时。',
info_tooltip: '快捷方式',
info_shortcuts_title: '键盘快捷键',
info_header_shortcut: '快捷键',
info_header_action: '功能',
info_shortcut_tab: '插入一个制表符',
info_shortcut_shift_enter: '插入一个换行符 <span style="color: #d21934;"><strong><br></strong></span>',
info_shortcut_ctrl_d: '将所选内容包裹在 <span style="color: #d21934;"><strong><div></strong></span> 标签中',
info_shortcut_ctrl_p: '将所选内容包裹在 <span style="color: #d21934;"><strong><p></strong></span> 段落中',
info_shortcut_ctrl_m: '将所选内容包裹在 markdown 代码块中',
info_shortcut_ctrl_space: '插入不间断空格 <span style="color: #d21934;"><strong>;nbsp</strong></span>',
prompt_link_text: '链接文本:',
prompt_abbreviation_text: '缩写文本:',
border_style: '边框样式',
prompt_border_size: '边框大小 (px)',
prompt_border_color: '边框颜色',
prompt_border_text: '文本',
prompt_border_tag_type: '标签类型',
border_text_placeholder: '文本',
ai_translate: 'AI 智能翻译',
alert_text_empty: '文本框为空。',
prompt_translate_to: '翻译目标语言:',
prompt_ai_model: 'AI 模型:',
prompt_api_key: 'API 密钥 (Gemini API):',
placeholder_api_key: '在此粘贴您的 AIza 密钥...',
alert_translation_error: '翻译错误:',
error_no_text: 'AI 未返回有效文本。',
error_api_processing: '处理 API 响应时出错。',
error_connection: '网络连接错误。',
error_generic: '错误',
selection: '选中内容',
api_help_title: '如何获取免费 API 密钥',
api_help_text: 'Google 提供了慷慨的免费配额。如需使用,请通过下方链接访问 Google AI Studio,登录您的帐户并点击 “Create API key”。只需复制生成的密钥并将其粘贴到脚本中即可。',
api_help_link_text: '获取 API 密钥',
api_help_tooltip: '帮助:如何获取密钥',
lang_en: '英语',
lang_pt_br: '葡萄牙语 (巴西)',
lang_zh_cn: '中文 (简体)',
lang_zh_tw: '中文 (繁体)',
lang_es: '西班牙语',
lang_fr: '法语',
lang_ru: '俄语',
lang_de: '德语',
lang_ja: '日语',
lang_ko: '韩语',
lang_ckb: '库尔德语 (索拉尼)',
lang_ar: '阿拉伯语',
lang_be: '白俄罗斯语',
lang_bg: '保加利亚语',
lang_cs: '捷克语',
lang_da: '丹麦语',
lang_el: '希腊语',
lang_eo: '世界语',
lang_fi: '芬兰语',
lang_fr_ca: '法语 (加拿大)',
lang_he: '希伯来语',
lang_hr: '克罗地亚语',
lang_hu: '匈牙利语',
lang_id: '印尼语',
lang_it: '意大利语',
lang_ka: '格鲁吉亚语',
lang_mr: '马拉地语',
lang_nb: '挪威语 (博克马尔)',
lang_nl: '荷兰语',
lang_pl: '波兰语',
lang_ro: '罗马尼亚语',
lang_sk: '斯洛伐克语',
lang_sr: '塞尔维亚语',
lang_sv: '瑞典语',
lang_th: '泰语',
lang_tr: '土耳其语',
lang_uk: '乌克兰语',
lang_ug: '维吾尔语',
lang_vi: '越南语'
},
'ja': {
langName: '日本語',
languageSettings: '🌐 言語',
close: '閉じる',
confirm: '確認',
cancel: 'キャンセル',
download: 'ダウンロード',
compatible_with: '互換性:',
force_update: '🔄️ 強制アップデート',
force_update_alert: 'キャッシュがクリアされました。ページがリロードされ、更新されたデータが取得されます。',
titles: '見出し',
title_placeholder: '見出し',
bold: '太字',
bold_placeholder: '太字のテキスト',
italic: '斜体',
italic_placeholder: '斜体のテキスト',
underline: '下線',
underline_placeholder: '下線付きテキスト',
strikethrough: '取り消し線',
strikethrough_placeholder: '取り消し線付きテキスト',
unordered_list: '順序なしリスト',
ordered_list: '順序付きリスト',
list_item_placeholder: 'アイテム',
quote: '引用',
inline_code: 'インラインコード',
inline_code_placeholder: 'コード',
code_block: 'コードブロック',
code_block_placeholder: 'ここにコード',
horizontal_line: '水平線',
horizontal_line_style: '水平線のスタイル',
prompt_hr_size: 'サイズ (px)',
prompt_hr_color: '色',
link: 'リンク',
prompt_insert_url: 'URLを入力してください:',
link_text_placeholder: 'リンクテキスト',
image: '画像',
prompt_insert_image_url: '画像URLを入力してください (https):',
prompt_image_title: '画像のタイトル (任意):',
image_title_placeholder: '例: 私の美しい画像',
prompt_image_width: '幅 (任意):',
prompt_image_height: '高さ (任意):',
video: '動画',
prompt_video_type: '動画タイプ',
video_type_embed: '埋め込み (YouTube, Bilibili)',
video_type_html5: 'HTML5 ビデオ (直接URL)',
prompt_video_poster_url: 'ポスター画像のURL',
prompt_insert_video_url: '動画URLを入力してください:',
prompt_video_width: '幅 (任意):',
prompt_video_height: '高さ (任意):',
alert_invalid_video_url: '無効またはサポートされていない動画URLです。',
table: '表',
prompt_columns: '列数:',
prompt_rows: '行数:',
table_header_placeholder: 'ヘッダー',
table_cell_placeholder: 'セル',
subscript: '下付き文字',
subscript_placeholder: '下付き',
superscript: '上付き文字',
superscript_placeholder: '上付き',
highlight: 'ハイライト',
highlight_placeholder: 'ハイライトされたテキスト',
keyboard: 'キーボード',
keyboard_placeholder: 'Ctrl+C',
abbreviation: '略語',
prompt_abbreviation_meaning: 'この略語は何の略ですか?',
abbreviation_placeholder: 'HTML',
text_color: '文字色',
colored_text_placeholder: '色付きテキスト',
background_color: '背景色',
colored_background_placeholder: '色付き背景',
details: '折りたたみ可能なセクション',
details_summary_placeholder: '概要またはタイトル',
details_content_placeholder: '非表示にするコンテンツ...',
center: '中央揃え',
center_placeholder: '中央揃えのテキスト',
notFound: 'コードが見つかりません!',
scriptIdNotFound: 'スクリプトIDを特定できませんでした。',
downloading: 'ダウンロード中...',
downloadError: 'スクリプトのダウンロード中にエラーが発生しました。',
downloadTimeout: 'スクリプトのダウンロードがタイムアウトしました。',
info_tooltip: 'ショートカット',
info_shortcuts_title: 'キーボードショートカット',
info_header_shortcut: 'ショートカット',
info_header_action: 'アクション',
info_shortcut_tab: 'タブスペースを挿入します',
info_shortcut_shift_enter: '改行 <span style="color: #d21934;"><strong><br></strong></span> を挿入します',
info_shortcut_ctrl_d: '選択範囲を <span style="color: #d21934;"><strong><div></strong></span> タグで囲みます',
info_shortcut_ctrl_p: '選択範囲を <span style="color: #d21934;"><strong><p></strong></span> 段落で囲みます',
info_shortcut_ctrl_m: '選択範囲を Markdown コードブロックで囲みます',
info_shortcut_ctrl_space: '改行しないスペース <span style="color: #d21934;"><strong>;nbsp</strong></span> を挿入します',
prompt_link_text: 'リンクテキスト:',
prompt_abbreviation_text: '略語テキスト:',
border_style: '枠線のスタイル',
prompt_border_size: '枠線のサイズ (px)',
prompt_border_color: '枠線の色',
prompt_border_text: 'テキスト',
prompt_border_tag_type: 'タグタイプ',
border_text_placeholder: 'テキスト',
ai_translate: 'AI翻訳ツール',
alert_text_empty: 'テキストボックスが空です。',
prompt_translate_to: '翻訳先言語:',
prompt_ai_model: 'AIモデル:',
prompt_api_key: 'APIキー (Gemini API):',
placeholder_api_key: 'AIzaキーをここに貼り付け...',
alert_translation_error: '翻訳エラー: ',
error_no_text: 'AIは有効なテキストを返しませんでした。',
error_api_processing: 'API応答の処理中にエラーが発生しました。',
error_connection: 'インターネット接続エラー。',
error_generic: 'エラー',
selection: '選択範囲',
api_help_title: '無料のAPIキーを取得する方法',
api_help_text: 'Googleは寛大な無料枠を提供しています。利用するには、以下のリンクからGoogle AI Studioにアクセスし、アカウントでログインして「Create API key」をクリックしてください。生成されたキーをコピーして、スクリプトに貼り付けるだけです。',
api_help_link_text: 'APIキーを取得',
api_help_tooltip: 'ヘルプ:キーの取得方法',
lang_en: '英語',
lang_pt_br: 'ポルトガル語 (ブラジル)',
lang_zh_cn: '中国語 (簡体字)',
lang_zh_tw: '中国語 (繁体字)',
lang_es: 'スペイン語',
lang_fr: 'フランス語',
lang_ru: 'ロシア語',
lang_de: 'ドイツ語',
lang_ja: '日本語',
lang_ko: '韓国語',
lang_ckb: 'クルド語 (ソラニー)',
lang_ar: 'アラビア語',
lang_be: 'ベラルーシ語',
lang_bg: 'ブルガリア語',
lang_cs: 'チェコ語',
lang_da: 'デンマーク語',
lang_el: 'ギリシャ語',
lang_eo: 'エスペラント',
lang_fi: 'フィンランド語',
lang_fr_ca: 'フランス語 (カナダ)',
lang_he: 'ヘブライ語',
lang_hr: 'クロアチア語',
lang_hu: 'ハンガリー語',
lang_id: 'インドネシア語',
lang_it: 'イタリア語',
lang_ka: 'ジョージア語',
lang_mr: 'マラーティー語',
lang_nb: 'ノルウェー語 (ブークモール)',
lang_nl: 'オランダ語',
lang_pl: 'ポーランド語',
lang_ro: 'ルーマニア語',
lang_sk: 'スロバキア語',
lang_sr: 'セルビア語',
lang_sv: 'スウェーデン語',
lang_th: 'タイ語',
lang_tr: 'トルコ語',
lang_uk: 'ウクライナ語',
lang_ug: 'ウイグル語',
lang_vi: 'ベトナム語'
},
'ko': {
langName: '한국어',
languageSettings: '🌐 언어',
close: '닫기',
confirm: '확인',
cancel: '취소',
download: '다운로드',
compatible_with: '호환 가능:',
force_update: '🔄️ 강제 업데이트',
force_update_alert: '캐시가 지워졌습니다. 페이지를 새로고침하여 업데이트된 데이터를 가져옵니다.',
titles: '제목',
title_placeholder: '제목',
bold: '굵게',
bold_placeholder: '굵은 텍스트',
italic: '기울임꼴',
italic_placeholder: '기울임꼴 텍스트',
underline: '밑줄',
underline_placeholder: '밑줄 친 텍스트',
strikethrough: '취소선',
strikethrough_placeholder: '취소선 텍스트',
unordered_list: '순서 없는 목록',
ordered_list: '순서 있는 목록',
list_item_placeholder: '항목',
quote: '인용',
inline_code: '인라인 코드',
inline_code_placeholder: '코드',
code_block: '코드 블록',
code_block_placeholder: '여기에 코드',
horizontal_line: '가로줄',
horizontal_line_style: '가로줄 스타일',
prompt_hr_size: '크기 (px)',
prompt_hr_color: '색상',
link: '링크',
prompt_insert_url: 'URL을 입력하세요:',
link_text_placeholder: '링크 텍스트',
image: '이미지',
prompt_insert_image_url: '이미지 URL을 입력하세요 (https):',
prompt_image_title: '이미지 제목 (선택 사항):',
image_title_placeholder: '예: 내 아름다운 이미지',
prompt_image_width: '너비 (선택 사항):',
prompt_image_height: '높이 (선택 사항):',
video: '동영상',
prompt_video_type: '비디오 유형',
video_type_embed: '임베드 (YouTube, Bilibili)',
video_type_html5: 'HTML5 비디오 (직접 URL)',
prompt_video_poster_url: '포스터 이미지 URL',
prompt_insert_video_url: '동영상 URL을 입력하세요:',
prompt_video_width: '너비 (선택 사항):',
prompt_video_height: '높이 (선택 사항):',
alert_invalid_video_url: '잘못되었거나 지원되지 않는 동영상 URL입니다.',
table: '표',
prompt_columns: '열 수:',
prompt_rows: '행 수:',
table_header_placeholder: '헤더',
table_cell_placeholder: '셀',
subscript: '아래 첨자',
subscript_placeholder: '아래 첨자',
superscript: '위 첨자',
superscript_placeholder: '위 첨자',
highlight: '강조',
highlight_placeholder: '강조된 텍스트',
keyboard: '키보드',
keyboard_placeholder: 'Ctrl+C',
abbreviation: '약어',
prompt_abbreviation_meaning: '이 약어는 무엇을 의미합니까?',
abbreviation_placeholder: 'HTML',
text_color: '텍스트 색상',
colored_text_placeholder: '색깔 있는 텍스트',
background_color: '배경색',
colored_background_placeholder: '색깔 있는 배경',
details: '접을 수 있는 섹션',
details_summary_placeholder: '요약 또는 제목',
details_content_placeholder: '숨길 내용...',
center: '가운데 정렬',
center_placeholder: '가운데 정렬된 텍스트',
notFound: '코드를 찾을 수 없습니다!',
scriptIdNotFound: '스크립트 ID를 식별할 수 없습니다.',
downloading: '다운로드 중...',
downloadError: '스크립트를 다운로드하는 동안 오류가 발생했습니다.',
downloadTimeout: '스크립트 다운로드 시간이 초과되었습니다.',
info_tooltip: '단축키',
info_shortcuts_title: '키보드 단축키',
info_header_shortcut: '단축키',
info_header_action: '동작',
info_shortcut_tab: '탭 공백을 삽입합니다',
info_shortcut_shift_enter: '줄 바꿈 <span style="color: #d21934;"><strong><br></strong></span>을 삽입합니다',
info_shortcut_ctrl_d: '선택 항목을 <span style="color: #d21934;"><strong><div></strong></span> 태그로 래핑합니다',
info_shortcut_ctrl_p: '선택한 내용을 <span style="color: #d21934;"><strong><p></strong></span> 문단으로 감쌉니다',
info_shortcut_ctrl_m: '선택한 내용을 마크다운 코드 블록으로 감쌉니다',
info_shortcut_ctrl_space: '줄 바꿈되지 않는 공백 <span style="color: #d21934;"><strong>;nbsp</strong></span> 을 삽입합니다',
prompt_link_text: '링크 텍스트:',
prompt_abbreviation_text: '약어 텍스트:',
border_style: '테두리 스타일',
prompt_border_size: '테두리 크기 (px)',
prompt_border_color: '테두리 색상',
prompt_border_text: '텍스트',
prompt_border_tag_type: '태그 유형',
border_text_placeholder: '텍스트',
ai_translate: 'AI 번역 도구',
alert_text_empty: '텍스트 상자가 비어 있습니다.',
prompt_translate_to: '번역 대상:',
prompt_ai_model: 'AI 모델:',
prompt_api_key: 'API 키 (Gemini API):',
placeholder_api_key: '여기에 AIza 키를 붙여넣으세요...',
alert_translation_error: '번역 오류: ',
error_no_text: 'AI가 유효한 텍스트를 반환하지 않았습니다.',
error_api_processing: 'API 응답 처리 오류.',
error_connection: '인터넷 연결 오류.',
error_generic: '오류',
selection: '선택 영역',
api_help_title: '무료 API 키를 발급받는 방법',
api_help_text: 'Google은 넉넉한 무료 할당량을 제공합니다. 이용하려면 아래 링크에서 Google AI Studio에 접속하여 계정으로 로그인한 후 "Create API key"를 클릭하세요. 생성된 키를 복사하여 스크립트에 붙여넣기만 하면 됩니다.',
api_help_link_text: 'API 키 발급받기',
api_help_tooltip: '도움말: 키 발급 방법',
lang_en: '영어',
lang_pt_br: '포르투갈어 (브라질)',
lang_zh_cn: '중국어 (간체)',
lang_zh_tw: '중국어 (번체)',
lang_es: '스페인어',
lang_fr: '프랑스어',
lang_ru: '러시아어',
lang_de: '독일어',
lang_ja: '일본어',
lang_ko: '한국어',
lang_ckb: '쿠르드어 (소라니)',
lang_ar: '아랍어',
lang_be: '벨라루스어',
lang_bg: '불가리아어',
lang_cs: '체코어',
lang_da: '덴마크어',
lang_el: '그리스어',
lang_eo: '에스페란토',
lang_fi: '핀란드어',
lang_fr_ca: '프랑스어 (캐나다)',
lang_he: '히브리어',
lang_hr: '크로아티아어',
lang_hu: '헝가리어',
lang_id: '인도네시아어',
lang_it: '이탈리아어',
lang_ka: '조지아어',
lang_mr: '마라티어',
lang_nb: '노르웨이어 (보크몰)',
lang_nl: '네덜란드어',
lang_pl: '폴란드어',
lang_ro: '루마니아어',
lang_sk: '슬로바키아어',
lang_sr: '세르비아어',
lang_sv: '스웨덴어',
lang_th: '태국어',
lang_tr: '튀르키예어',
lang_uk: '우크라이나어',
lang_ug: '위구르어',
lang_vi: '베트남어'
}
};
const translations = allTranslations;
const icons = JSON.parse(GM_getResourceText("iconsJSON"));
const myCss = GM_getResourceText("customCSS");
GM_addStyle(myCss);
function capitalizeCompatItem(item) {
return item.replace(/\b\w/g, char => char.toUpperCase());
}
let currentLang = 'en';
let languageModal = null;
const CACHE_KEY = 'Values';
const LANG_STORAGE_KEY = 'UserScriptLang';
const LAST_TAG_TYPE_KEY = 'Tag';
const LAST_COLOR_KEY = 'Color';
const SCRIPT_CONFIG = {
notificationsUrl: 'https://gist.github.com/0H4S/d55d216b4487d64c606abb5d4f097fe0',
scriptVersion: '1.9',
};
const notifier = new ScriptNotifier(SCRIPT_CONFIG);
notifier.run();
function getTranslation(key) {
return translations[currentLang] ?.[key] || translations.en[key];
}
async function determineLanguage() {
const savedLang = await GM_getValue(LANG_STORAGE_KEY);
if (savedLang && translations[savedLang]) {
currentLang = savedLang;
return;
}
const browserLang = (navigator.language || navigator.userLanguage).toLowerCase();
if (browserLang.startsWith('pt')) currentLang = 'pt-BR';
else if (browserLang.startsWith('zh')) currentLang = 'zh-CN';
else if (browserLang.startsWith('en')) currentLang = 'en';
else if (browserLang.startsWith('es')) currentLang = 'es';
else if (browserLang.startsWith('fr')) currentLang = 'fr';
else if (browserLang.startsWith('ru')) currentLang = 'ru';
else if (browserLang.startsWith('ja')) currentLang = 'ja';
else if (browserLang.startsWith('ko')) currentLang = 'ko';
else currentLang = 'en';
}
function registerLanguageMenu() {
GM_registerMenuCommand(getTranslation('languageSettings'), () => {
showModal(languageModal);
});
}
function registerForceUpdateMenu() {
GM_registerMenuCommand(getTranslation('force_update'), forceUpdate);
}
function showModal(modal) {
if (!modal) return;
modal.style.display = 'flex';
setTimeout(() => {
const box = modal.querySelector('.lang-modal-box');
box.style.opacity = '1';
box.style.transform = 'scale(1)';
}, 10);
}
function hideModal(modal) {
if (!modal) return;
const box = modal.querySelector('.lang-modal-box');
box.style.opacity = '0';
box.style.transform = 'scale(0.95)';
setTimeout(() => {
modal.style.display = 'none';
}, 200);
}
function createLanguageModal() {
const overlay = document.createElement('div');
overlay.className = 'lang-modal-overlay';
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
hideModal(overlay);
}
});
const box = document.createElement('div');
box.className = 'lang-modal-box';
const buttonsContainer = document.createElement('div');
buttonsContainer.className = 'lang-modal-buttons';
Object.keys(translations).forEach(langKey => {
const btn = document.createElement('button');
btn.textContent = translations[langKey].langName;
btn.onclick = async () => {
await GM_setValue(LANG_STORAGE_KEY, langKey);
window.location.reload();
};
buttonsContainer.appendChild(btn);
});
box.appendChild(buttonsContainer);
overlay.appendChild(box);
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
function applyTheme(isDark) {
box.classList.toggle('dark-theme', isDark);
box.classList.toggle('light-theme', !isDark);
}
applyTheme(mediaQuery.matches);
mediaQuery.addEventListener('change', e => applyTheme(e.matches));
return overlay;
}
async function forceUpdate() {
alert(getTranslation('force_update_alert'));
await GM_deleteValue(CACHE_KEY);
window.location.reload();
}
// #endregion
// ================
// #region ESTILIZAR
// ================
function isScriptPage() {
const path = window.location.pathname;
return /^\/([a-z]{2}(-[A-Z]{2})?\/)?scripts\/\d+-[^/]+$/.test(path);
}
function addAdditionalInfoSeparator() {
const additionalInfo = document.getElementById('additional-info');
if (additionalInfo && !additionalInfo.previousElementSibling?.matches('hr.bgs-info-separator')) {
const hr = document.createElement('hr');
hr.className = 'bgs-info-separator';
additionalInfo.before(hr);
}
}
function highlightScriptDescription() {
const descriptionElements = document.querySelectorAll('#script-description, .script-description.description');
descriptionElements.forEach(element => {
const scriptLink = element.closest('article, li')?.querySelector('a.script-link');
const path = scriptLink ? normalizeScriptPath(new URL(scriptLink.href).pathname) : normalizeScriptPath(window.location.pathname);
if (element && element.parentElement.tagName !== 'BLOCKQUOTE') {
const blockquoteWrapper = document.createElement('blockquote');
blockquoteWrapper.className = 'script-description-blockquote';
if (path) {
blockquoteWrapper.dataset.bgfPath = path;
}
element.parentNode.insertBefore(blockquoteWrapper, element);
blockquoteWrapper.appendChild(element);
}
});
}
function makeDiscussionClickable() {
document.querySelectorAll('.discussion-list-container').forEach(container => {
container.removeEventListener('click', handleDiscussionClick);
container.addEventListener('click', handleDiscussionClick);
});
}
function handleDiscussionClick(e) {
if (e.target.tagName === 'A' ||
e.target.closest('a') ||
e.target.closest('.user-link') ||
e.target.closest('.badge-author') ||
e.target.closest('.rating-icon')) {
return;
}
const discussionLink = this.querySelector('.discussion-title');
if (discussionLink && discussionLink.href) {
window.location.href = discussionLink.href;
}
}
function applySyntaxHighlighting() {
document.querySelectorAll('pre code').forEach(block => {
if (block.dataset.highlighted === 'true') { return; }
const code = block.textContent;
block.innerHTML = highlight(code);
block.dataset.highlighted = 'true';
});
}
function escapeHtml(str) {
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
}
function highlight(code) {
const keywords = new Set(['const', 'let', 'var', 'function', 'return', 'if', 'else', 'for', 'while', 'of', 'in', 'async', 'await', 'try', 'catch', 'new', 'import', 'export', 'from', 'class', 'extends', 'super', 'true', 'false', 'null', 'undefined', 'document', 'window']);
const tokens = [];
let cursor = 0;
const tokenDefinitions = [
{ type: 'url', regex: /^(https?:\/\/[^\s"'`<>]+)/ },
{ type: 'comment-special', regex: /^(\/\/[^\r\n]*)/ },
{ type: 'comment', regex: /^(\/\*[\s\S]*?\*\/|<!--[\s\S]*?-->)/ },
{ type: 'string', regex: /^(`(?:\\.|[^`])*`|"(?:\\.|[^"])*"|'(?:\\.|[^'])*')/ },
{ type: 'tag-punctuation', regex: /^(<\/?|\/>|>)/ },
{ type: 'tag-name', regex: /^([\w-]+)/, context: (t) => { const l=t[t.length-1]; return l&&l.type==='tag-punctuation'&&l.content.startsWith('<') }},
{ type: 'attribute', regex: /^([\w-]+)/, context: (t) => { for(let i=t.length-1;i>=0;i--){const n=t[i];if(n.type==='tag-punctuation'&&n.content.includes('>'))return!1;if(n.type==='tag-name')return!0;if(n.type==='whitespace')continue}return!1 }},
{ type: 'regex', regex: /^(\/(?!\*)(?:[^\r\n/\\]|\\.)+\/[gimyus]*)/ },
{ type: 'number', regex: /^\b-?(\d+(\.\d+)?)\b/ },
{ type: 'keyword', regex: new RegExp(`^\\b(${Array.from(keywords).join('|')})\\b`) },
{ type: 'function', regex: /^([a-zA-Z_][\w_]*)(?=\s*\()/ },
{ type: 'property', regex: /^\.([a-zA-Z_][\w_]*)/ },
{ type: 'operator', regex: /^(==?=?|!=?=?|=>|[+\-*/%&|^<>]=?|\?|:|=)/ },
{ type: 'punctuation', regex: /^([,;(){}[\]])/ },
{ type: 'whitespace', regex: /^\s+/ },
{ type: 'unknown', regex: /^./ },
];
let processedCode = escapeHtml(code);
while (cursor < processedCode.length) {
let matched = false;
for (const def of tokenDefinitions) {
if (def.context && !def.context(tokens)) { continue; }
const match = def.regex.exec(processedCode.slice(cursor));
if (match) {
const content = match[0];
if (def.type === 'function' && keywords.has(content)) { continue; }
tokens.push({ type: def.type, content });
cursor += content.length;
matched = true;
break;
}
}
if (!matched) {
tokens.push({ type: 'unknown', content: processedCode[cursor] });
cursor++;
}
}
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].type === 'string') {
let nextToken = null;
for(let j=i+1;j<tokens.length;j++){if(tokens[j].type!=='whitespace'){nextToken=tokens[j];break}}
if (nextToken && nextToken.content === ':') { tokens[i].type = 'json-key'; }
}
}
return tokens.map(token => {
if (['whitespace', 'unknown', 'url'].includes(token.type)) return token.content;
if (token.type === 'property') return `<span class="sh-punctuation">.</span><span class="sh-property">${token.content.slice(1)}</span>`;
return `<span class="sh-${token.type}">${token.content}</span>`;
}).join('');
}
// #endregion
// ================
// #region ÍCONES
// ================
let iconCache;
const processedKeys = new Set();
async function saveCache() {
await GM_setValue(CACHE_KEY, iconCache);
}
function normalizeScriptPath(pathname) {
let withoutLocale = pathname.replace(/^\/[a-z]{2}(?:-[A-Z]{2})?\//, '/');
const match = withoutLocale.match(/^\/scripts\/\d+-.+?(?=\/|$)/);
return match ? match[0] : null;
}
function extractScriptIdFromNormalizedPath(normalized) {
const match = normalized.match(/\/scripts\/(\d+)-/);
return match ? match[1] : null;
}
function createIconElement(src, isHeader = false) {
const img = document.createElement('img');
img.src = src;
img.alt = '';
if (isHeader) {
img.style.cssText = `
width: 80px;
height: 80px;
margin-right: 10px;
vertical-align: middle;
border-radius: 4px;
object-fit: contain;
pointer-events: none;
`;
} else {
img.style.cssText = `
width: 40px;
height: 40px;
margin-right: 8px;
vertical-align: middle;
border-radius: 3px;
object-fit: contain;
pointer-events: none;
`;
}
img.loading = 'lazy';
return img;
}
function extractMetadataFromContent(content) {
if (typeof content !== 'string') return {};
const metadata = {};
const lines = content.split('\n');
const supportedTags = new Set([ '@icon', '@bgf-colorLT', '@bgf-colorDT', '@bgf-compatible', '@bgf-copyright', '@bgf-social' ]);
for (const line of lines) {
const trimmedLine = line.trim();
if (trimmedLine.startsWith('// ==/UserScript==')) break;
if (!trimmedLine.startsWith('// @')) continue;
const match = trimmedLine.match(/\/\/\s*(@[a-zA-Z0-9-]+)\s+(.+)/);
if (!match) continue;
const key = match[1];
let value = match[2].trim();
if (supportedTags.has(key) && !metadata.hasOwnProperty(key)) {
if (key === '@bgf-colorLT' || key === '@bgf-colorDT') {
const colorRegex = /(#[0-9a-fA-F]{3,8}|(?:rgba?|hsla?)\s*\([^)]+\))/;
const colorMatch = value.match(colorRegex);
if (colorMatch) {
value = colorMatch[0];
} else {
value = value.split(',')[0].trim();
}
}
metadata[key] = value;
}
}
return metadata;
}
function isValidIconUrl(url) {
return url && (url.startsWith('http') || url.startsWith(''));
}
async function processScript(normalizedPath, targetElement, isHeader = false) {
if (processedKeys.has(normalizedPath) && isHeader) {
applyBfgFeatures(iconCache[normalizedPath]);
}
if (processedKeys.has(normalizedPath) && !isHeader) {
const cached = iconCache[normalizedPath];
if (cached && isValidIconUrl(cached.iconUrl)) {
targetElement.prepend(createIconElement(cached.iconUrl, isHeader));
}
return;
}
processedKeys.add(normalizedPath);
const cached = iconCache[normalizedPath];
const now = Date.now();
const applyColorToBlockquote = (metadata) => {
const blockquotes = document.querySelectorAll(`blockquote.script-description-blockquote[data-bgf-path="${normalizedPath}"]`);
if (blockquotes.length === 0) return;
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const colorToApply = isDarkMode ? metadata.bgfColorDT : metadata.bgfColorLT;
blockquotes.forEach(bq => {
if (colorToApply) {
bq.style.setProperty('border-left-color', colorToApply, 'important');
} else {
bq.style.removeProperty('border-left-color');
}
});
};
if (cached && now - cached.ts < 7 * 24 * 60 * 60 * 1000) {
if (isValidIconUrl(cached.iconUrl)) {
targetElement.prepend(createIconElement(cached.iconUrl, isHeader));
}
applyColorToBlockquote(cached);
if (isHeader) {
applyBfgFeatures(cached);
}
return;
}
const scriptId = extractScriptIdFromNormalizedPath(normalizedPath);
if (!scriptId) {
iconCache[normalizedPath] = { ts: now };
await saveCache();
return;
}
const scriptUrl = `https://update.greasyfork.org/scripts/${scriptId}.js`;
GM_xmlhttpRequest({
method: 'GET',
url: scriptUrl,
timeout: 6000,
onload: async function (res) {
if (typeof res.responseText !== 'string') {
iconCache[normalizedPath] = { ts: now };
await saveCache();
return;
}
const rawMetadata = extractMetadataFromContent(res.responseText);
const metadata = {
iconUrl: rawMetadata['@icon'] || null,
bgfColorLT: rawMetadata['@bgf-colorLT'] || null,
bgfColorDT: rawMetadata['@bgf-colorDT'] || null,
bgfCompatible: rawMetadata['@bgf-compatible'] || null,
bgfCopyright: rawMetadata['@bgf-copyright'] || null,
bgfSocial: rawMetadata['@bgf-social'] || null,
ts: now
};
iconCache[normalizedPath] = metadata;
await saveCache();
if (isValidIconUrl(metadata.iconUrl)) {
targetElement.prepend(createIconElement(metadata.iconUrl, isHeader));
}
applyColorToBlockquote(metadata);
if (isHeader) {
applyBfgFeatures(metadata);
}
},
onerror: async function () {
iconCache[normalizedPath] = { ts: now };
await saveCache();
}
});
}
function handleScriptLink(linkEl) {
if (linkEl._handled) return;
linkEl._handled = true;
const href = linkEl.getAttribute('href');
if (!href || !href.startsWith('/')) return;
try {
const url = new URL(href, window.location.origin);
const normalized = normalizeScriptPath(url.pathname);
if (!normalized) return;
setTimeout(() => processScript(normalized, linkEl, false), 0);
} catch (e) {}
}
function handleMainHeaderH2() {
const headers = document.querySelectorAll('header');
for (const header of headers) {
const h2 = header.querySelector('h2');
const desc = header.querySelector('p.script-description');
if (h2 && desc && !h2._handled) {
h2._handled = true;
const normalized = normalizeScriptPath(window.location.pathname);
if (!normalized) return;
setTimeout(() => processScript(normalized, h2, true), 0);
break;
}
}
}
function processIconElements() {
document.querySelectorAll('a.script-link:not([data-icon-processed])')
.forEach(el => {
el.setAttribute('data-icon-processed', '1');
handleScriptLink(el);
});
handleMainHeaderH2();
}
// #endregion
// ================
// #region METADADOS
// ================
function applyBfgFeatures(metadata) {
if (!metadata) return;
applyBfgCompatibility(metadata.bgfCompatible);
applyBfgCopyright(metadata.bgfCopyright);
applyBfgSocial(metadata.bgfSocial);
}
function applyBfgCompatibility(compatValue) {
if (!compatValue) return;
const compatDd = document.querySelector('dd.script-show-compatibility');
if (!compatDd) {
return;
}
let compatContainer = compatDd.querySelector('span');
if (!compatContainer) {
compatContainer = document.createElement('span');
compatDd.innerHTML = '';
compatDd.appendChild(compatContainer);
}
const compatItems = compatValue.split(',').map(item => item.trim().toLowerCase());
compatItems.forEach(item => {
if (!icons[item] || compatContainer.querySelector(`.bgf-compat-${item}`)) {
return;
}
const img = document.createElement('img');
img.className = `browser-compatible bgf-compat-${item}`;
const displayName = capitalizeCompatItem(item);
img.alt = `${getTranslation('compatible_with')} ${displayName}`;
img.title = `${getTranslation('compatible_with')} ${displayName}`;
img.style.marginLeft = '1px';
img.src = `data:image/svg+xml;utf8,${encodeURIComponent(icons[item])}`;
compatContainer.appendChild(img);
});
}
function reapplyAllBlockquoteColors() {
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const allBlockquotes = document.querySelectorAll('blockquote.script-description-blockquote[data-bgf-path]');
allBlockquotes.forEach(bq => {
const path = bq.dataset.bgfPath;
if (!path || !iconCache[path]) return;
const metadata = iconCache[path];
const colorToApply = isDarkMode ? metadata.bgfColorDT : metadata.bgfColorLT;
if (colorToApply) {
bq.style.setProperty('border-left-color', colorToApply, 'important');
} else {
bq.style.removeProperty('border-left-color');
}
});
}
function setupThemeChangeListener() {
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
mediaQuery.addEventListener('change', reapplyAllBlockquoteColors);
}
function applyBfgCopyright(copyrightValue) {
if (!copyrightValue || document.querySelector('.script-show-copyright')) return;
const copyrightRegex = /\[(.{1,50})\]\((https:\/\/gist\.github\.com\/[^)]+)\)/;
const match = copyrightValue.match(copyrightRegex);
if (!match) return;
const licenseDd = document.querySelector('dd.script-show-license');
if (!licenseDd) return;
const text = match[1];
const url = match[2];
const copyrightDt = document.createElement('dt');
copyrightDt.className = 'script-show-copyright';
copyrightDt.innerHTML = '<span>Copyright</span>';
const copyrightDd = document.createElement('dd');
copyrightDd.className = 'script-show-copyright';
copyrightDd.style.alignSelf = 'center';
const link = document.createElement('a');
link.href = url;
link.textContent = text;
link.target = '_blank';
link.rel = 'noopener noreferrer';
const span = document.createElement('span');
span.appendChild(link);
copyrightDd.appendChild(span);
licenseDd.after(copyrightDt, copyrightDd);
}
function applyBfgSocial(socialValue) {
if (!socialValue || document.querySelector('.script-show-social')) return;
const authorDd = document.querySelector('dd.script-show-author');
if (!authorDd) return;
const socialDomainMap = {
'instagram.com': { icon: icons.instagram, name: 'Instagram' },
'facebook.com': { icon: icons.facebook, name: 'Facebook' },
'x.com': { icon: icons.x, name: 'X / Twitter' },
'youtube.com': { icon: icons.youtube, name: 'YouTube' },
'bilibili.com': { icon: icons.bilibili, name: 'Bilibili' },
'tiktok.com': { icon: icons.tiktok, name: 'TikTok' },
'douyin.com': { icon: icons.tiktok, name: 'Douyin' },
'github.com': { icon: icons.github, name: 'GitHub' },
'linkedin.com': { icon: icons.linkedin, name: 'LinkedIn' },
};
const urls = socialValue.split(',').map(url => url.trim());
const validLinks = [];
let tiktokFamilyProcessed = false;
urls.forEach(url => {
try {
const domain = new URL(url).hostname.replace('www.', '');
if (socialDomainMap[domain]) {
if (domain === 'tiktok.com' || domain === 'douyin.com') {
if (tiktokFamilyProcessed) return;
tiktokFamilyProcessed = true;
}
validLinks.push({ url, ...socialDomainMap[domain] });
}
} catch (e) {}
});
if (validLinks.length === 0) return;
const socialDt = document.createElement('dt');
socialDt.className = 'script-show-social';
socialDt.innerHTML = '<span>Social</span>';
const socialDd = document.createElement('dd');
socialDd.className = 'script-show-social';
socialDd.style.cssText = 'display: flex; gap: 8px; align-items: center; align-self: center; z-index: 10;';
validLinks.forEach(linkInfo => {
const link = document.createElement('a');
link.href = linkInfo.url;
link.title = linkInfo.name;
link.target = '_blank';
link.rel = 'noopener noreferrer';
link.innerHTML = linkInfo.icon;
const svg = link.querySelector('svg');
if (svg) {
svg.style.width = '20px';
svg.style.height = '20px';
svg.style.verticalAlign = 'middle';
}
socialDd.appendChild(link);
});
authorDd.after(socialDt, socialDd);
}
// #endregion
// ================
// #region EDITOR HTML
// ================
async function translateWithGemini(text, targetLang, modelId, apiKey) {
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "POST",
url: `https://generativelanguage.googleapis.com/v1beta/models/${modelId}:generateContent?key=${apiKey}`,
headers: { "Content-Type": "application/json" },
data: JSON.stringify({
contents: [{
parts: [{
text: `Translate the following text to ${targetLang}.
CRITICAL OUTPUT RULES:
- Return ONLY the translated text.
- Do NOT include conversational filler (e.g., "Here is the translation", "Sure").
- Do NOT use Markdown code blocks (no \`\`\`).
- Do NOT include explanations or notes.
- Do NOT wrap the output in quotes unless the original text has them.
CORE INSTRUCTIONS:
1. Grammar & Correction: Ensure the translation is grammatically perfect and free of spelling errors. If the source text has structural flaws, fix them in the translated output to meet high linguistic standards.
2. Native Coherence: The text must sound natural to a native speaker, prioritizing flow over literal word-for-word translation.
3. Strict Fidelity: Do NOT summarize, remove, or add information. Keep the original meaning intact.
4. Technical Safety: Do NOT translate or change HTML tags, Markdown syntax, URLs, or variable placeholders (like {{value}}).
Text to translate:
${text}`
}]
}]
}),
onload: (response) => {
try {
if (response.status !== 200) {
const errorJson = JSON.parse(response.responseText);
reject(`${getTranslation('error_generic')} ${response.status} (${modelId}): ${errorJson.error?.message || 'Desconhecido'}`);
return;
}
const data = JSON.parse(response.responseText);
if (data.candidates && data.candidates[0]?.content?.parts[0]?.text) {
resolve(data.candidates[0].content.parts[0].text.trim());
} else {
reject(getTranslation('error_no_text'));
}
} catch (e) {
console.error(e);
reject(getTranslation('error_api_processing'));
}
},
onerror: (err) => reject(getTranslation('error_connection'))
});
});
}
function getTargetLanguages() {
return [
{ value: 'English (EN)', text: getTranslation('lang_en') },
{ value: 'Portuguese Brazil (PT-BR)', text: getTranslation('lang_pt_br') },
{ value: 'Chinese Simplified (ZH-CN)', text: getTranslation('lang_zh_cn') },
{ value: 'Chinese Traditional (ZH-TW)', text: getTranslation('lang_zh_tw') },
{ value: 'Spanish (ES)', text: getTranslation('lang_es') },
{ value: 'French (FR)', text: getTranslation('lang_fr') },
{ value: 'Russian (RU)', text: getTranslation('lang_ru') },
{ value: 'German (DE)', text: getTranslation('lang_de') },
{ value: 'Japanese (JA)', text: getTranslation('lang_ja') },
{ value: 'Korean (KO)', text: getTranslation('lang_ko') },
{ value: 'Kurdish (CKB)', text: getTranslation('lang_ckb') },
{ value: 'Arabic (AR)', text: getTranslation('lang_ar') },
{ value: 'Belarusian (BE)', text: getTranslation('lang_be') },
{ value: 'Bulgarian (BG)', text: getTranslation('lang_bg') },
{ value: 'Czech (CS)', text: getTranslation('lang_cs') },
{ value: 'Danish (DA)', text: getTranslation('lang_da') },
{ value: 'Greek (EL)', text: getTranslation('lang_el') },
{ value: 'Esperanto (EO)', text: getTranslation('lang_eo') },
{ value: 'Finnish (FI)', text: getTranslation('lang_fi') },
{ value: 'French Canadian (FR-CA)', text: getTranslation('lang_fr_ca') },
{ value: 'Hebrew (HE)', text: getTranslation('lang_he') },
{ value: 'Croatian (HR)', text: getTranslation('lang_hr') },
{ value: 'Hungarian (HU)', text: getTranslation('lang_hu') },
{ value: 'Indonesian (ID)', text: getTranslation('lang_id') },
{ value: 'Italian (IT)', text: getTranslation('lang_it') },
{ value: 'Georgian (KA)', text: getTranslation('lang_ka') },
{ value: 'Marathi (MR)', text: getTranslation('lang_mr') },
{ value: 'Norwegian Bokmål (NB)', text: getTranslation('lang_nb') },
{ value: 'Dutch (NL)', text: getTranslation('lang_nl') },
{ value: 'Polish (PL)', text: getTranslation('lang_pl') },
{ value: 'Romanian (RO)', text: getTranslation('lang_ro') },
{ value: 'Slovak (SK)', text: getTranslation('lang_sk') },
{ value: 'Serbian (SR)', text: getTranslation('lang_sr') },
{ value: 'Swedish (SV)', text: getTranslation('lang_sv') },
{ value: 'Thai (TH)', text: getTranslation('lang_th') },
{ value: 'Turkish (TR)', text: getTranslation('lang_tr') },
{ value: 'Ukrainian (UK)', text: getTranslation('lang_uk') },
{ value: 'Uyghur (UG)', text: getTranslation('lang_ug') },
{ value: 'Vietnamese (VI)', text: getTranslation('lang_vi') }
];
}
function getGeminiModels() {
return [
{ value: 'gemini-3-pro-preview', text: 'Gemini 3 Pro Preview' },
{ value: 'gemini-2.5-pro', text: 'Gemini 2.5 Pro' },
{ value: 'gemini-2.5-flash', text: 'Gemini 2.5 Flash' },
{ value: 'gemini-2.5-flash-lite', text: 'Gemini 2.5 Flash Lite' },
{ value: 'gemini-2.0-flash', text: 'Gemini 2.0 Flash' },
{ value: 'gemini-2.0-flash-lite', text: 'Gemini 2.0 Flash Lite' },
{ value: 'gemini-flash-latest', text: 'Gemini Flash Latest' },
{ value: 'gemini-flash-lite-latest', text: 'Gemini Flash Lite Latest' }
];
}
function insertText(textarea, prefix, suffix = '', placeholder = '') {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selected = textarea.value.substring(start, end);
const text = selected || placeholder;
if (!selected && !placeholder) {
textarea.setRangeText(prefix + suffix, start, end);
const cursorPosition = start + prefix.length;
textarea.setSelectionRange(cursorPosition, cursorPosition);
} else {
textarea.setRangeText(prefix + text + suffix, start, end, selected ? 'end' : 'select');
}
textarea.focus();
}
function createToolbarButton(def) {
const btn = document.createElement('button');
btn.type = 'button';
btn.className = 'txt-editor-toolbar-button';
btn.dataset.tooltip = def.title;
btn.innerHTML = def.icon || def.label;
btn.addEventListener('click', e => {
e.preventDefault();
def.action();
});
return btn;
}
function showCustomAlert(message) {
const overlay = document.createElement('div');
overlay.className = 'custom-prompt-overlay';
const modal = document.createElement('div');
modal.className = 'custom-prompt-box custom-alert-box';
const editorContainer = document.querySelector('.txt-editor-container');
modal.classList.add(editorContainer && editorContainer.classList.contains('dark-theme') ? 'dark-theme' : 'light-theme');
const messageP = document.createElement('p');
messageP.textContent = message;
const closeBtn = document.createElement('button');
closeBtn.textContent = getTranslation('close');
closeBtn.className = 'custom-prompt-confirm';
closeBtn.onclick = () => document.body.removeChild(overlay);
modal.append(messageP, closeBtn);
overlay.appendChild(modal);
document.body.appendChild(overlay);
closeBtn.focus();
}
function showCustomPrompt({ inputs, onConfirm }) {
const overlay = document.createElement('div');
overlay.className = 'custom-prompt-overlay';
const modal = document.createElement('div');
modal.className = 'custom-prompt-box';
const editorContainer = document.querySelector('.txt-editor-container');
modal.classList.add(editorContainer && editorContainer.classList.contains('dark-theme') ? 'dark-theme' : 'light-theme');
const form = document.createElement('form');
const inputsMap = new Map();
inputs.forEach(config => {
const label = document.createElement('label');
const labelTextContainer = document.createElement('div');
labelTextContainer.style.display = 'flex';
labelTextContainer.style.justifyContent = 'space-between';
labelTextContainer.style.alignItems = 'center';
labelTextContainer.style.marginBottom = '4px';
const textSpan = document.createElement('span');
textSpan.textContent = config.label;
labelTextContainer.appendChild(textSpan);
if (config.helpAction) {
const helpIcon = document.createElement('span');
helpIcon.innerHTML = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z"/></svg>';
helpIcon.style.cursor = 'pointer';
helpIcon.style.opacity = '0.7';
helpIcon.title = getTranslation('api_help_tooltip');
helpIcon.addEventListener('mouseenter', () => helpIcon.style.opacity = '1');
helpIcon.addEventListener('mouseleave', () => helpIcon.style.opacity = '0.7');
helpIcon.addEventListener('click', (e) => {
e.preventDefault();
config.helpAction();
});
labelTextContainer.appendChild(helpIcon);
}
label.appendChild(labelTextContainer);
let field;
if (config.type === 'select') {
field = document.createElement('select');
(config.options || []).forEach(opt => {
const option = document.createElement('option');
option.value = opt.value;
option.textContent = opt.text;
if (config.value && opt.value === config.value) {
option.selected = true;
}
field.appendChild(option);
});
} else {
field = document.createElement('input');
field.type = config.type || 'text';
field.placeholder = config.placeholder || '';
field.value = config.value || '';
field.required = config.required !== false;
if (config.type === 'number') field.min = '1';
}
label.appendChild(field);
form.appendChild(label);
inputsMap.set(config.id, field);
});
const buttons = document.createElement('div');
buttons.className = 'custom-prompt-buttons';
const confirmBtn = document.createElement('button');
confirmBtn.type = 'submit';
confirmBtn.textContent = getTranslation('confirm');
confirmBtn.className = 'custom-prompt-confirm';
const cancelBtn = document.createElement('button');
cancelBtn.type = 'button';
cancelBtn.textContent = getTranslation('cancel');
cancelBtn.className = 'custom-prompt-cancel';
cancelBtn.onclick = () => document.body.removeChild(overlay);
form.onsubmit = (e) => {
e.preventDefault();
const results = {};
for (const [id, inputElement] of inputsMap.entries()) {
results[id] = inputElement.value;
}
onConfirm(results);
document.body.removeChild(overlay);
};
buttons.append(confirmBtn, cancelBtn);
form.appendChild(buttons);
modal.appendChild(form);
overlay.appendChild(modal);
document.body.appendChild(overlay);
inputsMap.values().next().value.focus();
}
function showInfoModal() {
const overlay = document.createElement('div');
overlay.className = 'custom-prompt-overlay info-modal-overlay';
overlay.style.display = 'flex';
const modal = document.createElement('div');
modal.className = 'custom-prompt-box info-modal-box';
const editorContainer = document.querySelector('.txt-editor-container');
modal.classList.add(editorContainer && editorContainer.classList.contains('dark-theme') ? 'dark-theme' : 'light-theme');
modal.innerHTML = `
<h2>${getTranslation('info_shortcuts_title')}</h2>
<div class="info-shortcuts">
<table>
<thead>
<tr>
<th>${getTranslation('info_header_shortcut')}</th>
<th>${getTranslation('info_header_action')}</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Tab</code></td>
<td>${getTranslation('info_shortcut_tab')}</td>
</tr>
<tr>
<td><code>Shift + Enter</code></td>
<td>${getTranslation('info_shortcut_shift_enter')}</td>
</tr>
<tr>
<td><code>Ctrl + D</code></td>
<td>${getTranslation('info_shortcut_ctrl_d')}</td>
</tr>
<tr>
<td><code>Ctrl + P</code></td>
<td>${getTranslation('info_shortcut_ctrl_p')}</td>
</tr>
<tr>
<td><code>Ctrl + M</code></td>
<td>${getTranslation('info_shortcut_ctrl_m')}</td>
</tr>
<tr>
<td><code>Ctrl + Space</code></td>
<td>${getTranslation('info_shortcut_ctrl_space')}</td>
</tr>
</tbody>
</table>
</div>
<div class="custom-prompt-buttons">
<button class="custom-prompt-cancel">${getTranslation('close')}</button>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
overlay.querySelector('.custom-prompt-cancel').onclick = () => document.body.removeChild(overlay);
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
document.body.removeChild(overlay);
}
});
}
function showApiKeyHelp() {
const overlay = document.createElement('div');
overlay.className = 'custom-prompt-overlay info-modal-overlay';
overlay.style.display = 'flex';
const modal = document.createElement('div');
modal.className = 'custom-prompt-box info-modal-box';
const editorContainer = document.querySelector('.txt-editor-container');
modal.classList.add(editorContainer && editorContainer.classList.contains('dark-theme') ? 'dark-theme' : 'light-theme');
const linkStyle = `
display: inline-block;
padding: 8px 16px;
background-color: #444;
border: 1px solid #666;
color: #fff;
text-decoration: none;
border-radius: 4px;
font-size: 0.9em;
transition: background 0.2s;
cursor: pointer;
`;
modal.innerHTML = `
<h3 style="margin: 0 0 15px 0; text-align: center;">${getTranslation('api_help_title')}</h3>
<p style="margin: 0 15px 20px 15px; text-align: center; line-height: 1.6; opacity: 0.95;">
${getTranslation('api_help_text')}
</p>
<div style="text-align: center; margin-bottom: 0px;">
<a href="https://aistudio.google.com/api-keys" target="_blank" id="api-help-link" style="${linkStyle}"
onmouseover="this.style.backgroundColor='#555'"
onmouseout="this.style.backgroundColor='#444'">
${getTranslation('api_help_link_text')}
</a>
</div>
`;
overlay.appendChild(modal);
document.body.appendChild(overlay);
const linkBtn = document.getElementById('api-help-link');
if(linkBtn) linkBtn.focus();
overlay.addEventListener('click', (e) => {
if (e.target === overlay) {
document.body.removeChild(overlay);
}
});
const escListener = (e) => {
if (e.key === 'Escape') {
if (document.body.contains(overlay)) {
document.body.removeChild(overlay);
}
document.removeEventListener('keydown', escListener);
}
};
document.addEventListener('keydown', escListener);
}
async function createTextStyleEditor(textarea) {
if (textarea.dataset.editorApplied) return;
textarea.dataset.editorApplied = 'true';
textarea.addEventListener('keydown', function(e) {
if (e.key === 'Tab') {
e.preventDefault();
const start = this.selectionStart;
const end = this.selectionEnd;
this.setRangeText(' ', start, end, 'end');
}
if (e.shiftKey && e.key === 'Enter') {
e.preventDefault();
const start = this.selectionStart;
const end = this.selectionEnd;
this.setRangeText('<br>', start, end, 'end');
}
if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'd') {
e.preventDefault();
insertText(this, '<div>\n', '\n</div>', '');
}
if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'p') {
e.preventDefault();
insertText(this, '<p>', '</p>', '');
}
if (e.ctrlKey && !e.shiftKey && e.key.toLowerCase() === 'm') {
e.preventDefault();
insertText(this, '```\n', '\n```', '');
}
if (e.ctrlKey && !e.shiftKey && e.key === ' ') {
e.preventDefault();
insertText(this, ' ', '', '');
}
});
const container = document.createElement('div');
container.className = 'txt-editor-container';
const toolbar = document.createElement('div');
toolbar.className = 'txt-editor-toolbar';
const mediaQuery = window.matchMedia('(prefers-color-scheme: dark)');
function applyTheme(isDark) {
container.classList.toggle('dark-theme', isDark);
container.classList.toggle('light-theme', !isDark);
}
applyTheme(mediaQuery.matches);
mediaQuery.addEventListener('change', e => applyTheme(e.matches));
const tools = [
{ type: 'select', title: getTranslation('titles'), options: { 'H1': '1', 'H2': '2', 'H3': '3', 'H4': '4', 'H5': '5', 'H6': '6' }, action: (val) => insertText(textarea, `<h${val}>`, `</h${val}>`, getTranslation('title_placeholder')) },
{ type: 'divider' },
{ title: getTranslation('bold'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 26"><path fill="currentColor" d="M22.94 18.05a5.3 5.3 0 0 1-.7 2.82 5 5 0 0 1-2.03 1.83q-1.4.71-3.21 1c-1.22.2-1.65.3-3.3.3H2v-1.78c.32-.03 2.03-.43 2.03-1.96V6.06C4.03 4.2 2.32 3.97 2 3.92V2h11.95c3.01 0 3.64.41 4.98 1.24q2 1.25 2 3.66c0 3.09-2.47 4.93-3.28 5.11v.3c.8.08 5.3.95 5.3 5.74m-7.5-10.23A2.5 2.5 0 0 0 14.4 5.7c-.68-.51-1.49-.77-2.86-.77a24 24 0 0 0-1.58.05v6.1h.8c1.68 0 2.69-.3 3.48-.88q1.2-.87 1.2-2.37m.8 9.65q0-1.74-1.3-2.68c-.87-.62-1.9-.93-3.54-.93l-.75.02-.7.03v7.17h2.32q1.76 0 2.87-.94a3.3 3.3 0 0 0 1.1-2.66"/></svg>', action: () => insertText(textarea, '<strong>', '</strong>', getTranslation('bold_placeholder'))},
{ title: getTranslation('italic'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 8 8"><path fill="currentColor" d="M2 0v1h1.63l-.06.13-2 5-.34.88H.01v1h5v-1H3.38l.06-.13 2-5L5.78 1H7V0z"/></svg>', action: () => insertText(textarea, '<em>', '</em>', getTranslation('italic_placeholder'))},
{ title: getTranslation('underline'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 384 512"><path fill="currentColor" d="M0 32A32 32 0 0 1 32 0h64a32 32 0 1 1 0 64v160a96 96 0 0 0 192 0V64a32 32 0 1 1 0-64h64a32 32 0 1 1 0 64v160a160 160 0 1 1-320 0V64A32 32 0 0 1 0 32m0 448a32 32 0 0 1 32-32h320a32 32 0 1 1 0 64H32a32 32 0 0 1-32-32"/></svg>', action: () => insertText(textarea, '<u>', '</u>', getTranslation('underline_placeholder'))},
{ title: getTranslation('strikethrough'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M496 224H293.9l-87.2-26.8a43.6 43.6 0 0 1 12.9-85.2h66.7a50 50 0 0 1 44.7 27.6 16 16 0 0 0 21.5 7.1l42.9-21.4a16 16 0 0 0 7.2-21.5l-.6-1A128 128 0 0 0 287.5 32h-68a123.7 123.7 0 0 0-123 135.6c2 21 10.1 39.9 21.8 56.4H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h480a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16m-180.2 96a43 43 0 0 1 20.2 36.4 43.6 43.6 0 0 1-43.6 43.6h-66.7a50 50 0 0 1-44.7-27.6 16 16 0 0 0-21.5-7.1l-42.9 21.4a16 16 0 0 0-7.2 21.5l.6 1A128 128 0 0 0 224.5 480h68a123.7 123.7 0 0 0 123-135.6 114 114 0 0 0-5.3-24.4z"/></svg>', action: () => insertText(textarea, '<s>', '</s>', getTranslation('strikethrough_placeholder'))},
{ type: 'divider' },
{ title: getTranslation('unordered_list'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1792 1408"><path fill="currentColor" d="M384 1216q0 80-56 136t-136 56-136-56-56-136 56-136 136-56 136 56 56 136m0-512q0 80-56 136t-136 56-136-56T0 704t56-136 136-56 136 56 56 136m1408 416v192q0 13-9.5 22.5t-22.5 9.5H544q-13 0-22.5-9.5T512 1312v-192q0-13 9.5-22.5t22.5-9.5h1216q13 0 22.5 9.5t9.5 22.5M384 192q0 80-56 136t-136 56-136-56T0 192 56 56 192 0t136 56 56 136m1408 416v192q0 13-9.5 22.5T1760 832H544q-13 0-22.5-9.5T512 800V608q0-13 9.5-22.5T544 576h1216q13 0 22.5 9.5t9.5 22.5m0-512v192q0 13-9.5 22.5T1760 320H544q-13 0-22.5-9.5T512 288V96q0-13 9.5-22.5T544 64h1216q13 0 22.5 9.5T1792 96"/></svg>', action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selection = textarea.value.substring(start, end); const items = selection ? selection.split('\n').map(line => ` <li>${line}</li>`).join('\n') : ` <li>${getTranslation('list_item_placeholder')}</li>`; const listHtml = `<ul>\n${items}\n</ul>`; textarea.setRangeText(listHtml, start, end, 'select'); textarea.focus();}},
{ title: getTranslation('ordered_list'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M24 56a24 24 0 0 1 24-24h32a24 24 0 0 1 24 24v120h16a24 24 0 1 1 0 48H40a24 24 0 1 1 0-48h16V80h-8a24 24 0 0 1-24-24m62.7 285.2a15.3 15.3 0 0 0-24 1.2l-11.2 15.5A24 24 0 0 1 12.4 330l11.1-15.6a63.4 63.4 0 1 1 98.1 79.8L86.8 432H120a24 24 0 1 1 0 48H32a24.1 24.1 0 0 1-17.7-40.3l72-78c5.3-5.8 5.4-14.6.3-20.5zM224 64h256a32 32 0 1 1 0 64H224a32 32 0 1 1 0-64m0 160h256a32 32 0 1 1 0 64H224a32 32 0 1 1 0-64m0 160h256a32 32 0 1 1 0 64H224a32 32 0 1 1 0-64"/></svg>', action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selection = textarea.value.substring(start, end); const items = selection ? selection.split('\n').map(line => ` <li>${line}</li>`).join('\n') : ` <li>${getTranslation('list_item_placeholder')}</li>`; const listHtml = `<ol>\n${items}\n</ol>`; textarea.setRangeText(listHtml, start, end, 'select');textarea.focus();}},
{ title: getTranslation('details'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M4.25 3A2.25 2.25 0 0 0 2.3 6.37l7.75 13.5a2.25 2.25 0 0 0 3.9 0l7.74-13.5A2.25 2.25 0 0 0 19.74 3z"/></svg>', action: () => insertText(textarea, '<details><summary>' + getTranslation('details_summary_placeholder') + '</summary>' + getTranslation('details_content_placeholder') + '</details>')},
{ title: getTranslation('center'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="currentColor" d="M1 1h18v2H1zm0 8h18v2H1zm0 8h18v2H1zM4 5h12v2H4zm0 8h12v2H4z"/></svg>', action: () => insertText(textarea, '<center>', '</center>', getTranslation('center_placeholder')) },
{ type: 'divider' },
{ title: getTranslation('quote'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 304 384"><path fill="currentColor" d="m21 299 43-86H0V85h128v128l-43 86zm171 0 43-86h-64V85h128v128l-43 86z"/></svg>', action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selection = textarea.value.substring(start, end); const content = selection ? selection.replace(/\n/g, '<br>\n ') : getTranslation('quote'); const quoteHtml = `<blockquote>${content}</blockquote>`; textarea.setRangeText(quoteHtml, start, end, 'select'); textarea.focus(); } },
{ title: getTranslation('inline_code'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path fill="currentColor" d="m278.9 511.5-61-17.7a12 12 0 0 1-8.2-14.9L346.2 8.7A12 12 0 0 1 361.1.5l61 17.7a12 12 0 0 1 8.2 14.9L293.8 503.3a12 12 0 0 1-14.9 8.2m-114-112.2 43.5-46.4a12 12 0 0 0-.8-17.2L117 256l90.6-79.7a12 12 0 0 0 .8-17.2l-43.5-46.4a12 12 0 0 0-17-.5L3.8 247.2a12 12 0 0 0 0 17.5l144.1 135.1a12 12 0 0 0 17-.5m327.2.6 144.1-135.1a12 12 0 0 0 0-17.5L492.1 112.1a12 12 0 0 0-17 .5L431.6 159a12 12 0 0 0 .8 17.2L523 256l-90.6 79.7a12 12 0 0 0-.8 17.2l43.5 46.4a12 12 0 0 0 17 .6"/></svg>', action: () => insertText(textarea, '<code>', '</code>', getTranslation('inline_code_placeholder')) },
{ title: getTranslation('code_block'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 256 256"><path fill="currentColor" d="M52 52v152h28a12 12 0 0 1 0 24H40a12 12 0 0 1-12-12V40a12 12 0 0 1 12-12h40a12 12 0 0 1 0 24Zm164-24h-40a12 12 0 0 0 0 24h28v152h-28a12 12 0 0 0 0 24h40a12 12 0 0 0 12-12V40a12 12 0 0 0-12-12"/></svg>', action: () => insertText(textarea, '<pre><code>', '</code></pre>', getTranslation('code_block_placeholder')) },
{ title: getTranslation('horizontal_line'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="currentColor" fill-rule="evenodd" d="M1 10a1 1 0 0 1 1-1h16a1 1 0 1 1 0 2H2a1 1 0 0 1-1-1" clip-rule="evenodd"/></svg>', action: () => insertText(textarea, '\n<hr>\n') },
{ type: 'divider' },
{ title: getTranslation('link'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2.5" d="M8 12h8M9 8H6a4 4 0 1 0 0 8h3m6-8h3a4 4 0 0 1 0 8h-3"/></svg>', action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selectedText = textarea.value.substring(start, end); showCustomPrompt({ inputs: [ { id: 'url', label: getTranslation('prompt_insert_url'), placeholder: 'https://', type: 'url', required: true }, { id: 'text', label: getTranslation('prompt_link_text'), placeholder: getTranslation('link_text_placeholder'), value: selectedText, required: false } ], onConfirm: ({ url, text }) => { if (url) { const linkText = text || selectedText || url; const selectionMode = selectedText ? 'end' : 'select'; textarea.setRangeText(`<a href="${url}">${linkText}</a>`, start, end, selectionMode); textarea.focus();}}});}},
{ title: getTranslation('image'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 42 42"><path fill="currentColor" d="M.5 7.5v27c0 2.5.5 3 3 3h34c2.5 0 3-.5 3-3v-27c0-2.5-.5-3-3-3h-34c-2.5 0-3 .4-3 3m35.3 23H5.2c3.4-4.9 9.3-13 10.8-13s6.4 6.6 8.7 8.9c0 0 2.9-3.9 4.4-3.9s6.6 8 6.7 8m-9-17a3.7 3.7 0 1 1 7.4 0 3.7 3.7 0 0 1-7.4 0"/></svg>', action: () => showCustomPrompt({ inputs: [ { id: 'src', label: getTranslation('prompt_insert_image_url'), placeholder: 'https://', type: 'url' }, { id: 'title', label: getTranslation('prompt_image_title'), placeholder: getTranslation('image_title_placeholder'), required: false }, { id: 'width', label: getTranslation('prompt_image_width'), placeholder: '500px', type: 'number', required: false }, { id: 'height', label: getTranslation('prompt_image_height'), placeholder: '500px', type: 'number', required: false } ], onConfirm: ({ src, title, width, height }) => { if (src) { const titleAttr = title ? ` title="${title}"` : ''; const widthAttr = width ? ` width="${width}"` : ''; const heightAttr = height ? ` height="${height}"` : ''; insertText(textarea, `<img src="${src}"${titleAttr}${widthAttr}${heightAttr}>`);}}})},
{ title: getTranslation('table'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="currentColor" d="M2 2a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2zm0 4h7v4H2zm0 10v-4h7v4zm16 0h-7v-4h7zm0-6h-7V6h7z"/></svg>', action: () => showCustomPrompt({ inputs: [ { id: 'cols', label: getTranslation('prompt_columns'), type: 'number', value: '3' }, { id: 'rows', label: getTranslation('prompt_rows'), type: 'number', value: '2' } ], onConfirm: ({ cols, rows }) => { const numCols = parseInt(cols, 10) || 3; const numRows = parseInt(rows, 10) || 2; let table = '<table>\n <thead>\n <tr>\n'; table += ' ' + Array(numCols).fill(`<th>${getTranslation('table_header_placeholder')}</th>`).join('\n ') + '\n </tr>\n </thead>\n <tbody>\n'; for (let i = 0; i < numRows; i++) { table += ' <tr>\n'; table += ' ' + Array(numCols).fill(`<td>${getTranslation('table_cell_placeholder')}</td>`).join('\n ') + '\n </tr>\n'; } table += ' </tbody>\n</table>'; insertText(textarea, `\n${table}\n`);}})},
{ title: getTranslation('video'), icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M0 3.75C0 2.78.78 2 1.75 2h12.5c.97 0 1.75.78 1.75 1.75v8.5A1.75 1.75 0 0 1 14.25 14H1.75A1.75 1.75 0 0 1 0 12.25Zm1.75-.25a.25.25 0 0 0-.25.25v8.5c0 .14.11.25.25.25h12.5a.25.25 0 0 0 .25-.25v-8.5a.25.25 0 0 0-.25-.25Z"/><path fill="currentColor" d="M6 10.56V5.44a.25.25 0 0 1 .38-.21l4.26 2.56a.25.25 0 0 1 0 .42l-4.26 2.56a.25.25 0 0 1-.38-.21"/></svg>', action: () => showCustomPrompt({ inputs: [ { id: 'type', label: getTranslation('prompt_video_type'), type: 'select', options: [ { value: 'embed', text: getTranslation('video_type_embed') }, { value: 'html5', text: getTranslation('video_type_html5') } ]}, { id: 'url', label: getTranslation('prompt_insert_video_url'), placeholder: 'https://', type: 'url' }, { id: 'poster', label: getTranslation('prompt_video_poster_url'), placeholder: 'https://image.jpg', type: 'url', required: false }, { id: 'width', label: getTranslation('prompt_video_width'), placeholder: '560', type: 'number', required: false }, { id: 'height', label: getTranslation('prompt_video_height'), placeholder: '315', type: 'number', required: false } ], onConfirm: ({ type, url, poster, width, height }) => { if (!url) return; const widthAttr = width ? ` width="${width}"` : ''; const heightAttr = height ? ` height="${height}"` : ''; if (type === 'html5') { const posterAttr = poster ? ` poster="${poster}"` : ''; const videoTag = `\n<video src="${url}"${posterAttr}${widthAttr}${heightAttr} controls></video>\n`; insertText(textarea, videoTag); } else { let src = ''; try { if (url.includes('youtube.com/watch?v=')) { src = `https://www.youtube.com/embed/${new URL(url).searchParams.get('v')}`; } else if (url.includes('youtu.be/')) { src = `https://www.youtube.com/embed/${new URL(url).pathname.substring(1)}`; } else if (url.includes('bilibili.com/video/')) { src = `https://player.bilibili.com/player.html?bvid=${new URL(url).pathname.split('/')[2]}`; } } catch { src = ''; } if (src) { insertText(textarea, `\n<iframe src="${src}"${widthAttr}${heightAttr} allowfullscreen></iframe>\n`); } else { showCustomAlert(getTranslation('alert_invalid_video_url'));}}}})},
{ type: 'divider' },
{ title: getTranslation('subscript'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M32 64C14.3 64 0 78.3 0 96s14.3 32 32 32h15.3l89.6 128l-89.6 128H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h32c10.4 0 20.2-5.1 26.2-13.6L176 311.8l85.8 122.6c6 8.6 15.8 13.6 26.2 13.6h32c17.7 0 32-14.3 32-32s-14.3-32-32-32h-15.3l-89.6-128l89.6-128H320c17.7 0 32-14.3 32-32s-14.3-32-32-32h-32c-10.4 0-20.2 5.1-26.2 13.6L176 200.2L90.2 77.6C84.2 69.1 74.4 64 64 64H32zm448 256c0-11.1-5.7-21.4-15.2-27.2s-21.2-6.4-31.1-1.4l-32 16c-15.8 7.9-22.2 27.1-14.3 42.9C393 361.5 404.3 368 416 368v80c-17.7 0-32 14.3-32 32s14.3 32 32 32h64c17.7 0 32-14.3 32-32s-14.3-32-32-32V320z"/></svg>', action: () => insertText(textarea, '<sub>', '</sub>', getTranslation('subscript_placeholder'))},
{ title: getTranslation('superscript'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path fill="currentColor" d="M480 32c0-11.1-5.7-21.4-15.2-27.2s-21.2-6.4-31.1-1.4l-32 16c-15.8 7.9-22.2 27.1-14.3 42.9C393 73.5 404.3 80 416 80v80c-17.7 0-32 14.3-32 32s14.3 32 32 32h64c17.7 0 32-14.3 32-32s-14.3-32-32-32V32zM32 64C14.3 64 0 78.3 0 96s14.3 32 32 32h15.3l89.6 128l-89.6 128H32c-17.7 0-32 14.3-32 32s14.3 32 32 32h32c10.4 0 20.2-5.1 26.2-13.6L176 311.8l85.8 122.6c6 8.6 15.8 13.6 26.2 13.6h32c17.7 0 32-14.3 32-32s-14.3-32-32-32h-15.3l-89.6-128l89.6-128H320c17.7 0 32-14.3 32-32s-14.3-32-32-32h-32c-10.4 0-20.2 5.1-26.2 13.6L176 200.2L90.2 77.6C84.2 69.1 74.4 64 64 64H32z"/></svg>', action: () => insertText(textarea, '<sup>', '</sup>', getTranslation('superscript_placeholder'))},
{ title: getTranslation('highlight'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" d="M3 1a1 1 0 0 0-1 1v2.5A1.5 1.5 0 0 0 3.5 6h-.05.1-.05 9-.05.1-.05A1.5 1.5 0 0 0 14 4.5V2a1 1 0 0 0-1-1zm0 6a2 2 0 0 0 2 2h6a2 2 0 0 0 2-2zm2 7.5V10h6v.74a1.5 1.5 0 0 1-.69 1.26l-4.54 2.92A.5.5 0 0 1 5 14.5"/></svg>', action: () => insertText(textarea, '<mark>', '</mark>', getTranslation('highlight_placeholder'))},
{ title: getTranslation('keyboard'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill="currentColor" fill-rule="evenodd" d="M3 4h10a1.5 1.5 0 0 1 1.5 1.5v5A1.5 1.5 0 0 1 13 12H3a1.5 1.5 0 0 1-1.5-1.5v-5A1.5 1.5 0 0 1 3 4M0 5.5a3 3 0 0 1 3-3h10a3 3 0 0 1 3 3v5a3 3 0 0 1-3 3H3a3 3 0 0 1-3-3zm6.25 3.25a.75.75 0 0 0 0 1.5h3.5a.75.75 0 0 0 0-1.5zM4.5 6.5a1 1 0 1 1-2 0a1 1 0 0 1 2 0m2 1a1 1 0 1 0 0-2a1 1 0 0 0 0 2m4-1a1 1 0 1 1-2 0a1 1 0 0 1 2 0m2 1a1 1 0 1 0 0-2a1 1 0 0 0 0 2m-8 2a1 1 0 1 1-2 0a1 1 0 0 1 2 0m8 1a1 1 0 1 0 0-2a1 1 0 0 0 0 2" clip-rule="evenodd"/></svg>', action: () => insertText(textarea, '<kbd>', '</kbd>', getTranslation('keyboard_placeholder'))},
{ title: getTranslation('abbreviation'), label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 512"><path fill="currentColor" d="M20 424.229h20V279.771H20c-11.046 0-20-8.954-20-20V212c0-11.046 8.954-20 20-20h112c11.046 0 20 8.954 20 20v212.229h20c11.046 0 20 8.954 20 20V492c0 11.046-8.954 20-20 20H20c-11.046 0-20-8.954-20-20v-47.771c0-11.046 8.954-20 20-20zM96 0C56.235 0 24 32.235 24 72s32.235 72 72 72s72-32.235 72-72S135.764 0 96 0z"/></svg>', action: () => { const start = textarea.selectionStart; const end = textarea.selectionEnd; const selectedText = textarea.value.substring(start, end); showCustomPrompt({ inputs: [ { id: 'title', label: getTranslation('prompt_abbreviation_meaning'), required: true }, { id: 'text', label: getTranslation('prompt_abbreviation_text'), placeholder: getTranslation('abbreviation_placeholder'), value: selectedText, required: true } ], onConfirm: ({ title, text }) => { if (title && text) { const selectionMode = selectedText ? 'end' : 'select'; textarea.setRangeText(`<abbr title="${title}">${text}</abbr>`, start, end, selectionMode); textarea.focus();}}});}},
{ title: getTranslation('ai_translate'),
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="currentColor" d="m7.4 9 2.3 2.2-.9 2L6 10.4l-3.3 3.3-1.4-1.4L4.6 9l-.9-.9A6 6 0 0 1 2.4 6h2.2l.5.7.9.9.9-.9C7.5 6.1 8 4.8 8 4H0V2h5V0h2v2h5v2h-2c0 1.4-.7 3.2-1.7 4.1zm3.9 8L10 20H8l5-12h2l5 12h-2l-1.2-3zm.8-2h3.8L14 10.4z"/></svg>',
action: async () => {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const hasSelection = start !== end;
const selectedText = textarea.value.substring(start, end);
const textToTranslate = hasSelection ? selectedText : textarea.value;
if (!textToTranslate.trim()) {
showCustomAlert(getTranslation('alert_text_empty'));
return;
}
const savedKey = await GM_getValue('GOOGLE_AI_KEY', '');
const lastModel = await GM_getValue('GOOGLE_AI_LAST_MODEL', 'gemini-2.5-flash');
const lastLang = await GM_getValue('GOOGLE_AI_LAST_LANG', 'English');
const promptLabel = hasSelection
? `${getTranslation('prompt_translate_to')} (${getTranslation('selection') || 'Selection'})`
: getTranslation('prompt_translate_to');
showCustomPrompt({
inputs: [
{
id: 'lang',
label: promptLabel,
type: 'select',
value: lastLang,
options: getTargetLanguages()
},
{
id: 'model',
label: getTranslation('prompt_ai_model'),
type: 'select',
value: lastModel,
options: getGeminiModels()
},
{
id: 'apikey',
label: getTranslation('prompt_api_key'),
type: 'text',
value: savedKey,
placeholder: getTranslation('placeholder_api_key'),
required: true,
helpAction: showApiKeyHelp
}
],
onConfirm: async ({ lang, model, apikey }) => {
const cleanKey = apikey ? apikey.trim() : '';
if (cleanKey && cleanKey !== savedKey) {
await GM_setValue('GOOGLE_AI_KEY', cleanKey);
}
await GM_setValue('GOOGLE_AI_LAST_MODEL', model);
await GM_setValue('GOOGLE_AI_LAST_LANG', lang);
const originalCursor = document.body.style.cursor;
document.body.style.cursor = 'wait';
textarea.disabled = true;
try {
const result = await translateWithGemini(textToTranslate, lang, model, cleanKey);
if (hasSelection) {
textarea.setRangeText(result, start, end, 'select');
} else {
textarea.value = result;
}
textarea.dispatchEvent(new Event('input', { bubbles: true }));
} catch (error) {
showCustomAlert(getTranslation('alert_translation_error') + error);
} finally {
document.body.style.cursor = originalCursor;
textarea.disabled = false;
textarea.focus();
}
}
});
}
},
{ type: 'divider' },
{ type: 'color-picker' }
];
for (const tool of tools) {
if (tool.type === 'divider') {
const div = document.createElement('div');
div.className = 'txt-editor-toolbar-divider';
toolbar.appendChild(div);
} else if (tool.type === 'select') {
const container = document.createElement('span');
container.className = 'txt-editor-toolbar-button';
container.dataset.tooltip = tool.title;
container.style.position = 'relative';
container.style.display = 'flex';
container.style.alignItems = 'center';
container.style.justifyContent = 'center';
container.innerHTML = '<svg viewBox="0 0 16 16"><path d="M3.75 2a.75.75 0 0 1 .75.75V7h7V2.75a.75.75 0 0 1 1.5 0v10.5a.75.75 0 0 1-1.5 0V8.5h-7v4.75a.75.75 0 0 1-1.5 0V2.75A.75.75 0 0 1 3.75 2Z"></path></svg>';
const select = document.createElement('select');
select.className = 'txt-editor-toolbar-select';
select.style.cssText = ` -webkit-appearance: none; appearance: none; background: transparent; border: none; color: transparent; position: absolute; top: 0; left: 0; width: 100%; height: 100%; cursor: pointer; `;
const placeholderOpt = document.createElement('option');
placeholderOpt.value = '';
placeholderOpt.textContent = '';
placeholderOpt.disabled = true;
placeholderOpt.selected = true;
placeholderOpt.style.display = 'none';
select.appendChild(placeholderOpt);
Object.keys(tool.options).forEach(key => {
const opt = document.createElement('option');
opt.value = tool.options[key];
opt.textContent = key;
select.appendChild(opt);
});
select.addEventListener('change', () => {
if (select.value) tool.action(select.value);
select.selectedIndex = 0;
});
container.appendChild(select);
toolbar.appendChild(container);
} else if (tool.type === 'color-picker') {
const colorContainer = document.createElement('div');
colorContainer.className = 'txt-color-picker-container';
const input = document.createElement('input');
input.type = 'color';
input.className = 'txt-color-picker-input';
const lastColor = await GM_getValue(LAST_COLOR_KEY, '#58a6ff');
input.value = lastColor;
input.addEventListener('input', async (e) => {
await GM_setValue(LAST_COLOR_KEY, e.target.value);
});
const colorBtn = createToolbarButton({
title: getTranslation('text_color'),
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><g fill="none" stroke="currentColor" stroke-linejoin="round" stroke-width="4"><rect width="36" height="36" x="6" y="6" rx="3"/><path stroke-linecap="round" d="M16 19v-3h16v3M22 34h4m-2-16v16"/></g></svg>',
action: () => insertText(textarea, `<span style="color: ${input.value};">`, '</span>', getTranslation('colored_text_placeholder'))
});
const bgBtn = createToolbarButton({
title: getTranslation('background_color'),
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"><mask id="a"><g fill="none" stroke-linejoin="round" stroke-width="4"><rect width="36" height="36" x="6" y="6" fill="#fff" stroke="#fff" rx="3"/><path stroke="#000" stroke-linecap="round" d="M16 19v-3h16v3M22 34h4m-2-16v16"/></g></mask><path fill="currentColor" d="M0 0h48v48H0z" mask="url(#a)"/></svg>',
action: async () => {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
const lastTag = await GM_getValue(LAST_TAG_TYPE_KEY, 'span');
showCustomPrompt({
inputs: [
{ id: 'text', label: getTranslation('prompt_border_text'), type: 'text', value: selectedText || getTranslation('colored_background_placeholder') },
{ id: 'color', label: getTranslation('background_color'), type: 'text', value: input.value },
{ id: 'tag', label: getTranslation('prompt_border_tag_type'), type: 'select', value: lastTag, options: [ { value: 'span', text: '<span>' }, { value: 'div', text: '<div>' } ] }
],
onConfirm: async ({ text, color, tag }) => {
await GM_setValue(LAST_TAG_TYPE_KEY, tag);
let newElement;
if (tag === 'div') {
newElement = `\n<div style="background-color: ${color};">\n ${text}\n</div>\n`;
} else {
newElement = `<span style="background-color: ${color};">${text}</span>`;
}
textarea.setRangeText(newElement, start, end, selectedText ? 'end' : 'select');
textarea.focus();
}
});
}
});
const hrStyleBtn = createToolbarButton({
title: getTranslation('horizontal_line_style'),
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 20 20"><path fill="currentColor" d="M2 4.75A.75.75 0 0 1 2.75 4h2.5a.75.75 0 0 1 0 1.5h-2.5A.75.75 0 0 1 2 4.75m6 0A.75.75 0 0 1 8.75 4h2.5a.75.75 0 0 1 0 1.5h-2.5A.75.75 0 0 1 8 4.75m6 0a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75m-12 5A.75.75 0 0 1 2.75 9h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 9.75M3.25 14a1.25 1.25 0 1 0 0 2.5h13.5a1.25 1.25 0 1 0 0-2.5z"/></svg>',
action: () => {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end).trim();
let currentSize = '1';
let currentColor = input.value;
const hrRegex = /<hr\s+style="border:\s*(\d+)px\s+solid\s+([^;"]+)/i;
const match = selectedText.match(hrRegex);
if (match && selectedText.startsWith('<hr')) {
currentSize = match[1];
currentColor = match[2];
}
showCustomPrompt({
inputs: [
{ id: 'size', label: getTranslation('prompt_hr_size'), type: 'number', value: currentSize },
{ id: 'color', label: getTranslation('prompt_hr_color'), type: 'text', value: currentColor }
],
onConfirm: ({ size, color }) => {
const newHr = `<hr style="border: ${size}px solid ${color};">`;
textarea.setRangeText(newHr, start, end, 'select');
textarea.focus();
}
});
}
});
const borderStyleBtn = createToolbarButton({
title: getTranslation('border_style'),
label: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path fill="currentColor" d="M3 21V3h18v18zM5 5v14h14V5z"/></svg>',
action: async () => {
const start = textarea.selectionStart;
const end = textarea.selectionEnd;
const selectedText = textarea.value.substring(start, end);
const lastTag = await GM_getValue(LAST_TAG_TYPE_KEY, 'span');
showCustomPrompt({
inputs: [
{ id: 'text', label: getTranslation('prompt_border_text'), type: 'text', value: selectedText || getTranslation('border_text_placeholder') },
{ id: 'size', label: getTranslation('prompt_border_size'), type: 'number', value: '1' },
{ id: 'color', label: getTranslation('prompt_border_color'), type: 'text', value: input.value },
{ id: 'tag', label: getTranslation('prompt_border_tag_type'), type: 'select', value: lastTag, options: [ { value: 'span', text: '<span>' }, { value: 'div', text: '<div>' } ] }
],
onConfirm: async ({ text, size, color, tag }) => {
await GM_setValue(LAST_TAG_TYPE_KEY, tag);
let newElement;
if (tag === 'div') {
newElement = `\n<div style="border: ${size}px solid ${color};">\n ${text}\n</div>`;
} else {
newElement = `<span style="border: ${size}px solid ${color};">${text}</span>`;
}
textarea.setRangeText(newElement, start, end, selectedText ? 'end' : 'select');
textarea.focus();
}
});
}
});
colorContainer.append(input, colorBtn, bgBtn, hrStyleBtn, borderStyleBtn);
toolbar.appendChild(colorContainer);
} else {
toolbar.appendChild(createToolbarButton(tool));
}
}
const infoButton = createToolbarButton({
title: getTranslation('info_tooltip'),
icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 15c-.55 0-1-.45-1-1v-4c0-.55.45-1 1-1s1 .45 1 1v4c0 .55-.45 1-1 1zm1-8h-2V7h2v2z"/></svg>',
action: showInfoModal
});
infoButton.style.marginLeft = 'auto';
toolbar.appendChild(infoButton);
textarea.parentNode.insertBefore(container, textarea);
container.append(toolbar, textarea);
}
function applyToAllTextareas() {
const textareas = document.querySelectorAll('textarea:not(#script_version_code):not([data-editor-applied])');
textareas.forEach(createTextStyleEditor);
}
function enableSourceEditorCheckbox() {
const enableCheckbox = () => {
const checkbox = document.getElementById('enable-source-editor-code');
if (checkbox && !checkbox.checked) {
checkbox.checked = true;
const event = new Event('change', {
bubbles: true
});
checkbox.dispatchEvent(event);
}
};
enableCheckbox();
const observer = new MutationObserver((mutationsList, observer) => {
for (const mutation of mutationsList) {
if (mutation.type === 'childList') {
const checkbox = document.getElementById('enable-source-editor-code');
if (checkbox) {
enableCheckbox();
observer.disconnect();
break;
}
}
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
function isMarkdownPage() {
const path = window.location.pathname;
const markdownSegments = [ '/new', '/edit', '/feedback', '/discussions', '/conversations' ];
if (path.includes('/sets/')) {
return false;
}
return markdownSegments.some(segment => path.includes(segment));
}
// #endregion
// ================
// #region DOWNLOAD
// ================
function isCodePage() {
return /^\/([a-z]{2}(-[A-Z]{2})?\/)?scripts\/\d+-.+\/code/.test(window.location.pathname);
}
function initializeDownloadButton() {
const waitFor = (sel) =>
new Promise((resolve) => {
const el = document.querySelector(sel);
if (el) return resolve(el);
const obs = new MutationObserver(() => {
const el = document.querySelector(sel);
if (el) {
obs.disconnect();
resolve(el);
}
});
obs.observe(document, { childList: true, subtree: true });
});
waitFor('label[for="wrap-lines"]').then((label) => {
const wrapLinesCheckbox = document.getElementById('wrap-lines');
if (wrapLinesCheckbox) {
wrapLinesCheckbox.checked = false;
}
const toolbar = label.parentElement;
const btn = document.createElement('button');
btn.className = 'btn';
btn.textContent = getTranslation('download');
btn.style.marginLeft = '12px';
btn.style.backgroundColor = '#005200';
btn.style.color = 'white';
btn.style.border = 'none';
btn.style.padding = '6px 16px';
btn.style.borderRadius = '4px';
btn.style.cursor = 'pointer';
btn.addEventListener('mouseenter', () => btn.style.backgroundColor = '#1e971e');
btn.addEventListener('mouseleave', () => btn.style.backgroundColor = '#005200');
btn.addEventListener('click', () => {
const normalizedPath = normalizeScriptPath(window.location.pathname);
const scriptId = extractScriptIdFromNormalizedPath(normalizedPath);
if (!scriptId) {
alert(getTranslation('scriptIdNotFound'));
return;
}
const scriptUrl = `https://update.greasyfork.org/scripts/${scriptId}.js`;
btn.disabled = true;
btn.textContent = getTranslation('downloading');
GM_xmlhttpRequest({
method: 'GET',
url: scriptUrl,
onload: function (res) {
const code = res.responseText;
if (!code) {
alert(getTranslation('notFound'));
return;
}
const nameMatch = code.match(/\/\/\s*@name\s+(.+)/i);
const fileName = nameMatch ? `${nameMatch[1].trim()}.user.js` : 'script.user.js';
const blob = new Blob([code], { type: 'application/javascript;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = fileName;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
},
onerror: function (res) {
alert(getTranslation('downloadError'));
},
ontimeout: function () {
alert(getTranslation('downloadTimeout'));
},
onloadend: function () {
btn.disabled = false;
btn.textContent = getTranslation('download');
}
});
});
toolbar.appendChild(btn);
const spacer = document.createElement('div');
spacer.style.height = '12px';
toolbar.appendChild(spacer);
});
}
// #endregion
// ================
// #region INICIALIZAR
// ================
async function start() {
iconCache = await GM_getValue(CACHE_KEY, {});
await determineLanguage();
languageModal = createLanguageModal();
document.body.appendChild(languageModal);
registerLanguageMenu();
registerForceUpdateMenu();
setupThemeChangeListener();
if (isMarkdownPage()) {
applyToAllTextareas();
enableSourceEditorCheckbox();
}
if (isCodePage()){
initializeDownloadButton();
}
processIconElements();
highlightScriptDescription();
if (isScriptPage()) {
addAdditionalInfoSeparator();
}
makeDiscussionClickable();
applySyntaxHighlighting();
const observer = new MutationObserver(() => {
processIconElements();
highlightScriptDescription();
if (isScriptPage()) {
addAdditionalInfoSeparator();
}
if (isMarkdownPage()) {
applyToAllTextareas();
}
makeDiscussionClickable();
applySyntaxHighlighting();
});
observer.observe(document.body, {
childList: true,
subtree: true
});
}
start();
// #endregion
})();