미디어위키:Common.js: 두 판 사이의 차이

    CABAL WIKI
    편집 요약 없음
    편집 요약 없음
     
    25번째 줄: 25번째 줄:


       /* ----- 브라우저 언어 자동 번역 -----
       /* ----- 브라우저 언어 자동 번역 -----
         영어/일본어 브라우저로 접속하면 자동으로 번역 상태로 시작.
         영어/일본어/중국어 브라우저로 접속하면 자동으로 번역 상태로 시작.
         - googtrans 쿠키가 이미 있으면(이전에 언어를 고른 적 있음) 건드리지 않음
         - googtrans 쿠키가 이미 있으면(이전에 언어를 고른 적 있음) 건드리지 않음
         - 사용자가 위젯을 직접 조작한 적 있으면(gtUserChose) 자동 적용 안 함 */
         - 사용자가 위젯을 직접 조작한 적 있으면(gtUserChose) 자동 적용 안 함 */
    32번째 줄: 32번째 줄:
         var hasCookie = /(^|;\s*)googtrans=/.test(document.cookie);
         var hasCookie = /(^|;\s*)googtrans=/.test(document.cookie);
         var nav = ((navigator.languages && navigator.languages[0]) || navigator.language || '').toLowerCase();
         var nav = ((navigator.languages && navigator.languages[0]) || navigator.language || '').toLowerCase();
         var target = nav.indexOf('ja') === 0 ? 'ja' : (nav.indexOf('en') === 0 ? 'en' : '');
         var target = '';
        if (nav.indexOf('ja') === 0) target = 'ja';
        else if (nav.indexOf('en') === 0) target = 'en';
        else if (nav.indexOf('zh') === 0) {
          // 대만/홍콩/마카오·번체 표기는 번체(zh-TW), 그 외 중국어는 간체(zh-CN)
          target = /zh-(tw|hk|mo|hant)/.test(nav) ? 'zh-TW' : 'zh-CN';
        }
         if (target && !hasCookie && !userChose) {
         if (target && !hasCookie && !userChose) {
           document.cookie = 'googtrans=/ko/' + target + '; path=/';
           document.cookie = 'googtrans=/ko/' + target + '; path=/';
    67번째 줄: 73번째 줄:
       window.addEventListener('resize', updateTranslatePos);
       window.addEventListener('resize', updateTranslatePos);
       updateTranslatePos();
       updateTranslatePos();
      /* ----- 게임 용어 고정 번역 (용어집) -----
        Google이 게임 고유명사를 엉뚱하게 번역하는 것을 막습니다.
        원리: Google 번역이 돌기 전에, 본문에서 아래 용어를 찾아
        해당 언어의 공식 표기로 미리 바꾸고 notranslate 표시를 붙임
        → Google은 그 부분을 건너뛰고 나머지 문장만 번역.
        ★ 용어 추가 방법: 아래에 '한국어 용어': '고정 표기' 한 줄씩 추가.
          긴 용어를 먼저 매칭하므로 순서는 상관없음.
        ★ 주의: 문장 어순까지 고쳐주지는 못함 (용어만 고정) */
      var GT_GLOSSARY = {
        en: {
          '네바레스': 'Nevareth',
          '카발': 'CABAL',
          '블러디 아이스': 'Bloody Ice',
          '데저트 스크림': 'Desert Scream',
          '그린 디스파이어': 'Green Despair',
          '포트 룩스': 'Port Lux',
          '포트 루이나': 'Fort Ruina',
          '언데드 그라운드': 'Undead Ground',
          '포가튼 루인': 'Forgotten Ruin',
          '뮤턴트 포레스트': 'Mutant Forest',
          '폰투스 페룸': 'Pontus Ferrum',
          '포르타 인페르노': 'Porta Inferno',
          '아케인 트레이스': 'Arcane Trace',
          '레이크 사이드': 'Lakeside',
          '앱솔루트 소울 코어': 'Absolute Soul Core'
        },
        ja: {
          // 예시: '네바레스': 'ネヴァレス',
        },
        'zh-CN': {
          // 예시: '네바레스': '内瓦雷斯',
        },
        'zh-TW': {
          // 예시: '네바레스': '內瓦雷斯',
        }
      };
      function gtTargetLang() {
        var m = document.cookie.match(/(?:^|;\s*)googtrans=\/ko\/([a-zA-Z-]+)/);
        return m ? m[1] : '';
      }
      function gtEscapeRegExp(s) {
        return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      }
      function gtApplyGlossary() {
        var lang = gtTargetLang();
        if (!lang || lang === 'ko') return;
        var dict = GT_GLOSSARY[lang];
        if (!dict) return;
        var terms = Object.keys(dict).sort(function (a, b) { return b.length - a.length; });
        if (!terms.length) return;
        var re = new RegExp(terms.map(gtEscapeRegExp).join('|'), 'g');
        var roots = [document.getElementById('mw-content-text'), document.getElementById('firstHeading')];
        roots.forEach(function (root) {
          if (!root) return;
          var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
            acceptNode: function (node) {
              var p = node.parentElement;
              if (!p) return NodeFilter.FILTER_REJECT;
              if (p.closest('script, style, .notranslate, code, pre')) return NodeFilter.FILTER_REJECT;
              return re.test(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
            }
          });
          var nodes = [];
          while (walker.nextNode()) nodes.push(walker.currentNode);
          nodes.forEach(function (node) {
            var text = node.nodeValue;
            var frag = document.createDocumentFragment();
            var last = 0, m;
            re.lastIndex = 0;
            while ((m = re.exec(text)) !== null) {
              if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index)));
              var span = document.createElement('span');
              span.className = 'notranslate';
              span.setAttribute('translate', 'no');
              span.textContent = dict[m[0]];
              frag.appendChild(span);
              last = m.index + m[0].length;
            }
            if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));
            node.parentNode.replaceChild(frag, node);
          });
        });
      }
      // 번역 대상 언어가 정해진 상태라면 Google 번역 전에 용어 교체
      gtApplyGlossary();
      // 위젯에서 언어를 바꾸면(쿠키 변경 감지) 새로고침해서 용어집을 새 언어로 다시 적용
      var gtLangAtLoad = gtTargetLang();
      setInterval(function () {
        var now = gtTargetLang();
        if (now !== gtLangAtLoad) location.reload();
      }, 1000);


       window.googleTranslateElementInit = function () {
       window.googleTranslateElementInit = function () {
         new google.translate.TranslateElement({
         new google.translate.TranslateElement({
           pageLanguage: 'ko',
           pageLanguage: 'ko',
           includedLanguages: 'en,ja,ko',
           includedLanguages: 'en,ja,ko,zh-CN,zh-TW',
           layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
           layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
           autoDisplay: false
           autoDisplay: false

    2026년 7월 8일 (수) 13:20 기준 최신판

    /* 이 자바스크립트 설정은 모든 문서, 모든 사용자에게 적용됩니다. */
    /* MediaWiki:Common.js 에 이 파일 내용 전체를 붙여넣으세요. (CSS는 넣지 말 것!) */
    
    /* ===== 외부 링크 새 창 열기 ===== */
    $(document).ready(function () {
      $('#bodyContent a.external').attr('target', '_blank').attr('rel', 'noopener noreferrer');
    });
    
    /* ===== 목차 박스 드래그 이동 =====
       (원치 않으면 이 블록 전체를 삭제해도 됩니다.
        새 디자인은 목차가 본문 안 박스로 정리되어 있어 고정(fixed) 배치는 껐습니다) */
    mw.loader.using('jquery.ui', function () {
      if ($('#toc').length === 0) return;
      $('#toc').css({ cursor: 'move' });
      $('#toc').draggable({
        containment: 'window',
        scroll: false
      });
    });
    
    /* ===== Google 자동 번역 위젯 =====
       처음에는 배너 옆(카드 오른쪽 여백)에 있다가,
       스크롤해서 지나치면 화면 상단에 고정되어 계속 보입니다. */
    mw.loader.using('mediawiki.util', function () {
    
      /* ----- 브라우저 언어 자동 번역 -----
         영어/일본어/중국어 브라우저로 접속하면 자동으로 번역 상태로 시작.
         - googtrans 쿠키가 이미 있으면(이전에 언어를 고른 적 있음) 건드리지 않음
         - 사용자가 위젯을 직접 조작한 적 있으면(gtUserChose) 자동 적용 안 함 */
      try {
        var userChose = localStorage.getItem('gtUserChose');
        var hasCookie = /(^|;\s*)googtrans=/.test(document.cookie);
        var nav = ((navigator.languages && navigator.languages[0]) || navigator.language || '').toLowerCase();
        var target = '';
        if (nav.indexOf('ja') === 0) target = 'ja';
        else if (nav.indexOf('en') === 0) target = 'en';
        else if (nav.indexOf('zh') === 0) {
          // 대만/홍콩/마카오·번체 표기는 번체(zh-TW), 그 외 중국어는 간체(zh-CN)
          target = /zh-(tw|hk|mo|hant)/.test(nav) ? 'zh-TW' : 'zh-CN';
        }
        if (target && !hasCookie && !userChose) {
          document.cookie = 'googtrans=/ko/' + target + '; path=/';
        }
        // 위젯을 직접 클릭하면 이후로는 사용자의 선택을 존중 (자동 적용 중지)
        document.addEventListener('click', function (e) {
          if (e.target.closest && e.target.closest('#google_translate_element')) {
            localStorage.setItem('gtUserChose', '1');
          }
        }, true);
      } catch (err) { /* 쿠키/스토리지 차단 환경이면 자동 적용만 생략 */ }
    
      var $div = $('<div id="google_translate_element"></div>');
      // 본문 카드(.mw-body)에 붙여서 카드 기준으로 위치 고정 (없으면 body)
      var $anchor = $('.mw-body').first();
      ($anchor.length ? $anchor : $('body')).append($div);
    
      // 스크롤 시 상단 고정 전환
      var PIN_AT = 200; // 이만큼 스크롤하면 상단 고정 (배너를 지나는 지점)
      function updateTranslatePos() {
        var el = document.getElementById('google_translate_element');
        var body = document.querySelector('.mw-body');
        if (!el || !body) return;
        var pinned = window.scrollY > PIN_AT;
        el.classList.toggle('gt-pinned', pinned);
        // 넓은 화면에서 고정 시: 카드 오른쪽 모서리에 맞춰 가로 위치 유지
        if (pinned && window.innerWidth > 1420) {
          el.style.left = (body.getBoundingClientRect().right + 22) + 'px';
        } else {
          el.style.left = '';
        }
      }
      window.addEventListener('scroll', updateTranslatePos, { passive: true });
      window.addEventListener('resize', updateTranslatePos);
      updateTranslatePos();
    
      /* ----- 게임 용어 고정 번역 (용어집) -----
         Google이 게임 고유명사를 엉뚱하게 번역하는 것을 막습니다.
         원리: Google 번역이 돌기 전에, 본문에서 아래 용어를 찾아
         해당 언어의 공식 표기로 미리 바꾸고 notranslate 표시를 붙임
         → Google은 그 부분을 건너뛰고 나머지 문장만 번역.
    
         ★ 용어 추가 방법: 아래에 '한국어 용어': '고정 표기' 한 줄씩 추가.
           긴 용어를 먼저 매칭하므로 순서는 상관없음.
         ★ 주의: 문장 어순까지 고쳐주지는 못함 (용어만 고정) */
      var GT_GLOSSARY = {
        en: {
          '네바레스': 'Nevareth',
          '카발': 'CABAL',
          '블러디 아이스': 'Bloody Ice',
          '데저트 스크림': 'Desert Scream',
          '그린 디스파이어': 'Green Despair',
          '포트 룩스': 'Port Lux',
          '포트 루이나': 'Fort Ruina',
          '언데드 그라운드': 'Undead Ground',
          '포가튼 루인': 'Forgotten Ruin',
          '뮤턴트 포레스트': 'Mutant Forest',
          '폰투스 페룸': 'Pontus Ferrum',
          '포르타 인페르노': 'Porta Inferno',
          '아케인 트레이스': 'Arcane Trace',
          '레이크 사이드': 'Lakeside',
          '앱솔루트 소울 코어': 'Absolute Soul Core'
        },
        ja: {
          // 예시: '네바레스': 'ネヴァレス',
        },
        'zh-CN': {
          // 예시: '네바레스': '内瓦雷斯',
        },
        'zh-TW': {
          // 예시: '네바레스': '內瓦雷斯',
        }
      };
    
      function gtTargetLang() {
        var m = document.cookie.match(/(?:^|;\s*)googtrans=\/ko\/([a-zA-Z-]+)/);
        return m ? m[1] : '';
      }
    
      function gtEscapeRegExp(s) {
        return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
      }
    
      function gtApplyGlossary() {
        var lang = gtTargetLang();
        if (!lang || lang === 'ko') return;
        var dict = GT_GLOSSARY[lang];
        if (!dict) return;
        var terms = Object.keys(dict).sort(function (a, b) { return b.length - a.length; });
        if (!terms.length) return;
        var re = new RegExp(terms.map(gtEscapeRegExp).join('|'), 'g');
    
        var roots = [document.getElementById('mw-content-text'), document.getElementById('firstHeading')];
        roots.forEach(function (root) {
          if (!root) return;
          var walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
            acceptNode: function (node) {
              var p = node.parentElement;
              if (!p) return NodeFilter.FILTER_REJECT;
              if (p.closest('script, style, .notranslate, code, pre')) return NodeFilter.FILTER_REJECT;
              return re.test(node.nodeValue) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
            }
          });
          var nodes = [];
          while (walker.nextNode()) nodes.push(walker.currentNode);
          nodes.forEach(function (node) {
            var text = node.nodeValue;
            var frag = document.createDocumentFragment();
            var last = 0, m;
            re.lastIndex = 0;
            while ((m = re.exec(text)) !== null) {
              if (m.index > last) frag.appendChild(document.createTextNode(text.slice(last, m.index)));
              var span = document.createElement('span');
              span.className = 'notranslate';
              span.setAttribute('translate', 'no');
              span.textContent = dict[m[0]];
              frag.appendChild(span);
              last = m.index + m[0].length;
            }
            if (last < text.length) frag.appendChild(document.createTextNode(text.slice(last)));
            node.parentNode.replaceChild(frag, node);
          });
        });
      }
    
      // 번역 대상 언어가 정해진 상태라면 Google 번역 전에 용어 교체
      gtApplyGlossary();
    
      // 위젯에서 언어를 바꾸면(쿠키 변경 감지) 새로고침해서 용어집을 새 언어로 다시 적용
      var gtLangAtLoad = gtTargetLang();
      setInterval(function () {
        var now = gtTargetLang();
        if (now !== gtLangAtLoad) location.reload();
      }, 1000);
    
      window.googleTranslateElementInit = function () {
        new google.translate.TranslateElement({
          pageLanguage: 'ko',
          includedLanguages: 'en,ja,ko,zh-CN,zh-TW',
          layout: google.translate.TranslateElement.InlineLayout.SIMPLE,
          autoDisplay: false
        }, 'google_translate_element');
      };
    
      var script = document.createElement('script');
      script.src = '//translate.google.com/translate_a/element.js?cb=googleTranslateElementInit';
      document.body.appendChild(script);
    });