// ==UserScript==
// @name YouTube Customizable Subtitles / Youtube Subtitulos Personalizables
// @namespace http://tampermonkey.net/
// @version 1.3
// @description Allows you to customize YouTube subtitles / Le permite personalizar los subtítulos de YouTube.
// @author Eterve Nallo - Diam
// @license MIT
// @match *://*.youtube.com/*
// @grant GM_addStyle
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// ==/UserScript==
(function() {
'use strict';
const languages = {
en: {
configTitle: 'Subtitle Settings',
outlineColor: 'Outline Color:',
outlineWidth: 'Outline Width:',
textColor: 'Text Color:',
saveButton: 'Save',
closeButton: 'X',
languageButton: 'Language',
languageLabel: 'Choose Language',
menuCommand: 'Show/Hide Subtitle Settings'
},
es: {
configTitle: 'Configuración de Subtítulos',
outlineColor: 'Color del Contorno:',
outlineWidth: 'Grosor del Contorno:',
textColor: 'Color del Texto:',
saveButton: 'Guardar',
closeButton: 'X',
languageButton: 'Idioma',
languageLabel: 'Elegir Idioma',
menuCommand: 'Mostrar/Ocultar Configuración de Subtítulos'
},
ja: {
configTitle: '字幕設定',
outlineColor: 'アウトラインカラー:',
outlineWidth: 'アウトラインの幅:',
textColor: '文字色:',
saveButton: '保存',
closeButton: 'X',
languageButton: '言語',
languageLabel: '言語を選択',
menuCommand: '字幕設定を表示/非表示'
},
ru: {
configTitle: 'Настройки субтитров',
outlineColor: 'Цвет контура:',
outlineWidth: 'Ширина контура:',
textColor: 'Цвет текста:',
saveButton: 'Сохранить',
closeButton: 'X',
languageButton: 'Язык',
languageLabel: 'Выберите язык',
menuCommand: 'Показать/Скрыть настройки субтитров'
},
ko: {
configTitle: '자막 설정',
outlineColor: '윤곽선 색상:',
outlineWidth: '윤곽선 너비:',
textColor: '글자 색상:',
saveButton: '저장',
closeButton: 'X',
languageButton: '언어',
languageLabel: '언어 선택',
menuCommand: '자막 설정 표시/숨기기'
},
zh: {
configTitle: '字幕设置',
outlineColor: '轮廓颜色:',
outlineWidth: '轮廓宽度:',
textColor: '文字颜色:',
saveButton: '保存',
closeButton: 'X',
languageButton: '语言',
languageLabel: '选择语言',
menuCommand: '显示/隐藏字幕设置'
}
};
const defaultConfig = {
outlineColor: 'black',
outlineWidth: '1px',
textColor: 'white',
showPanel: false,
language: 'en'
};
function loadConfig() {
const savedConfig = GM_getValue('ytSubtitleConfig');
if (savedConfig) {
return JSON.parse(savedConfig);
}
return defaultConfig;
}
function saveConfig(config) {
GM_setValue('ytSubtitleConfig', JSON.stringify(config));
}
const config = loadConfig();
const lang = languages[config.language] || languages.en;
function applySubtitleStyles() {
const shadowOffset = `${parseFloat(config.outlineWidth) / 2}px`;
GM_addStyle(`
.ytp-caption-segment {
color: ${config.textColor} !important;
text-shadow:
${shadowOffset} ${shadowOffset} 0 ${config.outlineColor},
-${shadowOffset} -${shadowOffset} 0 ${config.outlineColor},
${shadowOffset} -${shadowOffset} 0 ${config.outlineColor},
-${shadowOffset} ${shadowOffset} 0 ${config.outlineColor};
background: transparent !important;
font-weight: bold;
font-size: calc(16px + 1vw);
}
`);
}
function createConfigPanel() {
const panel = document.createElement('div');
panel.id = 'subtitleConfigPanel';
panel.style.position = 'fixed';
panel.style.bottom = '10px';
panel.style.right = '10px';
panel.style.padding = '10px';
panel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
panel.style.color = 'white';
panel.style.borderRadius = '5px';
panel.style.zIndex = '9999';
panel.style.display = 'block';
panel.innerHTML = `
<h4>${lang.configTitle}</h4>
<label>${lang.outlineColor}
<input type="color" id="outlineColor" value="${config.outlineColor}">
</label><br>
<label>${lang.outlineWidth}
<input type="number" id="outlineWidth" min="1" max="10" step="1" value="${parseFloat(config.outlineWidth)}">
</label><br>
<label>${lang.textColor}
<input type="color" id="textColor" value="${config.textColor}">
</label><br>
<button id="saveConfig">${lang.saveButton}</button>
<button id="closePanel" style="margin-top: 10px;">${lang.closeButton}</button>
<br><button id="changeLanguage">${lang.languageButton}</button>
`;
document.body.appendChild(panel);
document.getElementById('saveConfig').addEventListener('click', () => {
config.outlineColor = document.getElementById('outlineColor').value;
config.outlineWidth = `${Math.max(1, Math.min(parseFloat(document.getElementById('outlineWidth').value), 10))}px`;
config.textColor = document.getElementById('textColor').value;
applySubtitleStyles();
saveConfig(config);
});
document.getElementById('closePanel').addEventListener('click', () => {
panel.style.display = 'none';
config.showPanel = false;
saveConfig(config);
closeLanguageMenu();
});
document.getElementById('changeLanguage').addEventListener('click', () => {
const langPanel = document.createElement('div');
langPanel.id = 'languageMenu';
langPanel.style.position = 'fixed';
langPanel.style.bottom = '10px';
langPanel.style.right = '150px';
langPanel.style.padding = '10px';
langPanel.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
langPanel.style.color = 'white';
langPanel.style.borderRadius = '5px';
langPanel.style.zIndex = '10000';
langPanel.innerHTML = `
<h4>${lang.languageLabel}</h4>
<button id="langEn">English</button><br>
<button id="langEs">Español</button><br>
<button id="langJa">日本語</button><br>
<button id="langRu">Русский</button><br>
<button id="langKo">한국어</button><br>
<button id="langZh">中文</button>
<button id="closeLangMenu" style="margin-top: 10px;">${lang.closeButton}</button>
`;
document.body.appendChild(langPanel);
document.getElementById('closeLangMenu').addEventListener('click', () => {
closeLanguageMenu();
});
function setLanguage(language) {
config.language = language;
saveConfig(config);
location.reload();
}
document.getElementById('langEn').addEventListener('click', () => setLanguage('en'));
document.getElementById('langEs').addEventListener('click', () => setLanguage('es'));
document.getElementById('langJa').addEventListener('click', () => setLanguage('ja'));
document.getElementById('langRu').addEventListener('click', () => setLanguage('ru'));
document.getElementById('langKo').addEventListener('click', () => setLanguage('ko'));
document.getElementById('langZh').addEventListener('click', () => setLanguage('zh'));
document.addEventListener('click', function(event) {
if (!langPanel.contains(event.target) && event.target.id !== 'changeLanguage') {
closeLanguageMenu();
}
});
function closeLanguageMenu() {
const langMenu = document.getElementById('languageMenu');
if (langMenu) {
langMenu.remove();
}
}
});
}
function toggleConfigPanel() {
const panel = document.getElementById('subtitleConfigPanel');
if (panel) {
panel.style.display = panel.style.display === 'none' ? 'block' : 'none';
config.showPanel = panel.style.display === 'block';
saveConfig(config);
} else {
createConfigPanel();
}
}
GM_registerMenuCommand(lang.menuCommand, toggleConfigPanel);
applySubtitleStyles();
if (config.showPanel) {
createConfigPanel();
}
})();