├── .github └── ISSUE_TEMPLATE │ ├── feature-request.md │ └── bug-report.md ├── README.md └── MAL_English_Titles.user.js /.github/ISSUE_TEMPLATE/feature-request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature Request 3 | about: Suggest an idea for this project 4 | title: "[Feature Request]" 5 | labels: Feature Request 6 | assignees: Animorphs 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Extra** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug Report 3 | about: Report a bug 4 | title: "[Bug]" 5 | labels: Bug 6 | assignees: Animorphs 7 | 8 | --- 9 | 10 |

Fill out **all** of the following fields:

11 | 12 | **Describe the bug** 13 | A clear and concise description of what the bug is. 14 | 15 | **Expected behavior** 16 | A clear and concise description of what you expected to happen. 17 | 18 | **How To Reproduce** 19 | Steps to reproduce the behavior: 20 | 1. Go to '...' 21 | 2. Click on '....' 22 | 3. Scroll down to '....' 23 | 4. See error 24 | 25 | **Screenshots** 26 | Include screenshots to help explain your problem. [Please use imgur.](https://imgur.com/upload) 27 | 28 | **PC** 29 | - OS: [e.g. Windows] 30 | - Browser [e.g. Chrome, Safari] 31 | 32 | **Extra (optional)** 33 | Add any other context about the problem here. 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # MyAnimeList English Titles 2 | Add English titles for various Anime and Manga pages on MyAnimeList.net, whilst still displaying the Japanese titles. 3 | 4 | Note: If you are getting timed out of MAL after installation, please see the [FAQ](#faq). 5 | 6 | ## 📝 Table of Contents 7 | * [How to Install](#install) 8 | * [FAQ](#faq) 9 | * [Examples / Screenshots](#screenshots) 10 | 11 | ## 💻 How to Install 12 | 1. Download and install [Tampermonkey](https://www.tampermonkey.net/). Note: this script may not work properly with Greasemonkey or Violentmonkey. 13 | 2. [Click Here](https://greasyfork.org/en/scripts/420200-mal-english-titles) to open the script on Greasy Fork. 14 | 3. Click "Install this script" ([image](https://i.imgur.com/j2vhMKI.png)). 15 | 4. Click "Install" ([image](https://i.imgur.com/AcVa6C0.png)). 16 | 5. Open [MAL](https://myanimelist.net/), and navigate to any of the pages pictured below. 17 | 6. Done, see your translations! 18 | 19 | ## ❓ FAQ 20 | 1. MAL is timing out / [thinks I'm a bot](https://i.imgur.com/wShsC6I.png)? When you browse a lot of new pages in a short amount of time, you may get temporarily timed out of MAL. If this happens, click 'Submit', wait for ~20 seconds, and then you can go back to browsing. Timeouts are most likely to happen when you first install, are browsing lots of pages at once, or for the first time are visiting a personal list with a lot of content. If it happens everytime you load a page, please submit a [bug report](https://github.com/Animorphs/MAL-English-Titles/issues/new/choose). 21 | 2. Why am I seeing the English title twice and not the Japanese title at all? In the [MAL Default Settings](https://myanimelist.net/editprofile.php?go=listpreferences), set "Anime Titles" and "Manga Titles" to "Main Title". 22 | 3. Why are some translations missing? Most likely, MAL does not have an official translation for that anime or manga. Alternatively, if the script is retrieving lots of new translations, MAL may time you out, and the script will be unable to load the remaining translations on the page. To fix this, refresh and see point 1 above. 23 | 4. Why am I not getting translations on 'x' page? Currently the script supports various pages throughout MAL, but not all, as it is manually coded for each endpoint. If you are not getting translations on any pages at all, submit a bug report. If you are getting translations, and have a specific page that is not currently supported, submit a feature request. 24 | 5. Why aren't you using Kitsu API so we don't get timed out? Primarily because I couldn't be bothered figuring it out, and I wanted it done natively as the info is already on MAL. Also if Kitsu goes down, the script won't have a good time. 25 | 6. I've encountered a bug? Please submit a [bug report](https://github.com/Animorphs/MAL-English-Titles/issues/new/choose). 26 | 7. I've got a great idea? Please submit a [feature request](https://github.com/Animorphs/MAL-English-Titles/issues/new/choose). 27 | 28 | ## 📸 Examples / Screenshots 29 | ### Personal Anime / Manga List 30 | 31 | 32 | ### Top Anime / Manga 33 | 34 | 35 | ### Seasonal Anime 36 | 37 | 38 | ### Search 39 | 40 | 41 | Special thanks to nicegamer7 for their code contributions. 42 | -------------------------------------------------------------------------------- /MAL_English_Titles.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name MAL English Titles 3 | // @version 2.2.3 4 | // @description Add English Titles to various MyAnimeList pages, whilst still displaying Japanese Titles 5 | // @author Animorphs 6 | // @grant GM.setValue 7 | // @grant GM.getValue 8 | // @namespace https://github.com/Animorphs/MAL-English-Titles 9 | // @icon https://myanimelist.net/favicon.ico 10 | // @match https://myanimelist.net/* 11 | // @updateURL https://raw.githubusercontent.com/Animorphs/MAL-English-Titles/master/MAL_English_Titles.user.js 12 | // @downloadURL https://raw.githubusercontent.com/Animorphs/MAL-English-Titles/master/MAL_English_Titles.user.js 13 | // ==/UserScript== 14 | 15 | 16 | 17 | // Get Japanese titles from page, and send to be translated (addTranslation) 18 | async function translate() 19 | { 20 | const LOCATION_HREF = location.href; 21 | const URL_REGEX = /https:\/\/myanimelist\.net\/(anime|manga)\/([1-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)\/?.*/; 22 | const URL_PHP_REGEX = /https:\/\/myanimelist\.net\/(anime|manga)\.php\?id\=([1-9][0-9]?[0-9]?[0-9]?[0-9]?[0-9]?)\/?.*/; 23 | 24 | // Anime/Manga Page (store only, don't display) 25 | if (URL_REGEX.test(LOCATION_HREF) || URL_PHP_REGEX.test(LOCATION_HREF)) 26 | { 27 | let titleHtml = document.getElementsByClassName('title-english')[0]; 28 | let id = LOCATION_HREF.includes('.php') ? LOCATION_HREF.split('id=')[1] : LOCATION_HREF.split('/')[4]; 29 | 30 | let type = LOCATION_HREF.includes('/anime') ? "anime" : "manga"; 31 | if (titleHtml) 32 | { 33 | let title = titleHtml.innerText; 34 | console.log(`Updated ${type} ${id}: ${title}`); 35 | type == 'anime' ? await storeAnime(id, title) : await storeManga(id, title); 36 | } 37 | else if (storedAnime[id][0] === '' || !storedAnime.hasOwnProperty(id)) 38 | { 39 | console.log(`Updated ${type} ${id}`); 40 | type == 'anime' ? await storeAnime(id, '') : await storeManga(id, ''); 41 | } 42 | } 43 | 44 | // Anime/Manga Page User Recommendations 45 | if ((URL_REGEX.test(LOCATION_HREF) || URL_PHP_REGEX.test(LOCATION_HREF)) && LOCATION_HREF.includes('/userrecs')) 46 | { 47 | let results = document.querySelectorAll('[style*="margin-bottom: 2px"]'); 48 | let type = LOCATION_HREF.includes('/anime') ? 'anime' : 'manga'; 49 | for (let i = 0; i < results.length; i++) 50 | { 51 | if (!document.getElementById(type + i)) 52 | { 53 | let url = results[i].children[0].href; 54 | let urlDecoded = decodeURIComponent(url); 55 | let id = url.split('/')[4]; 56 | //console.log(id) 57 | let selector = 'div[style="margin-bottom: 2px;"] > a[href="' + urlDecoded + '"]'; 58 | addTranslation(type, i, url, id, selector); 59 | } 60 | } 61 | } 62 | 63 | // Recommendations 64 | else if (LOCATION_HREF.includes('https://myanimelist.net/recommendations.php')) 65 | { 66 | let results = document.querySelectorAll('.spaceit.borderClass a:has(strong)'); 67 | let arr = [] 68 | let type = LOCATION_HREF.includes('&t=anime') ? 'anime' : 'manga'; 69 | for (let i = 0; i < results.length; i++) 70 | { 71 | if (!document.getElementById(type + i)) 72 | { 73 | let url = results[i].href; 74 | let urlDecoded = decodeURIComponent(url); 75 | let parts = urlDecoded.split('/' + type); 76 | let urlShort = '/' + type + parts[1]; 77 | if (!arr.includes(urlShort)) 78 | { 79 | arr.push(urlShort) 80 | let id = url.split('/')[4]; 81 | let selector = 'td > a[href*="' + urlShort + '"]:not(:has(+ div[style="font-weight:bold"]))'; 82 | addTranslation(type, i, url, id, selector); 83 | } 84 | } 85 | } 86 | } 87 | 88 | // Anime Top 89 | else if (LOCATION_HREF.includes('https://myanimelist.net/topanime.php')) 90 | { 91 | let results = document.getElementsByClassName('fl-l fs14 fw-b anime_ranking_h3'); 92 | for (let i = 0; i < results.length; i++) 93 | { 94 | if (!document.getElementById('anime' + i)) 95 | { 96 | let url = results[i].children[0].href; 97 | let urlDecoded = decodeURIComponent(url); 98 | let id = url.split('/')[4]; 99 | let selector = '.fl-l.fs14.fw-b.anime_ranking_h3 > a[href="' + urlDecoded + '"]'; 100 | addTranslation('anime', i, url, id, selector); 101 | } 102 | } 103 | } 104 | 105 | // Manga Top 106 | else if (LOCATION_HREF.includes('https://myanimelist.net/topmanga.php')) 107 | { 108 | let results = document.getElementsByClassName('hoverinfo_trigger fs14 fw-b'); 109 | for (let i = 0; i < results.length; i++) 110 | { 111 | if (!document.getElementById('manga' + i)) 112 | { 113 | let url = results[i].href; 114 | let urlDecoded = decodeURIComponent(url); 115 | let id = url.split('/')[4]; 116 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fs14.fw-b'; 117 | addTranslation('manga', i, url, id, selector); 118 | } 119 | } 120 | } 121 | 122 | // Anime List and Manga List 123 | else if (LOCATION_HREF.includes('https://myanimelist.net/animelist') || LOCATION_HREF.includes('https://myanimelist.net/mangalist')) 124 | { 125 | let type = LOCATION_HREF.substring(24, 29); 126 | let results = document.querySelectorAll('tbody:not([style]) .data.title'); 127 | 128 | function processResults(tempResults) 129 | { 130 | for (let i = 0; i < tempResults.length; i++) 131 | { 132 | let url = tempResults[i].children[0].href; 133 | let urlShort = url.slice(23); 134 | let urlShortDecoded = decodeURIComponent(urlShort); 135 | let id = url.split('/')[4]; 136 | let selector = '.data.title > a[href="' + urlShortDecoded + '"]'; 137 | addTranslation(type, i, url, id, selector); 138 | } 139 | } 140 | 141 | function attachMutationObserver(listTable) 142 | { 143 | new MutationObserver(function(mutationsList, observer) 144 | { 145 | mutationsList.forEach(function(mutation) 146 | { 147 | processResults( 148 | Array.from( 149 | mutation.addedNodes, 150 | (addedNode) => addedNode.children[0].children[3] 151 | ) 152 | ); 153 | }); 154 | 155 | if ((listTable.children.length - 1) % 150 !== 0) 156 | { 157 | observer.disconnect(); 158 | } 159 | }).observe( 160 | listTable, 161 | {childList: true} 162 | ); 163 | } 164 | 165 | let table = document.querySelector('table'); 166 | 167 | if (results.length) 168 | { 169 | processResults(results); 170 | if (results.length === 150) 171 | { 172 | attachMutationObserver(table); 173 | } 174 | } 175 | else if (table) 176 | { 177 | new MutationObserver(function(mutationsList, observer) 178 | { 179 | mutationsList.some(function(mutation) 180 | { 181 | return Array.from(mutation.addedNodes).some(function(addedNode) 182 | { 183 | if (addedNode.tagName === 'TABLE') 184 | { 185 | let results = addedNode.querySelectorAll('.data.title'); 186 | processResults(results); 187 | if (results.length === 150) 188 | { 189 | attachMutationObserver(addedNode); 190 | } 191 | observer.disconnect(); 192 | return true; 193 | } 194 | }); 195 | }); 196 | }).observe( 197 | table.parentElement, 198 | {childList: true} 199 | ); 200 | } 201 | } 202 | 203 | // Search 204 | else if (LOCATION_HREF.includes('https://myanimelist.net/search/')) 205 | { 206 | // Anime Results 207 | let resultsAnime = document.querySelectorAll('[class="hoverinfo_trigger fw-b fl-l"][href*="/anime/"]'); 208 | for (let i = 0; i < resultsAnime.length; i++) 209 | { 210 | if (!document.getElementById('anime' + i)) 211 | { 212 | let url = resultsAnime[i].href; 213 | let urlDecoded = decodeURIComponent(url); 214 | let id = url.split('/')[4]; 215 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b.fl-l'; 216 | addTranslation('anime', i, url, id, selector, true); 217 | } 218 | } 219 | 220 | // Manga Results 221 | let resultsManga = document.querySelectorAll('[class="hoverinfo_trigger fw-b"][href*="/manga/"]'); 222 | for (let i = 0; i < resultsManga.length; i++) 223 | { 224 | if (!document.getElementById('manga' + i)) 225 | { 226 | let url = resultsManga[i].href; 227 | let urlDecoded = decodeURIComponent(url); 228 | let id = url.split('/')[4]; 229 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b'; 230 | addTranslation('manga', i, url, id, selector); 231 | } 232 | } 233 | } 234 | 235 | // Anime Search 236 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime.php?q') || LOCATION_HREF.includes('https://myanimelist.net/anime.php?cat')) 237 | { 238 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b fl-l'); 239 | for (let i = 0; i < results.length; i++) 240 | { 241 | if (!document.getElementById('anime' + i)) 242 | { 243 | let url = results[i].href; 244 | let urlDecoded = decodeURIComponent(url); 245 | let id = url.split('/')[4]; 246 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b.fl-l'; 247 | addTranslation('anime', i, url, id, selector, true); 248 | } 249 | } 250 | } 251 | 252 | // Manga Search 253 | else if (LOCATION_HREF.includes('https://myanimelist.net/manga.php?q') || LOCATION_HREF.includes('https://myanimelist.net/manga.php?cat')) 254 | { 255 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b'); 256 | for (let i = 0; i < results.length; i++) 257 | { 258 | if (!document.getElementById('manga' + i)) 259 | { 260 | let url = results[i].href; 261 | let urlDecoded = decodeURIComponent(url); 262 | let id = url.split('/')[4]; 263 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b'; 264 | addTranslation('manga', i, url, id, selector); 265 | } 266 | } 267 | } 268 | 269 | // Anime Seasonal 270 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime/season')) 271 | { 272 | let results = document.getElementsByClassName('link-title'); 273 | for (let i = 0; i < results.length; i++) 274 | { 275 | if (!document.getElementById('anime' + i)) 276 | { 277 | let url = results[i].href; 278 | let urlDecoded = decodeURIComponent(url); 279 | let id = url.split('/')[4]; 280 | let selector = 'a[href="' + urlDecoded + '"].link-title'; 281 | addTranslation('anime', i, url, id, selector, false, true); 282 | } 283 | } 284 | } 285 | 286 | // Reviews 287 | else if (LOCATION_HREF.includes('https://myanimelist.net/reviews.php')) 288 | { 289 | let type = LOCATION_HREF.includes('t=manga') ? 'manga' : 'anime'; 290 | let results = document.querySelectorAll('.review-element .titleblock a.title'); 291 | let processedIds = new Set(); 292 | 293 | for (let i = 0; i < results.length; i++) 294 | { 295 | let url = results[i].href; 296 | let urlDecoded = decodeURIComponent(url); 297 | let id = url.split('/')[4]; 298 | if (!processedIds.has(id)) 299 | { 300 | processedIds.add(id); 301 | let selector = '.review-element .titleblock a.title[href="' + urlDecoded + '"]'; 302 | addTranslation(type, i, url, id, selector); 303 | } 304 | } 305 | } 306 | 307 | // Anime Genres 308 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime/genre')) 309 | { 310 | // Seasonal View 311 | if (document.getElementsByClassName('js-btn-view-style seasonal on')[0]) 312 | { 313 | let results = document.getElementsByClassName('link-title'); 314 | for (let i = 0; i < results.length; i++) 315 | { 316 | if (!document.getElementById('anime' + i)) 317 | { 318 | let url = results[i].href; 319 | let urlDecoded = decodeURIComponent(url); 320 | let id = url.split('/')[4]; 321 | let selector = 'a[href="' + urlDecoded + '"].link-title'; 322 | addTranslation('anime', i, url, id, selector, true, true); 323 | } 324 | } 325 | } 326 | 327 | // List View 328 | else if (document.getElementsByClassName('js-btn-view-style list on')[0]) 329 | { 330 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b'); 331 | for (let i = 0; i < results.length; i++) 332 | { 333 | if (!document.getElementById('anime' + i)) 334 | { 335 | let url = results[i].href; 336 | let urlDecoded = decodeURIComponent(url); 337 | let id = url.split('/')[4]; 338 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b'; 339 | addTranslation('anime', i, url, id, selector); 340 | } 341 | } 342 | } 343 | } 344 | 345 | // Manga Genres 346 | else if (LOCATION_HREF.includes('https://myanimelist.net/manga/genre') || LOCATION_HREF.includes('https://myanimelist.net/manga/adapted')) 347 | { 348 | // Seasonal View 349 | if (document.getElementsByClassName('js-btn-view-style seasonal on')[0]) 350 | { 351 | let results = document.getElementsByClassName('link-title'); 352 | for (let i = 0; i < results.length; i++) 353 | { 354 | if (!document.getElementById('manga' + i)) 355 | { 356 | let url = results[i].href; 357 | let urlDecoded = decodeURIComponent(url); 358 | let id = url.split('/')[4]; 359 | let selector = 'a[href="' + urlDecoded + '"].link-title'; 360 | addTranslation('manga', i, url, id, selector, false, true); 361 | } 362 | } 363 | } 364 | 365 | // List View 366 | else if (document.getElementsByClassName('js-btn-view-style list on')[0]) 367 | { 368 | let results = document.getElementsByClassName('hoverinfo_trigger fw-b'); 369 | for (let i = 0; i < results.length; i++) 370 | { 371 | if (!document.getElementById('manga' + i)) 372 | { 373 | let url = results[i].href; 374 | let urlDecoded = decodeURIComponent(url); 375 | let id = url.split('/')[4]; 376 | let selector = 'a[href="' + urlDecoded + '"].hoverinfo_trigger.fw-b'; 377 | addTranslation('manga', i, url, id, selector); 378 | } 379 | } 380 | } 381 | } 382 | 383 | // Anime Producers 384 | else if (LOCATION_HREF.includes('https://myanimelist.net/anime/producer')) 385 | { 386 | // Tile View 387 | if (document.getElementsByClassName('js-btn-view-style2 tile on')[0]) 388 | { 389 | let results = document.getElementsByClassName('seasonal-anime js-seasonal-anime js-anime-type-all '); 390 | for (let i = 0; i < results.length; i++) 391 | { 392 | if (!document.getElementById('anime' + i)) 393 | { 394 | let url = results[i].children[0].children[0].href 395 | let urlDecoded = decodeURIComponent(url); 396 | let id = url.split('/')[4]; 397 | let selector = '.seasonal-anime.js-seasonal-anime.js-anime-type-all > .title > a[href="' + urlDecoded + '"]'; 398 | addTranslation('anime', i, url, id, selector, false, false, true); 399 | } 400 | } 401 | } 402 | // 403 | // Seasonal View 404 | if (document.getElementsByClassName('js-btn-view-style2 seasonal on')[0]) 405 | { 406 | let results = document.getElementsByClassName('link-title'); 407 | for (let i = 0; i < results.length; i++) 408 | { 409 | if (!document.getElementById('anime' + i)) 410 | { 411 | let url = results[i].href; 412 | let urlDecoded = decodeURIComponent(url); 413 | let id = url.split('/')[4]; 414 | let selector = 'a[href="' + urlDecoded + '"].link-title'; 415 | addTranslation('anime', i, url, id, selector, false, true); 416 | } 417 | } 418 | } 419 | 420 | // List View 421 | else if (document.getElementsByClassName('js-btn-view-style2 list on')[0]) 422 | { 423 | let results = document.getElementsByClassName('seasonal-anime js-seasonal-anime js-anime-type-all'); 424 | for (let i = 0; i < results.length; i++) 425 | { 426 | if (!document.getElementById('anime' + i)) 427 | { 428 | let url = results[i].children[0].children[0].children[0].href; 429 | let urlDecoded = decodeURIComponent(url); 430 | let id = url.split('/')[4]; 431 | let selector = '.spaceit_pad > a[href="' + urlDecoded + '"]'; 432 | addTranslation('anime', i, url, id, selector); 433 | } 434 | } 435 | } 436 | } 437 | 438 | // Anime Shared 439 | else if (LOCATION_HREF.includes('https://myanimelist.net/shared.php') && !LOCATION_HREF.includes('&type=manga')) 440 | { 441 | let results = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit):not([href*="anime/season"])'); 442 | for (let i = 0; i < results.length; i++) 443 | { 444 | if (!document.getElementById('anime' + i)) 445 | { 446 | let url = results[i].href; 447 | let urlShort = url.slice(23); 448 | let urlShortDecoded = decodeURIComponent(urlShort); 449 | let id = url.split('/')[4]; 450 | let selector = 'a[href="' + urlShortDecoded + '"]'; 451 | addTranslation('anime', i, url, id, selector); 452 | } 453 | } 454 | } 455 | 456 | // Manga Shared 457 | else if (LOCATION_HREF.includes('https://myanimelist.net/shared.php') && LOCATION_HREF.includes('&type=manga')) 458 | { 459 | let results = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)'); 460 | for (let i = 0; i < results.length; i++) 461 | { 462 | if (!document.getElementById('manga' + i)) 463 | { 464 | let url = results[i].href; 465 | let urlShort = url.slice(23); 466 | let urlShortDecoded = decodeURIComponent(urlShort); 467 | let id = url.split('/')[4]; 468 | let selector = 'a[href="' + urlShortDecoded + '"]'; 469 | addTranslation('manga', i, url, id, selector); 470 | } 471 | } 472 | } 473 | 474 | // History 475 | else if (LOCATION_HREF.includes('https://myanimelist.net/history')) 476 | { 477 | // Anime Results 478 | let resultsAnime = document.querySelectorAll('[href*="/anime.php?id="]'); 479 | let animeIds = []; 480 | for (let i = 0; i < resultsAnime.length; i++) 481 | { 482 | if (!document.getElementById('anime' + i)) 483 | { 484 | let url = resultsAnime[i].href; 485 | let urlShort = url.slice(23); 486 | let urlShortDecoded = decodeURIComponent(urlShort); 487 | let id = url.split('=')[1]; 488 | let selector = 'a[href="' + urlShortDecoded + '"]'; 489 | if (!animeIds.includes(id)) 490 | { 491 | addTranslation('anime', i, url, id, selector); 492 | } 493 | animeIds.push(id); 494 | } 495 | } 496 | 497 | // Manga Results 498 | let resultsManga = document.querySelectorAll('[href*="/manga.php?id="]'); 499 | let mangaIds = []; 500 | for (let i = 0; i < resultsManga.length-1; i++) 501 | { 502 | if (!document.getElementById('manga' + i)) 503 | { 504 | let url = resultsManga[i].href; 505 | let urlShort = url.slice(23); 506 | let urlShortDecoded = decodeURIComponent(urlShort); 507 | let id = url.split('=')[1]; 508 | let selector = 'a[href="' + urlShortDecoded + '"]'; 509 | if (!mangaIds.includes(id)) 510 | { 511 | addTranslation('manga', i, url, id, selector); 512 | } 513 | mangaIds.push(id); 514 | } 515 | } 516 | } 517 | 518 | // People 519 | else if (LOCATION_HREF.includes('https://myanimelist.net/people')) 520 | { 521 | // Anime Results 522 | let resultsAnime = document.querySelectorAll('[href*="/anime/"]:not(.Lightbox_AddEdit):not([href*="anime/season"])'); 523 | let animeIds = []; 524 | for (let i = 0; i < resultsAnime.length; i++) 525 | { 526 | if (!document.getElementById('anime' + i)) 527 | { 528 | let url = resultsAnime[i].href; 529 | let urlDecoded = decodeURIComponent(url); 530 | let id = url.split('/')[4]; 531 | let selector = 'a[href="' + urlDecoded + '"]:not(.picSurround > a)'; 532 | if (!animeIds.includes(id)) 533 | { 534 | addTranslation('anime', i, url, id, selector); 535 | } 536 | animeIds.push(id); 537 | } 538 | } 539 | 540 | // Manga Results 541 | let resultsManga = document.querySelectorAll('[href*="/manga/"]:not(.Lightbox_AddEdit)'); 542 | let mangaIds = []; 543 | for (let i = 0; i < resultsManga.length; i+=2) 544 | { 545 | if (!document.getElementById('manga' + i)) 546 | { 547 | let url = resultsManga[i].href; 548 | let urlDecoded = decodeURIComponent(url); 549 | let id = url.split('/')[4]; 550 | let selector = 'a[href="' + urlDecoded + '"]:not(.picSurround > a)'; 551 | if (!mangaIds.includes(id)) 552 | { 553 | addTranslation('manga', i, url, id, selector); 554 | } 555 | mangaIds.push(id); 556 | } 557 | } 558 | } 559 | } 560 | 561 | // English title element to be added to page 562 | function createTranslationElement(styleId, englishTitle, styleIdEnd) { 563 | const container = document.createElement('div'); 564 | container.innerHTML = styleId + englishTitle + styleIdEnd; 565 | container.firstElementChild.title = englishTitle; 566 | return container.firstElementChild; 567 | } 568 | 569 | // Get English title (storedAnime and getEnglishTitle) and add to page 570 | function addTranslation(type, count, url, id, selector, parent=false, tile=false, producer=false) 571 | { 572 | let styleId = "" 573 | let styleIdEnd = "" 574 | if (tile) 575 | { 576 | styleId = '

'; 577 | styleIdEnd = '

'; 578 | } 579 | else 580 | { 581 | styleId = '
'; 582 | styleIdEnd = '
'; 583 | } 584 | if (type === 'anime') 585 | { 586 | if (producer) 587 | { 588 | document.getElementsByClassName('category')[count].style.visibility = 'hidden' 589 | } 590 | if (checkAnime(id)) 591 | { 592 | const englishTitle = storedAnime[id][0]; 593 | 594 | document.querySelectorAll(selector).forEach(function(element) 595 | { 596 | if (parent) 597 | { 598 | element = element.parentElement; 599 | } 600 | 601 | // Check for tiles: don't add if h3 with same text already exists 602 | if (tile) { 603 | const titleTextContainer = element.closest('.title-text'); 604 | if (titleTextContainer) { 605 | const existingH3 = titleTextContainer.querySelector('h3.h3_anime_subtitle'); 606 | if (existingH3 && existingH3.textContent.trim() === englishTitle) { 607 | return; 608 | } 609 | } 610 | } 611 | 612 | // Check for non-tiles: don't add if Japanese and English titles are the same 613 | if (!tile) { 614 | const japaneseTitle = element.textContent.trim(); 615 | if (japaneseTitle === englishTitle) { 616 | return; 617 | } 618 | } 619 | 620 | const translation = createTranslationElement(styleId, englishTitle, styleIdEnd); 621 | element.parentNode.insertBefore(translation, element); 622 | }); 623 | } 624 | else 625 | { 626 | getEnglishTitle(type, url, id, selector, parent, styleId, styleIdEnd); 627 | } 628 | } 629 | else if (type === 'manga') 630 | { 631 | if (checkManga(id)) 632 | { 633 | const englishTitle = storedManga[id][0]; 634 | 635 | document.querySelectorAll(selector).forEach(function(element) 636 | { 637 | if (parent) 638 | { 639 | element = element.parentElement; 640 | } 641 | 642 | // Check for tiles: don't add if h3 with same text already exists 643 | if (tile) { 644 | const titleTextContainer = element.closest('.title-text'); 645 | if (titleTextContainer) { 646 | const existingH3 = titleTextContainer.querySelector('h3.h3_anime_subtitle'); 647 | if (existingH3 && existingH3.textContent.trim() === englishTitle) { 648 | return; 649 | } 650 | } 651 | } 652 | 653 | // Check for non-tiles: don't add if Japanese and English titles are the same 654 | if (!tile) { 655 | const japaneseTitle = element.textContent.trim(); 656 | if (japaneseTitle === englishTitle) { 657 | return; 658 | } 659 | } 660 | 661 | const translation = createTranslationElement(styleId, englishTitle, styleIdEnd); 662 | element.parentNode.insertBefore(translation, element); 663 | }); 664 | } 665 | else 666 | { 667 | getEnglishTitle(type, url, id, selector, parent, styleId, styleIdEnd); 668 | } 669 | } 670 | } 671 | 672 | // Request English title from MAL and send to be stored (storeAnime) 673 | function getEnglishTitle(type, url, id, selector, parent, styleId, styleIdEnd) 674 | { 675 | // Create new request 676 | let xhr = new XMLHttpRequest(); 677 | xhr.responseType = 'document'; 678 | 679 | // Set the callback 680 | xhr.onload = async function() 681 | { 682 | if (xhr.readyState === xhr.DONE && xhr.status === 200 && xhr.responseXML !== null) 683 | { 684 | let englishTitleElement = xhr.responseXML.querySelector('.title-english'); 685 | 686 | let englishTitle; 687 | if (englishTitleElement) 688 | { 689 | englishTitle = englishTitleElement.innerText; 690 | } 691 | else 692 | { 693 | englishTitle = ''; 694 | } 695 | 696 | if (type === 'anime') 697 | { 698 | await storeAnime(id, englishTitle); 699 | } 700 | else if (type === 'manga') 701 | { 702 | await storeManga(id, englishTitle); 703 | } 704 | 705 | document.querySelectorAll(selector).forEach(function(element) 706 | { 707 | if (parent) 708 | { 709 | element = element.parentElement; 710 | } 711 | const translation = createTranslationElement(styleId, englishTitle, styleIdEnd); 712 | element.parentNode.insertBefore(translation, element); 713 | }); 714 | } 715 | }; 716 | 717 | // Send the request 718 | xhr.open('GET', url); 719 | xhr.send(); 720 | } 721 | 722 | // Store English titles for anime in cache 723 | async function storeAnime(id, engTitle) 724 | { 725 | storedAnime[id] = [engTitle, Date.now()]; 726 | GM.setValue('anime', storedAnime); 727 | } 728 | 729 | // Store English titles for manga in cache 730 | async function storeManga(id, engTitle) 731 | { 732 | storedManga[id] = [engTitle, Date.now()]; 733 | GM.setValue('manga', storedManga); 734 | } 735 | 736 | // Check if English title for anime is cached, and recheck if empty + last check was >3 weeks 737 | function checkAnime(id) 738 | { 739 | if (storedAnime.hasOwnProperty(id)) 740 | { 741 | if (storedAnime[id][0] === '') 742 | { 743 | let dateNow = Date.now(); 744 | let dateOld = storedAnime[id][1]; 745 | if (dateNow - dateOld > 2628000000) 746 | { 747 | console.log('Updated anime ' + id); 748 | return false; 749 | } 750 | } 751 | return true; 752 | } 753 | console.log('New anime ' + id); 754 | return false; 755 | } 756 | 757 | // Check if English title for manga is cached, and recheck if empty + last check was >3 weeks 758 | function checkManga(id) 759 | { 760 | if (storedManga.hasOwnProperty(id)) 761 | { 762 | if (storedManga[id][0] === '') 763 | { 764 | let dateNow = Date.now(); 765 | let dateOld = storedManga[id][1]; 766 | if (dateNow - dateOld > 2628000000) 767 | { 768 | console.log('Updated manga ' + id); 769 | return false; 770 | } 771 | } 772 | return true; 773 | } 774 | console.log('New manga ' + id); 775 | return false; 776 | } 777 | var storedAnime; 778 | var storedManga; 779 | 780 | (async () => { 781 | // Get cached English titles if they exist, else create empty dictionary 782 | storedAnime = await GM.getValue('anime'); 783 | storedManga = await GM.getValue('manga'); 784 | if (!storedAnime) { 785 | await GM.setValue('anime', {}); 786 | storedAnime = {}; 787 | } 788 | if (!storedManga) { 789 | await GM.setValue('manga', {}); 790 | storedManga = {}; 791 | } 792 | 793 | // Launch actual script 794 | await translate(); 795 | })(); 796 | --------------------------------------------------------------------------------