MyAnimeList Synopsis Auto-Translator

Traduce la descripción del anime y manga en MyAnimeList de manera automática.

// ==UserScript==
// @name         MyAnimeList Synopsis Auto-Translator
// @namespace    https://greasyfork.org/scripts/463192
// @version      2.9.1
// @description:en Automatically translates MyAnimeList anime and manga descriptions.
// @description:es Traduce automáticamente las descripciones de anime y manga en MyAnimeList.
// @author       Shu2Ouma
// @icon         https://cdn.myanimelist.net/images/favicon.ico
// @match        https://myanimelist.net/anime/*
// @match        https://myanimelist.net/manga/*
// @grant        GM_xmlhttpRequest
// @license      MIT
// @description Traduce la descripción del anime y manga en MyAnimeList de manera automática.
// ==/UserScript==

// Define los idiomas disponibles
const idiomas = [
  { codigo: "af", bandera: "🇿🇦", nombre: "Afrikaans" }, // Afrikaans
  { codigo: "am", bandera: "🏳️", nombre: "አማርኛ" }, // Amharic (bandera de reserva)
  { codigo: "ar", bandera: "🇸🇦", nombre: "العربية" }, // Arabic
  { codigo: "az", bandera: "🇦🇿", nombre: "Azərbaycanca" }, // Azerbaijani
  { codigo: "be", bandera: "🇧🇾", nombre: "Беларуская" }, // Belarusian
  { codigo: "bg", bandera: "🇧🇬", nombre: "Български" }, // Bulgarian
  { codigo: "bn", bandera: "🇧🇩", nombre: "বাংলা" }, // Bengali
  { codigo: "bs", bandera: "🇧🇦", nombre: "Bosanski" }, // Bosnian
  { codigo: "ca", bandera: "🇪🇸", nombre: "Català" }, // Catalan
  { codigo: "ceb", bandera: "🇵🇭", nombre: "Cebuano" }, // Cebuano
  { codigo: "cs", bandera: "🇨🇿", nombre: "Čeština" }, // Czech
  { codigo: "cy", bandera: "🏳️", nombre: "Cymraeg" }, // Welsh
  { codigo: "da", bandera: "🇩🇰", nombre: "Dansk" }, // Danish
  { codigo: "de", bandera: "🇩🇪", nombre: "Deutsch" }, // German
  { codigo: "el", bandera: "🇬🇷", nombre: "Ελληνικά" }, // Greek
  { codigo: "en", bandera: "🇬🇧", nombre: "English" }, // English
  { codigo: "eo", bandera: "🇪🇸", nombre: "Esperanto" }, // Esperanto
  { codigo: "es", bandera: "🇪🇸", nombre: "Español (España)" }, // Spanish (Spain)
  { codigo: "es-MX", bandera: "🇲🇽", nombre: "Español (México)" }, // Spanish (Mexico)
  { codigo: "et", bandera: "🇪🇪", nombre: "Eesti" }, // Estonian
  { codigo: "eu", bandera: "🇪🇸", nombre: "Euskara" }, // Basque
  { codigo: "fa", bandera: "🇮🇷", nombre: "فارسی" }, // Persian
  { codigo: "fi", bandera: "🇫🇮", nombre: "Suomi" }, // Finnish
  { codigo: "fil", bandera: "🇵🇭", nombre: "Filipino" }, // Filipino
  { codigo: "fr", bandera: "🇫🇷", nombre: "Français" }, // French
  { codigo: "fy", bandera: "🇳🇱", nombre: "Frysk" }, // Frisian
  { codigo: "ga", bandera: "🇮🇪", nombre: "Gaeilge" }, // Irish
  { codigo: "gd", bandera: "🇬🇧", nombre: "Gàidhlig" }, // Scottish Gaelic
  { codigo: "gl", bandera: "🇪🇸", nombre: "Galego" }, // Galician
  { codigo: "gu", bandera: "🇮🇳", nombre: "ગુજરાતી" }, // Gujarati
  { codigo: "ha", bandera: "🇳🇬", nombre: "Hausa" }, // Hausa
  { codigo: "haw", bandera: "🇺🇸", nombre: "ʻŌlelo Hawaiʻi" }, // Hawaiian
  { codigo: "hi", bandera: "🇮🇳", nombre: "हिन्दी" }, // Hindi
  { codigo: "hmn", bandera: "🇲🇲", nombre: "Hmong" }, // Hmong
  { codigo: "hr", bandera: "🇭🇷", nombre: "Hrvatski" }, // Croatian
  { codigo: "ht", bandera: "🇭🇹", nombre: "Kreyòl Ayisyen" }, // Haitian Creole
  { codigo: "hu", bandera: "🇭🇺", nombre: "Magyar" }, // Hungarian
  { codigo: "hy", bandera: "🇦🇲", nombre: "Հայերեն" }, // Armenian
  { codigo: "id", bandera: "🇮🇩", nombre: "Bahasa Indonesia" }, // Indonesian
  { codigo: "ig", bandera: "🇳🇬", nombre: "Igbo" }, // Igbo
  { codigo: "is", bandera: "🇮🇸", nombre: "Íslenska" }, // Icelandic
  { codigo: "it", bandera: "🇮🇹", nombre: "Italiano" }, // Italian
  { codigo: "ja", bandera: "🇯🇵", nombre: "日本語" }, // Japanese
  { codigo: "jv", bandera: "🇮🇩", nombre: "Basa Jawa" }, // Javanese
  { codigo: "ka", bandera: "🇬🇪", nombre: "ქართული" }, // Georgian
  { codigo: "kk", bandera: "🇰🇿", nombre: "Қазақ Тілі" }, // Kazakh
  { codigo: "km", bandera: "🇰🇭", nombre: "ភាសាខ្មែរ" }, // Khmer
  { codigo: "kn", bandera: "🇮🇳", nombre: "ಕನ್ನಡ" }, // Kannada
  { codigo: "ko", bandera: "🇰🇷", nombre: "한국어" }, // Korean
  { codigo: "ku", bandera: "🇹🇯", nombre: "Kurdî" }, // Kurdish
  { codigo: "ky", bandera: "🇰🇬", nombre: "Кыргызча" }, // Kyrgyz
  { codigo: "la", bandera: "🏳️", nombre: "Latina" }, // Latin
  { codigo: "lb", bandera: "🇱🇺", nombre: "Lëtzebuergesch" }, // Luxembourgish
  { codigo: "lo", bandera: "🇱🇦", nombre: "ພາສາລາວ" }, // Lao
  { codigo: "lt", bandera: "🇱🇹", nombre: "Lietuvių" }, // Lithuanian
  { codigo: "lv", bandera: "🇱🇻", nombre: "Latviešu" }, // Latvian
  { codigo: "mg", bandera: "🇲🇬", nombre: "Malagasy" }, // Malagasy
  { codigo: "mi", bandera: "🇳🇿", nombre: "Māori" }, // Māori
  { codigo: "mk", bandera: "🇲🇰", nombre: "Македонски" }, // Macedonian
  { codigo: "ml", bandera: "🇮🇳", nombre: "മലയാളം" }, // Malayalam
  { codigo: "mn", bandera: "🇲🇳", nombre: "Монгол" }, // Mongolian
  { codigo: "mr", bandera: "🇮🇳", nombre: "मराठी" }, // Marathi
  { codigo: "ms", bandera: "🇲🇾", nombre: "Bahasa Melayu" }, // Malay
  { codigo: "mt", bandera: "🇲🇹", nombre: "Malti" }, // Maltese
  { codigo: "my", bandera: "🇲🇲", nombre: "မြန်မာ" }, // Burmese
  { codigo: "ne", bandera: "🇳🇵", nombre: "नेपाली" }, // Nepali
  { codigo: "nl", bandera: "🇳🇱", nombre: "Nederlands" }, // Dutch
  { codigo: "no", bandera: "🇳🇴", nombre: "Norsk" }, // Norwegian
  { codigo: "ny", bandera: "🇲🇺", nombre: "Chichewa" }, // Chichewa
  { codigo: "or", bandera: "🇮🇳", nombre: "ଓଡ଼ିଆ" }, // Oriya
  { codigo: "pa", bandera: "🇮🇳", nombre: "ਪੰਜਾਬੀ" }, // Punjabi
  { codigo: "pl", bandera: "🇵🇱", nombre: "Polski" }, // Polish
  { codigo: "ps", bandera: "🇵🇰", nombre: "پښتو" }, // Pashto
  { codigo: "pt", bandera: "🇵🇹", nombre: "Português" }, // Portuguese
  { codigo: "ro", bandera: "🇷🇴", nombre: "Română" }, // Romanian
  { codigo: "ru", bandera: "🇷🇺", nombre: "Русский" }, // Russian
  { codigo: "sd", bandera: "🇵🇰", nombre: "سنڌي" }, // Sindhi
  { codigo: "si", bandera: "🇱🇰", nombre: "සිංහල" }, // Sinhala
  { codigo: "sk", bandera: "🇸🇰", nombre: "Slovenčina" }, // Slovak
  { codigo: "sl", bandera: "🇸🇮", nombre: "Slovenščina" }, // Slovenian
  { codigo: "sm", bandera: "🇲🇸", nombre: "Gagana Samoa" }, // Samoan
  { codigo: "sn", bandera: "🇿🇼", nombre: "ChiShona" }, // Shona
  { codigo: "so", bandera: "🇲🇱", nombre: "Soomaaliga" }, // Somali
  { codigo: "sq", bandera: "🇦🇱", nombre: "Shqip" }, // Albanian
  { codigo: "sr", bandera: "🇷🇸", nombre: "Српски" }, // Serbian
  { codigo: "st", bandera: "🇱🇸", nombre: "Sesotho" }, // Sesotho
  { codigo: "su", bandera: "🇮🇩", nombre: "Basa Sunda" }, // Sundanese
  { codigo: "sv", bandera: "🇸🇪", nombre: "Svenska" }, // Swedish
  { codigo: "sw", bandera: "🇰🇪", nombre: "Kiswahili" }, // Swahili
  { codigo: "ta", bandera: "🇮🇳", nombre: "தமிழ்" }, // Tamil
  { codigo: "te", bandera: "🇮🇳", nombre: "తెలుగు" }, // Telugu
  { codigo: "tg", bandera: "🇹🇯", nombre: "Тоҷикӣ" }, // Tajik
  { codigo: "th", bandera: "🇹🇭", nombre: "ไทย" }, // Thai
  { codigo: "tl", bandera: "🇵🇭", nombre: "Tagalog" }, // Filipino (Tagalog)
  { codigo: "tr", bandera: "🇹🇷", nombre: "Türkçe" }, // Turkish
  { codigo: "uk", bandera: "🇺🇦", nombre: "Українська" }, // Ukrainian
  { codigo: "ur", bandera: "🇵🇰", nombre: "اردو" }, // Urdu
  { codigo: "uz", bandera: "🇺🇿", nombre: "Oʻzbekcha" }, // Uzbek
  { codigo: "vi", bandera: "🇻🇳", nombre: "Tiếng Việt" }, // Vietnamese
  { codigo: "xh", bandera: "🇿🇦", nombre: "isiXhosa" }, // Xhosa
  { codigo: "yi", bandera: "🇮🇱", nombre: "יידיש" }, // Yiddish
  { codigo: "yo", bandera: "🇳🇬", nombre: "Yorùbá" }, // Yoruba
  { codigo: "zh-CN", bandera: "🇨🇳", nombre: "中文 (简体)" }, // Chinese Simplified
  { codigo: "zh-TW", bandera: "🇹🇼", nombre: "中文 (繁體)" }, // Chinese Traditional
  { codigo: "zu", bandera: "🇿🇦", nombre: "isiZulu" }, // Zulu
];

// Función para crear la lista desplegable de idiomas
const crearListaDesplegable = () => {
  const selectIdioma = document.createElement('select');
  selectIdioma.id = 'selectIdioma';
  selectIdioma.style.cssText = 'background-color: #fff; border: #bebebe 1.1px solid; border-radius: 4px; color: #323232; display: inline-block; font-family: Avenir,lucida grande,tahoma,verdana,arial,sans-serif; font-size: 11px; padding: 3.5px 8px; text-align: left; text-decoration: none; vertical-align: middle; margin-left: 8px;';

  idiomas.sort((a, b) => a.nombre.localeCompare(b.nombre));

  idiomas.forEach(idioma => {
    const option = document.createElement('option');
    option.value = idioma.codigo;
    option.text = `${idioma.bandera} ${idioma.nombre}`;
    selectIdioma.appendChild(option);
  });

  return selectIdioma;
};

const inicializarIdioma = () => {
  const idiomaGuardado = localStorage.getItem('idiomaSeleccionado') || 'en';
  document.querySelector('#selectIdioma').value = idiomaGuardado;

  if (idiomaGuardado !== 'en') {
    traducirDescripciones(idiomaGuardado);
  }
};

const manejarCambioIdioma = () => {
  const idiomaSeleccionado = document.querySelector('#selectIdioma').value;
  localStorage.setItem('idiomaSeleccionado', idiomaSeleccionado);
  traducirDescripciones(idiomaSeleccionado);
};

// Espera a que se cargue completamente la página
window.addEventListener('load', () => {
  const selectIdioma = crearListaDesplegable();

  let divElement = document.querySelector('.header-right-link'); // Elemento adecuado para insertar la lista
  if (divElement) {
    divElement.appendChild(selectIdioma);
  }

  inicializarIdioma();
  selectIdioma.addEventListener('change', manejarCambioIdioma);
});

const clonarEstilos = (origen, destino, agregarMarginLeft = true) => {
  const estilos = window.getComputedStyle(origen);
  const propiedades = [
    'background-color', 'border', 'border-radius', 'color', 'display',
    'font-family', 'text-align', 'text-decoration', 'vertical-align',
    'cursor', 'font-size', 'font-weight', 'margin-right', 'padding',
    '-webkit-transition-duration', 'transition-duration', '-webkit-transition-property',
    'transition-property', '-webkit-transition-timing-function', 'transition-timing-function'
  ];
  destino.style.cssText = propiedades
    .filter(prop => estilos.getPropertyValue(prop)) // Filtra propiedades que tienen valor
    .map(prop => `${prop}: ${estilos.getPropertyValue(prop)};`) // Aplica el estilo
    .join(' ');

  // Aplicar margin-left si se requiere
  if (agregarMarginLeft) {
    destino.style.setProperty('margin-left', '8px', 'important');
  }
};

// Define el estilo de texto que se aplicará a las etiquetas <p> y <span>
const estiloTexto = "font-size: 100%;"; // Cambiar el valor para ajustar el tamaño del texto

// Traduce las descripciones al idioma seleccionado
const traducirDescripciones = idiomaSeleccionado => {
  const descripciones = document.querySelectorAll('p.preline, p[itemprop="description"], span[itemprop="description"], div.synopsis.js-synopsis p, div.pt4, div.relation');
  descripciones.forEach(descripcion => {
    if ((descripcion.tagName === "P" && descripcion.getAttribute("itemprop") === "description") ||
        (descripcion.tagName === "SPAN" && descripcion.getAttribute("itemprop") === "description") ||
        descripcion.classList.contains("relation")) {
      descripcion.style = estiloTexto;
    }

    let textoLinea = descripcion.innerHTML.trim();

    // Si es un elemento con la clase 'relation', excluye el contenido entre paréntesis de la traducción
    if (descripcion.classList.contains("relation")) {
      // Divide el texto en partes, excluyendo el contenido específico entre paréntesis
      const partes = textoLinea.split(/(\(Light Novel\)|\(TV\)|\(manga\))/i);

      // Traducir solo las partes que no son "Light Novel", "TV" o "manga" dentro de los paréntesis
      const partesTraducibles = partes.map(parte => {
        // Si la parte es uno de los textos omitidos, agrégale un espacio antes
        if (parte.toLowerCase() === '(light novel)' || parte.toLowerCase() === '(tv)' || parte.toLowerCase() === '(manga)') {
          return ` ${parte}`;
        } else {
          return translateText(parte, idiomaSeleccionado);
        }
      });

      // Combinar las partes traducidas con las partes excluidas sin cambios
      Promise.all(partesTraducibles).then(partesTraducidas => {
        descripcion.innerHTML = partesTraducidas.join('');
      });
    } else {
      // Traducir el texto completo si no tiene la clase 'relation'
      translateText(textoLinea, idiomaSeleccionado).then(textoTraducido => {
        descripcion.innerHTML = textoTraducido;
      });
    }
  });
};

const translateText = (text, targetLang) => {
  return new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
      method: "GET",
      url: `https://translate.googleapis.com/translate_a/single?client=gtx&sl=auto&tl=${targetLang}&dt=t&q=${encodeURI(text)}`,
      onload: function(response) {
        const resultado = JSON.parse(response.responseText);
        const textoTraducido = resultado[0].map(function(elemento){ return elemento[0]; }).join('');
        resolve(textoTraducido);
      },
      onerror: function(error) {
        reject(error);
      }
    });
  });
};

// Espera a que se cargue completamente la página antes de ejecutar el script
window.addEventListener('load', () => {
  const selectIdioma = crearListaDesplegable();
  const idiomaSeleccionado = localStorage.getItem('idiomaSeleccionado');
  if (idiomaSeleccionado) {
    selectIdioma.value = idiomaSeleccionado;
    traducirDescripciones(idiomaSeleccionado);
  }

  // Agrega el evento de cambio de idioma
  selectIdioma.addEventListener('change', manejarCambioIdioma);

  // Busca el elemento donde deseamos insertar la lista de idiomas
  let divElement = document.querySelector('div.user-status-block.js-user-status-block.fn-grey6.clearfix.al.mt8.po-r[data-type="manga"]');
  if (!divElement) {
    divElement = document.querySelector('div.user-status-block.js-user-status-block.fn-grey6.clearfix.al.mt8.po-r');
  }

  if (!divElement) {
    // Si no se encuentra el elemento de destino, lo manejamos de otra manera
    const targetElement = document.querySelector('.btn-seasonal');
    if (targetElement) {
      // Clona los estilos del <li class="btn-type js-btn-seasonal" data-key="4">Special</li>
      const liEstilos = document.querySelector('li.btn-type.js-btn-seasonal[data-key="4"]');
      if (liEstilos) {
        clonarEstilos(liEstilos, selectIdioma, false); // No agregar margin-left
      }

      // Insertamos la lista desplegable en el lugar correspondiente
      const lastListItem = targetElement.querySelector('li:last-child');
      if (lastListItem) {
        targetElement.insertBefore(selectIdioma, lastListItem.nextSibling);
      } else {
        targetElement.prepend(selectIdioma);
      }
    } else {
      // Si no se encuentra ningún elemento de destino, manejamos la situación de otra manera
      const container = document.createElement('div');
      container.style.cssText = 'position: fixed; right: 10px; top: 50%; transform: translate(0, -50%);';
      document.body.appendChild(container);
      divElement = container;

      // Clona los estilos del <div class="di-ib form-user-episode ml8">
      const divEstilos = document.querySelector('div.di-ib.form-user-episode.ml8');
      if (divEstilos) {
        clonarEstilos(divEstilos, selectIdioma);
      }
    }
  } else {
    // Clona los estilos del <div class="di-ib form-user-episode ml8">
    const divEstilos = document.querySelector('div.di-ib.form-user-episode.ml8');
    if (divEstilos) {
      clonarEstilos(divEstilos, selectIdioma);
    }

    // Insertamos la lista desplegable en el lugar correspondiente
    divElement.appendChild(selectIdioma);
  }

  // Añadimos margen inferior al div principal
  divElement.style.marginBottom = '10px';
});