您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Load translations for Online Math Contest. / OMCの翻訳を表示します。
当前为
// ==UserScript== // @name OMC Translator // @namespace https://github.com/yuyuuuuuuuuuuuu/omc-translations // @version 1.1.0 // @description Load translations for Online Math Contest. / 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' const REPO_NAME = 'omc-translations' const BRANCH = 'main' const LANG_KEY = 'omcLang' let LANGUAGES = [] let MESSAGES = {} async function loadLanguageConfig() { const urlConfig = `https://raw.githubusercontent.com/${GITHUB_USER}/${REPO_NAME}/${BRANCH}/languages/config.json`; const urlLabel = `https://raw.githubusercontent.com/${GITHUB_USER}/${REPO_NAME}/${BRANCH}/languages/label.json`; try { const [confRes, labelRes] = await Promise.all([ fetch(urlConfig), fetch(urlLabel) ]); const conf = await confRes.json(); // { languages: ["en", ...] } const labels = await labelRes.json(); // { en: "English 🇺🇸", ja: "日本語 🇯🇵 original", ... } // 日本語を最初に、以降翻訳対象を順に LANGUAGES = [{ code: 'ja', label: labels['ja'] || '日本語' }]; for (const code of conf.languages) { if (code !== 'ja') { LANGUAGES.push({ code, label: labels[code] || code }); } } } catch (e) { console.error('Language config の読み込みに失敗:', e); // フォールバック: 日本語と英語のみ LANGUAGES = [ { code: 'ja', label: '日本語' }, { code: 'en', label: 'English 🇺🇸' } ]; } } async function loadMessages() { if (getLang() === 'ja') return; const urlMsg = `https://raw.githubusercontent.com/${GITHUB_USER}/${REPO_NAME}/${BRANCH}` + `/languages/${getLang()}/static/messages.json`; try { MESSAGES = await fetch(urlMsg).then(r => r.json()); } catch (e) { console.warn('messages.json の読み込みに失敗:', e); MESSAGES = {}; } } function getLang() { const v = localStorage.getItem(LANG_KEY); return LANGUAGES.some(l => l.code === v) ? v : 'ja'; } function setLang(code) { localStorage.setItem(LANG_KEY, code); } 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 = 'omcLangDropdown'; 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', 'omcLangDropdown'); LANGUAGES.forEach(l => { const a = document.createElement('a'); a.className = 'dropdown-item'; a.href = '#'; a.textContent = l.label; if (l.code === current) a.style.fontWeight = 'bold'; a.addEventListener('click', e => { e.preventDefault(); setLang(l.code); location.reload(); }); menu.appendChild(a); }); li.appendChild(toggle); li.appendChild(menu); ul.appendChild(li); } async function translateStaticUI() { if (getLang() === 'ja') return; const base = `https://raw.githubusercontent.com/${GITHUB_USER}/${REPO_NAME}/${BRANCH}` + `/languages/${getLang()}/static`; let config; try { config = await fetch(`${base}/config.json`).then(r => r.json()); } catch (e) { console.error('config.json の取得に失敗:', e); return; } const path = location.pathname; const entries = config.filter(c => c.paths.some(p => new RegExp(`^${p}$`).test(path)) ); if (!entries.length) return; const dictNames = [...new Set(entries.flatMap(e => e.dictionaries))]; const dict = {}; for (const name of dictNames) { try { const d = await fetch(`${base}/${name}.json`).then(r => r.json()); Object.assign(dict, d); } catch (e) { console.warn(`辞書 ${name}.json の読み込みに失敗:`, e); } } const walker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, null, false ); let node; while (node = walker.nextNode()) { // ——— 動的コンテンツ (#problem_content, #editorial_content) は除外 ——— if (node.parentElement.closest('#problem_content, #editorial_content')) { continue; } let text = node.nodeValue; if (!text.trim()) continue; text = text.replace(/[\u00A0\u3000]/g, ' '); let replaced = text; for (const [ja, en] of Object.entries(dict)) { const key = ja.replace(/[\u00A0\u3000]/g, ' '); if (key && replaced.includes(key)) { replaced = replaced.split(key).join(en); } } if (replaced !== text) { node.nodeValue = replaced; } } } function parseUserEditorial() { const m = location.pathname.match( /^\/contests\/([^\/]+)\/editorial\/(\d+)\/(\d+)(?:\/|$)/ ); return m ? { contestId: m[1], taskId: m[2], userId: m[3] } : null; } function rawUrl(type, contestId, id) { return `https://raw.githubusercontent.com/${GITHUB_USER}/${REPO_NAME}/${BRANCH}` + `/languages/${getLang()}/contests/${contestId}/${type}/${id}.html`; } function appendMessage(container, text, color) { const p = document.createElement('p'); p.textContent = text; p.style.color = color; p.style.marginTop = '1em'; container.appendChild(p); } function replaceTasks() { const m = location.pathname.match( /^\/contests\/([^\/]+)\/tasks\/(\d+)(?:\/$|$)/ ); if (!m || getLang() === 'ja') return; const c = document.getElementById('problem_content'); fetch(rawUrl('tasks', m[1], m[2])) .then(r => { if (!r.ok) throw 0; return r.text(); }) .then(html => { if (!c) return; c.innerHTML = html; // 注意書きを追加 if (MESSAGES.tasks) { appendMessage(c, MESSAGES.tasks, 'blue'); } }) .catch(() => { if (c && MESSAGES.tasks_not_done) { appendMessage(c, MESSAGES.tasks_not_done, 'orange'); } }); } function replaceEditorial() { const m = location.pathname.match( /^\/contests\/([^\/]+)\/editorial\/(\d+)(?:\/$|$)/ ); if (!m || getLang() === 'ja' || parseUserEditorial()) return; const c = document.getElementById('editorial_content'); fetch(rawUrl('editorial', m[1], m[2])) .then(r => { if (!r.ok) throw 0; return r.text(); }) .then(html => { if (!c) return; c.innerHTML = html; // 注意書きを追加 if (MESSAGES.editorials) { appendMessage(c, MESSAGES.editorials, 'blue'); } }) .catch(() => { if (c && MESSAGES.editorial_not_done) { appendMessage(c, MESSAGES.editorial_not_done, 'orange'); } }); } function replaceUserEditorial() { const info = parseUserEditorial(); if (!info || getLang() === 'ja') return; const c = document.getElementById('editorial_content'); fetch(rawUrl('user_editorial', info.contestId, info.userId)) .then(r => { if (!r.ok) throw 0; return r.text(); }) .then(html => { if (!c) return; c.innerHTML = html; if (MESSAGES.user_editorial) { appendMessage(c, MESSAGES.user_editorial, 'blue'); } }) .catch(() => { if (c && MESSAGES.user_editorial_not_done) { appendMessage(c, MESSAGES.user_editorial_not_done, 'orange'); } }); } async function main() { await loadLanguageConfig(); addLangDropdown(); await loadMessages(); await translateStaticUI(); replaceTasks(); replaceUserEditorial(); replaceEditorial(); } if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', main); } else { main(); } })();