您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Load translations for Online Math Contest problems and editorials. / OMCの問題文および公式解説文の翻訳を表示します。
// ==UserScript== // @name OMC Translator // @namespace https://github.com/yuyuuuuuuuuuuuu/omc-translations // @version 1.0.0 // @description Load translations for Online Math Contest problems and editorials. / OMCの問題文および公式解説文の翻訳を表示します。 // @author yuyuuuuuuuuuuuu // @match https://onlinemathcontest.com/* // @grant none // @homepageURL https://github.com/yuyuuuuuuuuuuuu/omc-translations // @license MIT // ==/UserScript== (function() { 'use strict'; // 設定 const GITHUB_USER = 'yuyuuuuuuuuuuuu', REPO_NAME = 'omc-translations', BRANCH = 'main'; // 対応言語リスト (code: 言語コード, label: ハンバーグ表示) const LANGUAGES = [ { code: 'en', label: 'English 🇺🇸' }, { code: 'ja', label: '日本語 🇯🇵' } // 追加例(誰か2ドルくれ): { code: 'zh', label: '中文 🇨🇳' } ]; // デフォルト 'en' const getLang = () => { const v = localStorage.getItem('omcLang'); return LANGUAGES.some(l => l.code === v) ? v : 'en'; }; const setLang = code => localStorage.setItem('omcLang', code); // URL から contestId/taskId を抽出 (type: 'tasks' or 'editorial') const parseInfo = type => { const re = new RegExp(`^/contests/([^/]+)/(?:${type})/(\\d+)(?:/|$)`); const m = location.pathname.match(re); return m ? { contestId: m[1], taskId: m[2] } : null; }; // GitHub raw URL を生成(type: 'tasks' or 'editorial', lang, contestId, taskId) const rawUrl = (type, lang, c, t) => `https://raw.githubusercontent.com/${GITHUB_USER}/${REPO_NAME}/${BRANCH}` + `/languages/${lang}/contests/${c}/${type}/${t}.html`; // 言語ハンバーグをヘッダーに追加 function addLangDropdown() { const ul = document.querySelector('.navbar-nav.mr-auto'); if (!ul) return; const current = getLang(); const li = document.createElement('li'); li.className = 'nav-item dropdown'; li.style.marginLeft = '10px'; const toggle = document.createElement('a'); toggle.className = 'nav-link dropdown-toggle'; toggle.href = '#'; toggle.id = 'langDropdown'; toggle.setAttribute('role', 'button'); toggle.setAttribute('data-toggle', 'dropdown'); toggle.textContent = `Language: ${LANGUAGES.find(l => l.code === current).label}`; const menu = document.createElement('div'); menu.className = 'dropdown-menu'; menu.setAttribute('aria-labelledby', 'langDropdown'); LANGUAGES.forEach(l => { const a = document.createElement('a'); a.className = 'dropdown-item'; a.href = '#'; a.dataset.code = l.code; a.textContent = l.label; if (l.code === current) a.style.fontWeight = 'bold'; a.addEventListener('click', e => { e.preventDefault(); setLang(l.code); toggle.textContent = `Language: ${l.label}`; menu.classList.remove('show'); location.reload(); }); menu.appendChild(a); }); li.appendChild(toggle); li.appendChild(menu); ul.appendChild(li); } //('problem_content' or 'editorial_content') 置き換え function replaceContent(type) { const info = parseInfo(type); if (!info || getLang() === 'ja') return; const { contestId, taskId } = info; const url = rawUrl(type, getLang(), contestId, taskId); fetch(url) .then(res => { if (!res.ok) throw new Error('not found'); return res.text(); }) .then(html => { const sel = type === 'tasks' ? 'problem_content' : 'editorial_content'; const container = document.getElementById(sel); if (container) container.innerHTML = html; }) .catch(() => { const sel = type === 'tasks' ? 'problem_content' : 'editorial_content'; const c = document.getElementById(sel); if (c) { const p = document.createElement('p'); p.textContent = "It seems the translation hasn't been completed yet... Please wait a little longer..."; p.style.color = 'red'; p.style.marginTop = '1em'; c.appendChild(p); } }); } // 実行 const main = () => { addLangDropdown(); replaceContent('tasks'); replaceContent('editorial'); }; if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } })();