/* === English language toggle === */ (function () { "use strict"; if (window.__engToggleInstalled) return; window.__engToggleInstalled = true; const JP_TO_EN = new Map([ ["ファイル", "File"], ["編集", "Edit"], ["元画像", "Original Image"], ["設定", "Settings"], ["画像出力", "Image Export"], ["動画出力", "Video Export"], ["ライト", "Light"], ["ダーク", "Dark"], ["クラシック", "Classic"], ["倍率", "Scale "], ["角度", "Angle "], ["透過", "Opacity"], ["【Alt + ← or →】1ドットずらす", "【Alt + ← or →】Nudge 1 px"], ["新規タブ", "New Tab"], ["リスト編集", "Edit List"], ["追加したい文字列", "String to Add"], ["- 削除", "- Delete"], ["選択中の見出しを削除しますか?", "Delete selected heading?"], ["この文字を削除しますか?:", "Delete this character?: "], //今のところ対応しません ["選択中のファイルを削除しますか?", "Delete selected file?"], ["選択中のAAを削除しますか?", "Delete selected AA?"], ["削除しました", "Deleted"], ["+ 追加", "+ Add"], ["追加", "Add"], ["テスト", "Test"], ["透明色", "Transparent"], ["背景色", "Background Color"], ["文字色", "Text Color"], ["プレビュー", "Preview"], ["ダウンロード", "Download"], ["画像化", "Convert to Image"], ["ダウンロードボタンのリンク先から保存してください", "You may save from the download button"], ["確認", "Confirmation"], ["aalist読み込み", "Load aalist"], ["aalist出力", "Export aalist"], ["ダウンロードフォルダに保存しました", "Saved to download folder"], ["とじる", "Close"], ["マニュアル", "Manual"], ["情報", "Info"], ["開く", "Open"], ["画像選択", "Select Image"], ["ウェブ", "Web"], ["ローカル", "Local"], ["参照(ローカル)", "Browse (Local)"], ["設定初期化", "Reset Settings"], ["線色", "Line Color"], ["位置", "Position"], ["その他", "Other"], ["保存", "Save"], ["キャンセル", "Cancel"], ["出力設定", "Export Settings"], ["ファイル形式", "File Format"], ["txt: 現在編集中のAAを保存します", " txt: Save currently edited AA"], ["mlt: 現在編集中のファイルを保存します(AA見出しなし)", "mlt: Save current file (without AA header)"], ["ast: 現在編集中のファイルを保存します(AA見出しあり)", "ast: Save current file (with AA header)"], ["aaa: 現在編集中のファイルを保存します(編集履歴含む)", "aaa: Save current file (with edit history)"], ["エンコード", "Encoding"], ["utf-8: 最近の形式", " utf-8: Modern format"], ["shift-jis: (´д`)Edit 等で編集したい場合の形式", "shift-jis: Format for editing with tools like (´д`)Edit"], ["【ダブルクリック】名前の変更 【D & D】順番を入れ替える", "【Double-click】Rename 【Drag & Drop】Reorder"], ["【右クリック】追加・削除", "【Right-click】Add/Delete"], ["【ダブルクリック】名前の変更 【右クリック】追加・削除 【D & D】順番を入れ替える", "【Double-click】Rename 【Right-click】Add/Delete 【Drag & Drop】Reorder"], ["Unicode空白の使用(ドットずらし時)", "Unicode Spaces (for nudging)"], ["使う", "Use   "], ["使わない", "Do Not Use"], ["空白の強調表示・行末表示", "Show Spaces & EOL"], ["する(重い)", "Enable (Heavy)"], ["エラー時のみ(軽い)", "Only on Error (Light)"], ["空白の強調表示方法", "Space Highlighting Method"], ["テキスト(軽い)", "Text (Light)"], ["DOM要素(重い)", "DOM Elements (Heavy)"], ["[最終編集AAの保持]", "[Keep Last Edited AA]"], ["する", "Enable "], ["しない", "Disable"], ["[画像プレビュー位置]", "[Image Preview Position]"], ["左", "Left "], ["右", "Right"], ["[グリッドの表示]", "[Show Grid]"], ["[配色]", "[Color Scheme]"], ["動画モード", "Video Mode"], ["動画化", "Convert to Video"], ["動画作成中 (数分かかります)", "Creating video (this will take a few minutes)"], ["1ドット左", "1 Dot Left"], ["1ドット右", "1 Dot Right"], ["コピー(Shift-JIS)", "Copy (Shift-JIS)"], ["コピー(UTF-8)", "Copy (UTF-8)"], ["矩形選択", "Rect Select"], ["挿入 / 上書", "Insert / Overwrite"], ["挿入", "Insert"], ["上書", "Overwrite"], ["空白追加", "Add Space"], ["行末空白削除", "Remove Trailing Spaces"], ["行頭空白追加", "Add Leading Spaces"], ["行頭空白削除", "Remove Leading Spaces"], ["空行削除", "Remove Empty Lines"], ["行末を揃える", "Align Line Ends"], ["行末から1文字削除", "Delete Last Chars"] ]); const STORAGE_KEY = "app.lang"; const LANG_EN = "en"; const LANG_JA = "ja"; const ORIGINALS = new WeakMap(); function getLang() { try { return localStorage.getItem(STORAGE_KEY) || LANG_JA; } catch (_) { return LANG_JA; } } function setLang(lang) { try { localStorage.setItem(STORAGE_KEY, lang); } catch (_) {} } function eachTextNode(root, cb) { const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, { acceptNode(node) { if (!node.nodeValue || !node.nodeValue.trim()) return NodeFilter.FILTER_REJECT; return NodeFilter.FILTER_ACCEPT; } }); const nodes = []; while (walker.nextNode()) nodes.push(walker.currentNode); for (const n of nodes) cb(n); } function toEnglish(root) { eachTextNode(root, (n) => { const raw = n.nodeValue; const t = raw.trim(); if (!ORIGINALS.has(n)) ORIGINALS.set(n, raw); if (JP_TO_EN.has(t)) { n.nodeValue = raw.replace(t, JP_TO_EN.get(t)); } }); } function toJapanese(root) { eachTextNode(root, (n) => { if (ORIGINALS.has(n)) { n.nodeValue = ORIGINALS.get(n); } }); } function applyLanguage(lang) { if (lang === LANG_EN) { toEnglish(document.body); } else { toJapanese(document.body); } } function findMainMenu() { return document.querySelector( "#mainMenu, .mainMenu, [data-main-menu], nav[aria-label='Main menu']" ); } /* clone exact visual identity from a reference menu item */ function cloneMenuItemLook(ref, el) { try { el.className = ref.className; // exact classes const styleTxt = ref.getAttribute("style"); if (styleTxt) el.setAttribute("style", styleTxt); // exact inline style for (const a of Array.from(ref.attributes)) { const name = a.name; if (name === "id" || name === "class" || name === "style") continue; /* copy Vue/React scoped attrs and data-* so styles apply (e.g., data-v-xxxx) */ el.setAttribute(name, a.value); } } catch (_) {} } function getReferenceMenuItem(menu) { /* prefer the last real item (often closest in layout), otherwise first */ let ref = null; const kids = Array.from(menu.children).filter((n) => n.id !== "eng-lang-toggle-inline"); for (let i = kids.length - 1; i >= 0; i--) { if (kids[i].textContent && kids[i].textContent.trim()) { ref = kids[i]; break; } } return ref || kids[0] || null; } function attachInlineToggle() { const menu = findMainMenu(); if (menu && !document.getElementById("eng-lang-toggle-inline")) { const span = document.createElement("span"); span.id = "eng-lang-toggle-inline"; span.textContent = getLang() === LANG_EN ? "日本語" : "English"; /* behave like a button while remaining a */ span.setAttribute("role", "button"); span.setAttribute("tabindex", "0"); span.style.cursor = "pointer"; /* clone exact classes/attrs from a real menu item so it's indistinguishable */ const ref = getReferenceMenuItem(menu); if (ref) cloneMenuItemLook(ref, span); /* ensure same hover behavior as other items */ if (!/\bmouseout\b/.test(span.className) && !/\bmouseover\b/.test(span.className)) { span.className += (span.className ? " " : "") + "mouseout"; } span.addEventListener("mouseenter", function () { this.className = this.className.replace(/\bmouseout\b/g, "").trim(); if (!/\bmouseover\b/.test(this.className)) { this.className += (this.className ? " " : "") + "mouseover"; } }); span.addEventListener("mouseleave", function () { this.className = this.className.replace(/\bmouseover\b/g, "").trim(); if (!/\bmouseout\b/.test(this.className)) { this.className += (this.className ? " " : "") + "mouseout"; } }); menu.appendChild(span); span.addEventListener("click", onToggle); span.addEventListener("keydown", function (e) { if (e.key === "Enter" || e.key === " ") { e.preventDefault(); onToggle(); } }); span.__bound = true; } } function updateInlineLabel() { const el = document.getElementById("eng-lang-toggle-inline"); if (el) el.textContent = getLang() === LANG_EN ? "日本語" : "English"; } function onToggle() { const next = getLang() === LANG_EN ? LANG_JA : LANG_EN; setLang(next); applyLanguage(next); updateInlineLabel(); } function init() { attachInlineToggle(); updateInlineLabel(); const lang = getLang(); requestAnimationFrame(() => applyLanguage(lang)); const mo = new MutationObserver(() => { if (getLang() === LANG_EN) { clearTimeout(mo._t); mo._t = setTimeout(() => toEnglish(document.body), 0); } /* ensure the span persists across menu re-renders and keeps exact look */ if (!document.getElementById("eng-lang-toggle-inline") && findMainMenu()) { attachInlineToggle(); updateInlineLabel(); } }); mo.observe(document.documentElement, { childList: true, subtree: true, characterData: true }); } if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", init, { once: true }); } else { init(); } })();