mirror of
https://ghfast.top/https://github.com/StarCitizenToolBox/StarCitizenBoxBrowserEx.git
synced 2025-05-09 21:51:25 +08:00
296 lines
11 KiB
TypeScript
296 lines
11 KiB
TypeScript
declare const $: any;
|
|
declare const timeago: any;
|
|
|
|
let SCLocalizationReplaceLocalesMap = {};
|
|
let SCLocalizationEnableSplitMode = false;
|
|
let SCLocalizationTranslating = false;
|
|
|
|
function InitWebLocalization() {
|
|
// init script
|
|
LocalizationWatchUpdate();
|
|
// load Data
|
|
_loadLocalizationData();
|
|
}
|
|
|
|
function LocalizationWatchUpdate() {
|
|
const m = window.MutationObserver || (window as any).WebKitMutationObserver;
|
|
const observer = new m(function (mutations: MutationRecord[], observer: MutationObserver) {
|
|
for (let mutationRecord of mutations) {
|
|
for (let node of mutationRecord.addedNodes) {
|
|
traverseElement(node as Element);
|
|
}
|
|
}
|
|
});
|
|
|
|
observer.observe(document.body, {
|
|
subtree: true,
|
|
characterData: true,
|
|
childList: true,
|
|
});
|
|
|
|
if (window.location.href.includes("robertsspaceindustries.com")) {
|
|
SCLocalizationEnableSplitMode = true;
|
|
}
|
|
|
|
if (window.location.hostname.includes("www.erkul.games")) {
|
|
document.body.addEventListener("click", function (event) {
|
|
setTimeout(function () {
|
|
allTranslate().then(_ => {
|
|
});
|
|
}, 200);
|
|
});
|
|
}
|
|
}
|
|
|
|
function WebLocalizationUpdateReplaceWords(w) {
|
|
let replaceWords = w.sort(function (a, b) {
|
|
return b.word.length - a.word.length;
|
|
});
|
|
replaceWords.forEach(({ word, replacement }) => {
|
|
SCLocalizationReplaceLocalesMap[word] = replacement;
|
|
});
|
|
if (window.location.hostname.startsWith("issue-council.robertsspaceindustries.com")) {
|
|
SCLocalizationReplaceLocalesMap["save"] = "保存";
|
|
}
|
|
allTranslate().then(_ => {
|
|
});
|
|
}
|
|
|
|
async function allTranslate() {
|
|
async function replaceTextNode(node: Node, parentNode?: Element) {
|
|
if (node.nodeType === Node.TEXT_NODE) {
|
|
// 保存原始文本内容
|
|
const originalText = node.nodeValue || '';
|
|
const translatedText = GetSCLocalizationTranslateString(originalText);
|
|
|
|
// 只有当文本发生变化时才保存原始文本
|
|
if (originalText !== translatedText && parentNode) {
|
|
const originalValue = parentNode.getAttribute('data-original-value') || "";
|
|
parentNode.setAttribute('data-original-value', originalValue + originalText);
|
|
node.nodeValue = translatedText;
|
|
}
|
|
} else {
|
|
for (let i = 0; i < node.childNodes.length; i++) {
|
|
await replaceTextNode(node.childNodes[i], node as Element);
|
|
}
|
|
}
|
|
}
|
|
|
|
await replaceTextNode(document.body, document.body);
|
|
}
|
|
|
|
async function undoTranslate(): Promise<{success: boolean}> {
|
|
SCLocalizationTranslating = false;
|
|
|
|
document.querySelectorAll('*[data-original-value]').forEach((element: Element) => {
|
|
(element as HTMLElement).innerText = element.getAttribute('data-original-value') || '';
|
|
element.removeAttribute('data-original-value');
|
|
});
|
|
|
|
// 处理输入元素
|
|
const inputElements = document.querySelectorAll('input[type="button"], input[type="submit"], input[type="text"], input[type="password"]');
|
|
inputElements.forEach((el: Element) => {
|
|
// 尝试从 data-original-value 属性恢复原始值
|
|
if (el.hasAttribute('data-original-value')) {
|
|
if ((el as HTMLInputElement).type === 'button' || (el as HTMLInputElement).type === 'submit') {
|
|
(el as HTMLInputElement).value = el.getAttribute('data-original-value') || '';
|
|
} else {
|
|
(el as HTMLInputElement).placeholder = el.getAttribute('data-original-value') || '';
|
|
}
|
|
el.removeAttribute('data-original-value');
|
|
}
|
|
});
|
|
|
|
return Promise.resolve({ success: true });
|
|
}
|
|
|
|
function traverseElement(el: Element) {
|
|
if (!SCLocalizationTranslating) {
|
|
return;
|
|
}
|
|
|
|
if (!shouldTranslateEl(el)) {
|
|
return
|
|
}
|
|
|
|
for (const child of el.childNodes) {
|
|
if (["RELATIVE-TIME", "TIME-AGO"].includes(el.tagName)) {
|
|
translateRelativeTimeEl(el);
|
|
return;
|
|
}
|
|
|
|
if (child.nodeType === Node.TEXT_NODE) {
|
|
translateElement(child, el);
|
|
} else if (child.nodeType === Node.ELEMENT_NODE) {
|
|
if ((child as Element).tagName === "INPUT") {
|
|
translateElement(child, el);
|
|
} else {
|
|
traverseElement(child as Element);
|
|
}
|
|
} else {
|
|
// pass
|
|
}
|
|
}
|
|
}
|
|
|
|
function translateElement(el, parentNode) {
|
|
// Get the text field name
|
|
let k;
|
|
let translatedText;
|
|
if (el.tagName === "INPUT") {
|
|
if (el.type === 'button' || el.type === 'submit') {
|
|
k = 'value';
|
|
} else {
|
|
k = 'placeholder';
|
|
}
|
|
|
|
translatedText = GetSCLocalizationTranslateString(el[k]);
|
|
if (el[k] === translatedText) return;
|
|
|
|
const originalValue = parentNode.getAttribute('data-original-value') || "";
|
|
el.setAttribute('data-original-value', originalValue + el[k]);
|
|
} else {
|
|
k = 'data';
|
|
|
|
translatedText = GetSCLocalizationTranslateString(el[k]);
|
|
if (el[k] === translatedText) return;
|
|
|
|
const originalValue = parentNode.getAttribute('data-original-value') || "";
|
|
parentNode.setAttribute('data-original-value', originalValue + el[k]);
|
|
}
|
|
el[k] = translatedText;
|
|
}
|
|
|
|
function translateRelativeTimeEl(el: Element) {
|
|
const lang = (navigator.language || navigator.language);
|
|
const datetime = ($ as any)(el).attr('datetime');
|
|
($ as any)(el).text((timeago as any).format(datetime, lang.replace('-', '_')));
|
|
}
|
|
|
|
function shouldTranslateEl(el: Element) {
|
|
const blockIds: string[] = [];
|
|
const blockClass = [
|
|
"css-truncate" // 过滤文件目录
|
|
];
|
|
const blockTags = ["IMG", "svg", "mat-icon"];
|
|
if (blockTags.includes(el.tagName)) {
|
|
return false;
|
|
}
|
|
if (el.id && blockIds.includes(el.id)) {
|
|
return false;
|
|
}
|
|
if (el.classList) {
|
|
for (let clazz of blockClass) {
|
|
if (el.classList.contains(clazz)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function GetSCLocalizationTranslateString(txtSrc) {
|
|
const key = txtSrc.toLowerCase().replace(/\xa0/g, ' ').replace(/\s{2,}/g, ' ').trim();
|
|
const sourceKey = txtSrc.replace(/\xa0/g, ' ').replace(/\s{2,}/g, ' ').trim();
|
|
let noTheKey = key.replace("the ", "");
|
|
let noHorizontalKey = key.replace("- ", "");
|
|
|
|
if (SCLocalizationReplaceLocalesMap[key]) {
|
|
txtSrc = SCLocalizationReplaceLocalesMap[key]
|
|
} else if (SCLocalizationEnableSplitMode) {
|
|
if (sourceKey.includes(" - ")) {
|
|
let nodeValue = txtSrc
|
|
let splitKey = sourceKey.split(" - ");
|
|
if (splitKey[0].toLowerCase() === "upgrade" && key.includes("to") && key.endsWith("edition")) {
|
|
// 升级包规则
|
|
let noVersionStr = key.replace("STANDARD EDITION".toLowerCase(), "").replace("upgrade", "").replace("WARBOND EDITION".toLowerCase(), "")
|
|
let shipNames = noVersionStr.split(" to ")
|
|
let finalString = "升级包 " + GetSCLocalizationTranslateString(shipNames[0]) + " 到 " + GetSCLocalizationTranslateString(shipNames[1]);
|
|
if (key.endsWith("WARBOND EDITION".toLowerCase())) {
|
|
finalString = finalString + " 战争债券版"
|
|
} else {
|
|
finalString = finalString + " 标准版"
|
|
}
|
|
txtSrc = finalString
|
|
} else {
|
|
// 机库通用规则
|
|
splitKey.forEach(function (splitKey) {
|
|
if (SCLocalizationReplaceLocalesMap[splitKey.toLowerCase()]) {
|
|
nodeValue = nodeValue.replace(splitKey, SCLocalizationReplaceLocalesMap[splitKey.toLowerCase()])
|
|
} else {
|
|
nodeValue = nodeValue.replace(splitKey, GetSCLocalizationTranslateString(splitKey))
|
|
}
|
|
});
|
|
txtSrc = nodeValue
|
|
}
|
|
} else if (key.endsWith("starter pack") || key.endsWith("starter package")) {
|
|
let shipName = key.replace("starter package", "").replace("starter pack", "").trim()
|
|
if (SCLocalizationReplaceLocalesMap[shipName.toLowerCase()]) {
|
|
shipName = SCLocalizationReplaceLocalesMap[shipName.toLowerCase()];
|
|
}
|
|
txtSrc = shipName + " 新手包";
|
|
} else if (key.startsWith("the ") && SCLocalizationReplaceLocalesMap[noTheKey]) {
|
|
txtSrc = SCLocalizationReplaceLocalesMap[noTheKey];
|
|
} else if (key.startsWith("- ") && SCLocalizationReplaceLocalesMap[noHorizontalKey]) {
|
|
txtSrc = "- " + SCLocalizationReplaceLocalesMap[noHorizontalKey];
|
|
}
|
|
}
|
|
return txtSrc
|
|
}
|
|
|
|
InitWebLocalization();
|
|
|
|
function _loadLocalizationData() {
|
|
chrome.runtime.sendMessage({ action: "_loadLocalizationData", url: window.location.href }, function (response) {
|
|
console.log("response ===" + JSON.stringify(response));
|
|
if (response.result.length > 0) {
|
|
SCLocalizationTranslating = true;
|
|
WebLocalizationUpdateReplaceWords(response.result);
|
|
}
|
|
});
|
|
}
|
|
|
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
if (request.action === "_toggleTranslation") {
|
|
if (SCLocalizationTranslating) {
|
|
SCLocalizationTranslating = false;
|
|
undoTranslate();
|
|
window.postMessage({ type: 'TOGGLED-SC-BOX-TRANSLATE', action: 'off' }, '*');
|
|
return;
|
|
}
|
|
SCLocalizationTranslating = true;
|
|
SCLocalizationEnableSplitMode = true;
|
|
WebLocalizationUpdateReplaceWords(request.data);
|
|
window.postMessage({ type: 'TOGGLED-SC-BOX-TRANSLATE', action: 'on' }, '*');
|
|
}
|
|
});
|
|
|
|
window.addEventListener('message', async (event) => {
|
|
if (event.source !== window || !event.data || event.data.type !== 'SC_TRANSLATE_REQUEST') return;
|
|
|
|
const { action } = event.data;
|
|
|
|
let response: {success: boolean, error?: string} = { success: false };
|
|
|
|
if (action === 'translate') {
|
|
try {
|
|
SCLocalizationEnableSplitMode = true;
|
|
chrome.runtime.sendMessage({ action: "_loadLocalizationData", url: "manual" }, function (response) {
|
|
SCLocalizationTranslating = true;
|
|
WebLocalizationUpdateReplaceWords(response.result);
|
|
});
|
|
response = { success: true };
|
|
} catch (error: any) {
|
|
response = { success: false, error: error.message };
|
|
}
|
|
} else if (action === 'undoTranslate') {
|
|
try {
|
|
response = await undoTranslate();
|
|
} catch (error: any) {
|
|
response = { success: false, error: error.message };
|
|
}
|
|
}
|
|
});
|
|
|
|
window.postMessage({ type: 'SC-BOX-TRANSLATE-API-AVAILABLE' }, '*');
|