BIS 표 한글화

스탯과 장비.. 기타 등등을 한국어로 보여줌

// ==UserScript==
// @name         BIS 표 한글화
// @namespace    ㅇㅇ
// @version      7.11.04
// @description  스탯과 장비.. 기타 등등을 한국어로 보여줌
// @match        https://etro.gg/*
// @match        https://xivgear.app/*
// @grant        GM_addStyle
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    function translateAllTextNodes() {
        const treeWalker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, null, false);
        let node;
        while (node = treeWalker.nextNode()) {
            translateNodeText(node);
        }
    }

    function translateNodeText(node) {
        if (node.parentNode.closest('materia-count-display, single-materia-view-only')) {
            return;
        }

        let text = node.nodeValue;
        for (const wordset of wordsets) {
            for (const [key, value] of Object.entries(wordset)) {
                text = text.replaceAll(key, value);
            }
        }
        node.nodeValue = text;
    }

    const observer = new MutationObserver(() => {
        translateAllTextNodes();
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true,
    });

    window.addEventListener('load', () => {
        translateAllTextNodes();
    });

GM_addStyle(`
    materia-totals-display {
        display: flex !important;
        flex-direction: column !important;
        align-items: center !important;
        justify-content: center !important;
        text-align: center !important;
        margin-bottom: 1em;
        padding: 0;
    }

    materia-count-display {
        display: block !important;
        margin-top: 4px;
        text-align: center;
    }

    .materia-line-wrapper {
        display: flex;
        justify-content: center;
        align-items: center;
        gap: 8px;
        margin-bottom: 4px;
        white-space: nowrap;
    }

    .materia-stat-block-left {
        display: flex;
        align-items: center;
        gap: 6px;
        flex: 1;
    }

    .materia-name-quantity-right {
        font-weight: bold;
        text-align: right;
        min-width: 120px;
        white-space: nowrap;
        flex-shrink: 0;
    }

    .materia-name-quantity {
        white-space: nowrap;
    }

    .materia-stat-block {
        display: flex;
        align-items: center;
        gap: 6px;
        white-space: nowrap;
    }

    single-materia-view-only {
        display: inline-block !important;
        white-space: nowrap;
    }
    slot-materia-manager {
        display: inline-flex !important;
        align-items: center !important;
        margin-right: 20px !important;
        gap: 1px;
    }

    slot-materia-manager > .materia-image-holder {
        flex-shrink: 0;
    }

    slot-materia-manager > span {
        white-space: nowrap !important;
    }
    slot-materia-manager:last-child {
      margin-right: 12 !important;
    }
    .gear-items-view-table th div {
        white-space: nowrap;
        overflow: visible;
    }
`);




    const wordReplacements = {
      //기타2
      "Math": "계산기",
      "Solve for the highest-dps set of melds for this gearset.": "이 장비세트를 위한 최대 DPS 금단을 계산합니다. ",
      "Computation will be much slower without a target GCD.": "글쿨 목표치를 설정하지 않으면 계산이 많이 느릴 것입니다.",
      "Target GCD": "글쿨 목표치",
      "New Gear Set": "새 장비세트",
      "Add Simulation": "시뮬레이션 추가",
      "Default Set": "기본 장비세트",

      //스탯
      "STR": "힘","Strength": "힘",
      "DEX": "민첩","Dexterity": "민첩",
      "VIT": "활력","Vitality": "활력",
      "INT": "지능","Intelligence": "지능",
      "MND": "정신력","Mind": "정신력",

      "CRT": "극대화","Crit": "극대화",
      "DET": "의지력","Determination": "의지력",
      "DHT": "직격","DH": "직격","Direct Hit": "직격",
      "MDEF": "마법 방어",
      "DEF": "물리 방어",

      "SkS": "기시속","SkS": "기시속","Skill Speed": "기시속",
      "SpS": "마시속","SPS": "마시속","Spell Speed": "마시속",
      "TNC": "불굴","Tenacity": "불굴",
      "PIE": "신앙","Piety": "신앙",

      "Main Stat": "주 능력치",
      "WD": "공격력","Weapon Damage": "공격력",
      "GCD": "글쿨",
      "Levels": "레벨",
      "Jobs": "직업","Job": "직업",
      "Stat": "능력치",
      "Multiplier": "계수",
      "Dmg": "데미지",
      "Def": "방어",

      "PoM": "쾌속마",
      "DoT": "도트",

      //장비 슬롯
      "Weapon:": "무기:",
      "Head:": "머리:",
      "Body:": "몸통:",
      "Hand:": "손:",
      "Legs:": "다리:",
      "Feet:": "발:",
      "Ears:": "귀:",
      "Neck:": "목:",
      "Wrist:": "손목:",
      "Left Ring:": "왼쪽 손가락:",
      "Right Ring:": "오른쪽 손가락:",

      "Tome": "석판 장비",
      " Raid": " 레이드 장비",

      //방어구, 장신구 형용사 및 종류
      "Augmented": "보강된","Aug.": "보강된",
      "Quetzalli": "케츠할리",
      "Dark Horse Champion's": "다크호스 챔피언",
      "Ultimate Edenmorn": "절 에덴의 아침",

      //방어구
      "Helm of Fending": "수호자 투구","Bandana of Fending": "수호자 두건",
      "Mail of Fending": "수호자 갑옷","Coat of Fending": "수호자 외투",
      "Gauntlets of Fending": "수호자 건틀릿","Halfgloves of Fending": "수호자 손등장갑",
      "Brais of Fending": "수호자 바지","Breeches of Fending": "수호자 바지",
      "Sollerets of Fending": "수호자 쇠구두","Greaves of Fending": "수호자 갑주장화",

      "Helm of Maiming": "학살자 투구","Bandana of Maiming": "학살자 두건",
      "Mail of Maiming": "학살자 갑옷","Coat of Maiming": "학살자 외투",
      "Gauntlets of Maiming": "학살자 건틀릿","Halfgloves of Maiming": "학살자 손등장갑",
      "Brais of Maiming": "학살자 바지","Breeches of Maiming": "학살자 바지",
      "Sollerets of Maiming": "학살자 쇠구두","Greaves of Maiming": "학살자 갑주장화",

      "Visor of Striking": "타격대 얼굴가리개","Hood of Striking": "타격대 후드",
      "Jacket of Striking": "타격대 재킷","Coat of Striking": "타격대 외투",
      "Vambraces of Striking": "타격대 완갑","Gloves of Striking": "타격대 장갑",
      "Breeches of Striking": "타격대 바지","Brais of Striking": "타격대 바지",
      "Leggings of Striking": "타격대 다리보호대","Boots of Striking": "타격대 장화",

      "Visor of Aiming": "유격대 얼굴가리개","Mask of Aiming": "유격대 가면",
      "Jacket of Aiming": "유격대 재킷",
      "Vambraces of Aiming": "유격대 완갑","Gloves of Aiming": "유격대 장갑",
      "Breeches of Aiming": "유격대 바지","Brais of Aiming": "유격대 바지",
      "Leggings of Aiming": "유격대 다리보호대","Boots of Aiming": "유격대 장화",

      "Visor of Scouting": "정찰대 얼굴가리개","Hood of Scouting": "정찰대 후드",
      "Jacket of Scouting": "정찰대 재킷","Coat of Scouting": "정찰대 외투",
      "Vambraces of Scouting": "정찰대 완갑","Gloves of Scouting": "정찰대 장갑",
      "Breeches of Scouting": "정찰대 바지","Brais of Scouting": "정찰대 바지",
      "Leggings of Scouting": "정찰대 다리보호대","Boots of Scouting": "정찰대 장화",

      "Hood of Healing": "치유사 후드","Hat of Healing": "치유사 모자",
      "Robe of Healing": "치유사 로브","Coat of Healing": "치유사 외투",
      "Gloves of Healing": "치유사 장갑","Halfgloves of Healing": "치유사 손등장갑",
      "Hose of Healing": "치유사 기마바지","Brais of Healing": "치유사 바지",
      "Shoes of Healing": "치유사 신발",

      "Hood of Casting": "마술사 후드","Hat of Casting": "마술사 모자",
      "Robe of Casting": "마술사 로브","Coat of Casting": "마술사 외투",
      "Gloves of Casting": "마술사 장갑","Halfgloves of Casting": "마술사 손등장갑",
      "Hose of Casting": "마술사 기마바지","Brais of Casting": "마술사 바지",
      "Shoes of Casting": "마술사 신발","Shoes of Casting": "마술사 신발",

      //장신구
      "Ear Cuffs of Fending": "수호자 귀찌","Earring of Fending": "수호자 귀걸이",
      "Ear Cuffs of Slaying": "공격대 귀찌","Earring of Slaying": "공격대 귀걸이",
      "Ear Cuffs of Aiming": "유격대 귀찌","Earring of Aiming": "유격대 귀걸이",
      "Ear Cuffs of Healing": "치유사 귀찌","Earring of Healing": "치유사 귀걸이",
      "Ear Cuffs of Casting": "마술사 귀찌","Earring of Casting": "마술사 귀걸이",

      "Necklace of Fending": "수호자 목걸이","Choker of Fending": "수호자 목장식",
      "Necklace of Slaying": "공격대 목걸이","Choker of Slaying": "공격대 목장식",
      "Necklace of Aiming": "유격대 목걸이","Choker of Aiming": "유격대 목장식",
      "Necklace of Healing": "치유사 목걸이","Choker of Healing": "치유사 목장식",
      "Necklace of Casting": "마술사 목걸이","Choker of Casting": "마술사 목장식",

      "Bracelets of Fending": "수호자 팔찌","Bangle of Fending": "수호자 장식고리",
      "Bracelets of Slaying": "공격대 팔찌","Bangle of Slaying": "공격대 장식고리",
      "Bracelets of Aiming": "유격대 팔찌","Bangle of Aiming": "유격대 장식고리",
      "Bracelets of Healing": "치유사 팔찌","Bangle of Healing": "치유사 장식고리",
      "Bracelets of Casting": "마술사 팔찌","Bangle of Casting": "마술사 장식고리",

      "Ring of Fending": "수호자 반지",
      "Ring of Slaying": "공격대 반지",
      "Ring of Aiming": "유격대 반지",
      "Ring of Healing": "치유사 반지",
      "Ring of Casting": "마술사 반지",

      //무기
      "Longsword": "롱소드","Sword": "한손검",
      "Patas": "파타","Jamadhars": "자마다르",
      "Bardiche": "긴날도끼","Labrys": "양날도끼",
      "Spear": "창",
      "Longbow": "장궁",
      "Cane": "환술봉",
      "Rod": "주술봉",
      "Chronicle": "연대기","Index": "금서",
      "Counsel": "상담록","Codex": "치유서",
      "Knives": "단검",
      "Greatsword": "그레이트소드",
      "Handgonne": "권총","Musketoon": "단총",
      "Star Globe": "천구의","Astrometer": "천체광도계",
      "Samurai Blade": "외날검","Blade": "외날검",
      "Rapier": "레이피어","Foil": "플뢰레",
      "Bayonet": "총검","Gunblade": "건블레이드",
      "Chakrams": "차크람","Tathlums": "타흘룸",
      "War Scythe": "전투낫",
      "Wings": "날개","Milpreves": "밀프레베",
      "Twinfangs": "쌍송곳니",
      "Round Brush": "둥근붓",
      "Kite Shield": "연모양 방패",

      //마테리아
      "Quicktongue Mate": "시전의",
      "Quickarm Mate": "신속의",

      "Craftsman's Command Mate": "거장의",
      "Craftsman's Cunning Mate": "명인의",
      "Craftsman's Competence Mate": "장인의",

      "Gatherer's Grasp Mate": "기량의",
      "Gatherer's Guile Mate": "박식의",
      "Gatherer's Guerdon Mate": "달견의",

      "Battledance Mate": "강유의",
      "Savage Might Mate": "야망의",
      "Savage Aim Mate": "무략의",
      "Heavens' Eye Mate": "심안의",

      "Piety Mate": "신앙의",
      "Mind Mate": "정신력의",
      "Intelligence Mate": "지능의",
      "Dexterity Mate": "민첩성의",
      "Vitality Mate": "활력의",
      "Strength Mate": "힘의",

      "ria XII": " 하이알테마마테리쟈",
      "ria XI": " 하이오메가마테리쟈",
      "ria X": " 알테마마테리쟈",
      "ria IX": " 오메가마테리쟈",
      "ria VIII": " 엑스마테리쟈",
      "ria VII": " 메가마테리쟈",
      "ria VI": " 하이마테리쟈",
      "ria V": " 마테리쟈",
      "ria IV": " 마테리가",
      "ria III": " 마테리다",
      "ria II": " 마테리라",
      "ria I": " 마테리아",

      "Materia": "마테리아",
      "Fill/Lock": "채우기/잠금",
      "Mat": "마테",
      "Solve Melds": "금단 계산",
      "Meld": "금단",
      "Solver": "계산기",


      //음식
      "Food": "음식",
      "Moqueca": "무케카",
      "Roast Chicken": "로스트 치킨",
      "Churrasco": "슈하스코",
      "Pineapple Orange Jelly": "파인애플 오렌지 젤리",
      "Navel Orange Cookies": "배꼽 오렌지 쿠키",

      //기타
      "Totals:": "",
      "No materia slots on this item": "마테리아 슬롯 없음",
      "Alternative Items": "대체 가능 장비",
      "Average Item Level": "평균 아이템 레벨",
      "Set Name": "장비세트 이름",
      "max": "최대",
      "alt items": " 대체 가능 장비",
      "The item ": "아이템 \"",
      " can be replaced by all of the following items, which have equivalent or better effective stats:": "\"은(는) 아래의 장비로 대체 가능 합니다.",
      "iLvl": "레벨",
      "iLv": "레벨",

      "To edit this sheet, click the \"Save As\" button below the table.": "이 시트를 수정하려면, 테이블 아래의 \"다른 이름으로 저장\" 버튼을 누르세요. ",
      "Save As": "다른 이름으로 저장",
      "Sheet Name": "시트 이름",
      "Job:": "직업:",
      "Level:": "레벨:",
      "Sync Item Level": "아이템 레벨 조율",
      "You don't have any sheets. Click 'New Sheet' to get started.": "시트가 없습니다. '새 시트'를 클릭해 시작합니다.",
      "New Sheet": "새 시트",
      "New Gear Planning Sheet": "새로운 장비 계획 시트",
      "My Sheets": "내 시트",
      "This is for importing gear set\\(s\\) into this sheet. ": "이 시트에 장비세트를 불러올 수 있습니다. ",
      "If you would like to import a full sheet export \\(including sim settings\\) to a new sheet,": "시뮬레이터 설정을 포함해 내보냈었던 전체 시트를 불러오고 싶다면,",
      "use the \"Import Sheet\" at the top of the page. You can import a gear planner URL or JSON, or an Etro URL.": "페이지 상단의 \"시트 불러오기\"를 사용하세요. 링크나 JSON, Etro 에서도 불러올 수 있습니다.",

      "Import Sheet": "시트 불러오기",
      "This will import into a new sheet. You can paste a gear planner link, gear planner JSON, or an Etro link.": "새 시트로 불러옵니다.",
      "Import Sets": "장비세트 불러오기",
      "Import Gear Set\\(s\\)": "장비세트 불러오기",
      "Import": "불러오기",

      "Export Full Sheet": "전체 시트 내보내기",
      "Link to Whole Sheet": "전체 시트 링크",
      "One Link for Each Set": "개별 장비세트 링크",
      "Embed URL for Each Set": "개별 장비세트 임베드 URL",
      "JSON for Whole Sheet": "전체 시트 JSON",

      "Export Individual Set": "개별 장비세트 내보내기",
      "Link to This Set": "선택한 세트 링크",
      "JSON for This Set": "선택한 장비세트 JSON",
      "Embed URL for This Set": "선택한 장비세트 임베드 URL",
      "Export to Teamcraft": "전체 시트의 JSON",

      "Choose Sims to Export": "내보낼 시뮬레이터 선택",

      "Generate": "내보내기",
      "Preview": "미리보기",
      "Close": "닫기",

      "Gear": "장비",
      "Filters": "필터",
      "Show NQ Items": "NQ 아이템 표시",

      " Sim": " 시뮬",
      "Sim:": "시뮬레이터:",

      //"Export...": "내보내기...",
      "Export Whole Sheet": "전체 시트 내보내기",
      "Whole Sheet": "전체 시트",
      "Selected Set": "선택한 세트",

      "Toggle Header": "제목",
      "Toggle Details": "자세히",

      "Name": "이름",

      "Source": "얻는 곳",
      "Raid": "레이드",
      "Ultimate": "절 레이드",
      "Crafted": "제작",
      "Unknown": "알 수 없음",

      "Settings": "설정",
      "Dark": "다크",
      "Light": "라이트",
      "Classic": "클래식",
      "Modern": "모던",
      "Game Items Language:": "게임 아이템 언어:",
      "Meld Solver Workers:": "금단 계산기:",

      //직업
      "PLD": "나이트",
      "WAR": "전사",
      "DRK": "암흑기사",
      "GNB": "건블레이드",

      "WHM": "백마도사",
      "SCH": "학자",
      "AST": "점성술사",
      "SGE": "현자",

      "MNK": "몽크",
      "DRG": "용기사",
      "NIN": "닌자",
      "SAM": "사무라이",
      "RPR": "리퍼",
      "VPR": "바이퍼",

      "BRD": "음유시인",
      "MCH": "기공사",
      "DNC": "무도가",

      "BLM": "흑마도사",
      "SMN": "소환사",
      "RDM": "적마도사",
      "PCT": "픽토맨서",

      "BLU": "청마도사",

      //종족
      "Select a Race/Clan": "종족/부족 선택",
      "Duskwight": "황혼 부족",
      "Wildwood": "숲 부족",
      "Seekers of the Sun": "태양의 추종자",
      "Keepers of the Moon": "달의 수호자",
      "Sea Wolf": "바다늑대",
      "Hellsguard": "불꽃지킴이",
      "The Lost": "떠도는 별",
      "Helion": "맴도는 별",
      "Highlander": "고원 부족",
      "Midlander": "중원 부족",
      "Plainsfolk": "평원 부족",
      "Dunesfolk": "사막 부족",
      "Rava": "라바 비에라",
      "Veena": "비나 비에라",
      "Xaela": "아우라 젤라",
      "Raen": "아우라 렌",

      //파티 보너스
      "No Party Bonus": "파티 보너스 없음",
      "1 Unique Roles": "1종류 직업군",
      "2 Unique Roles": "2종류 직업군",
      "3 Unique Roles": "3종류 직업군",
      "4 Unique Roles": "4종류 직업군",
      "5 Unique Roles": "5종류 직업군",
    };

    function replaceText() {
        const walk = document.createTreeWalker(
            document.body,
            NodeFilter.SHOW_TEXT,
            null,
            false
        );

        let node;
        while ((node = walk.nextNode())) {
            let newText = node.nodeValue;

            newText = newText.replace(/\+(\d+)\s+([가-힣]+)/g, (_, num, stat) => `${stat} +${num}`);

            for (const [original, replacement] of Object.entries(wordReplacements)) {
                newText = newText.replace(new RegExp(original, 'g'), replacement);
            }

            if (newText !== node.nodeValue) {
                node.nodeValue = newText;
            }
        }
    }

    function updateMateriaLabels() {
        const materiaDisplays = document.querySelectorAll('materia-count-display');

        materiaDisplays.forEach(display => {
            if (display.getAttribute('data-updated') === 'true') return;

            const title = display.getAttribute('title');
            if (!title) return;

            const nameMatch = title.match(/^(.+?)\s*:/);
            const statMatch = title.match(/:\s*\+(\d+)\s+(.+)$/);
            if (!nameMatch || !statMatch) return;

            const materiaName = nameMatch[1];
            const statValue = statMatch[1];
            const statNameOriginal = statMatch[2];

            const quantityElem = display.querySelector('.materia-count-quantity');
            const materiaView = display.querySelector('single-materia-view-only');
            if (!quantityElem || !materiaView) return;

            const quantityText = quantityElem.textContent.match(/\d+/)?.[0];
            if (!quantityText) return;

            const imageHolder = materiaView.querySelector('.materia-image-holder');
            const statSpanInView = materiaView.querySelector('span');
            const statText = statSpanInView?.textContent || `${statNameOriginal} +${statValue}`;

            display.innerHTML = '';

            const wrapper = document.createElement('div');
            wrapper.className = 'materia-line-wrapper';

            const statBlock = document.createElement('div');
            statBlock.className = 'materia-stat-block-left';

            if (materiaView && imageHolder) {
                const clonedView = materiaView.cloneNode(true);
                const holders = clonedView.querySelectorAll('.materia-image-holder');
                const spans = clonedView.querySelectorAll('span');

                holders.forEach((el, idx) => {
                    if (idx > 0) el.remove();
                });

                spans.forEach((el, idx) => {
                    if (idx > 0) el.remove();
                });

                statBlock.appendChild(clonedView);
            }

            const nameQuantitySpan = document.createElement('span');
            nameQuantitySpan.className = 'materia-name-quantity-right';
            nameQuantitySpan.textContent = `${materiaName} ${quantityText}개`;

            const exportOption = document.querySelector('option[label="Export..."]');
            if (exportOption) {
            exportOption.label = '내보내기...';
            }

            wrapper.appendChild(statBlock);
            wrapper.appendChild(nameQuantitySpan);
            display.appendChild(wrapper);
            display.setAttribute('data-updated', 'true');
        });
    }

    window.addEventListener('load', () => {
        replaceText();
        updateMateriaLabels();

        const observer = new MutationObserver(() => {
            replaceText();
            updateMateriaLabels();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true,
            characterData: true
        });
    });
})();