UNPKG

weploy-translate

Version:

Translate your React.js or Next.js app with AI

249 lines (214 loc) 10.6 kB
const { MERGE_PREFIX, isUntranslatedValue } = require("../configs"); const getCacheKey = require("./getCacheKey"); function decodeHTMLEntities(window, text = "") { let textArea = window.document.createElement('textarea'); textArea.innerHTML = text; let decodedText = textArea.value; textArea = null; // Nullify the reference, so garbage collection can take care of it return decodedText; } function updateNode(window, node, language, type = "text", debugSource) { // console.log("update node", debugSource, node, node.textContent, language); // update title if (node == window.document) { const newText = window.translationCache?.[window.location.pathname]?.[language]?.[window.document.title] || ""; if (newText && !isUntranslatedValue(newText)) { window.document.title = decodeHTMLEntities(window, newText); } return; } // update meta tags if (node.tagName == "META") { const newText = window.translationCache?.[window.location.pathname]?.[language]?.[node.content] || ""; if (newText && !isUntranslatedValue(newText)) { node.content = decodeHTMLEntities(window, newText); } return; } // update image if (node.tagName == "IMG") { const newAlt = window.translationCache?.[window.location.pathname]?.[language]?.[node.alt] || ""; const newTitle = window.translationCache?.[window.location.pathname]?.[language]?.[node.title] || ""; if (newAlt && !isUntranslatedValue(newAlt)) { node.alt = decodeHTMLEntities(window, newAlt); } if (newTitle && !isUntranslatedValue(newTitle)) { node.title = decodeHTMLEntities(window, newTitle); } return; } // update anchor title if (type == "seo" && node.tagName == "A") { const newTitle = window.translationCache?.[window.location.pathname]?.[language]?.[node.title] || ""; if (newTitle && !isUntranslatedValue(newTitle)) { node.title = decodeHTMLEntities(window, newTitle); } return; } if (type == "form" && (node.tagName == "TEXTAREA" || (node.tagName == "INPUT" && node.type != "button" && node.type != "submit"))) { const newPlaceholder = window.translationCache?.[window.location.pathname]?.[language]?.[node.placeholder] || ""; if (newPlaceholder && !isUntranslatedValue(newPlaceholder)) { node.placeholder = decodeHTMLEntities(window, newPlaceholder); } return; } if (type == "form" && (node.tagName == "INPUT" && (node.type == "button" || node.type == "submit"))) { const newValue = window.translationCache?.[window.location.pathname]?.[language]?.[node.value] || ""; if (newValue && !isUntranslatedValue(newValue)) { node.value = decodeHTMLEntities(window, newValue); } return; } if (type == "form" && node.tagName == "OPTION") { const newText = window.translationCache?.[window.location.pathname]?.[language]?.[node.textContent] || ""; if (newText && !isUntranslatedValue(newText)) { node.textContent = decodeHTMLEntities(window, newText); } return; } const fullTextArray = node.fullTextArray; const text = node.textContent; const cache = getCacheKey(window, node); // console.log("CACHE", debugSource, cache, node.textContent) // console.log(debugSource, window.translationCache?.[window.location.pathname]?.[language]) const newText = window.translationCache?.[window.location.pathname]?.[language]?.[cache] || ""; // if (node.textContent == "Cost-efficient" || text == "Cost-efficient") { // console.log("Cost-efficient", // fullText, // fullTextArray, // text, // cache, // newText // ) // } if (cache.includes(MERGE_PREFIX) && fullTextArray) { try { const parsedNewText = JSON.parse(newText); const translatedObject = typeof parsedNewText == 'string' ? JSON.parse(parsedNewText) : parsedNewText; const currentIndex = node.fullTextIndex; const isCurrentIndexTheLastIndex = currentIndex == (fullTextArray.length - 1); if (translatedObject.translatedText && translatedObject.translatedMap) { // console.log("node.textContent translatedObject", translatedObject) const translatedText = translatedObject.translatedText; // format: string const translatedMap = translatedObject.translatedMap; // format { "originalText": "translatedText" } const translatedDir = translatedObject.translatedDir || "ltr"; const keys = Object.keys(translatedMap).sort((a, b) => b.length - a.length); const pattern = keys.map(key => { const translatedKey = translatedMap[key]; return translatedKey.replace(/([()])/g, '\\$1'); }).join('|'); const regex = new RegExp(`(${pattern})`, 'g'); // console.log("node.textContent regex", regex) const splitted = translatedText.split(regex); // console.log("node.textContent splitted", splitted) // merge the falsy value into the previous string const mergedSplitted = splitted.reduce((acc, curr) => { if (typeof curr != 'string') { // console.log("node.textContent typeof curr != 'string'", curr) return acc; } if (typeof curr == 'string' && !curr.trim()) { // console.log("node.textContent typeof curr == 'string' && !curr.trim()", curr) acc[acc.length - 1] += curr; return acc; } return [...acc, curr]; }, []); // console.log("node.textContent mergedSplitted", mergedSplitted) const mergedOrphanString = mergedSplitted.reduce((acc, curr, index) => { const findTranslationKey = Object.entries(translatedMap).find(([, value], ) => { const isFirstIndex = index == 0; const isLastIndex = index == mergedSplitted.length - 1; const isFirstOrLast = isFirstIndex || isLastIndex; // trim first or last because sometimes the translation key has extra space but the full translation doesn't have it // TODO: might need to just trim all because the AI can produce weird extra space in the middle too const valueToCompare = isFirstOrLast ? value.trim() : value; const matched = curr.includes(valueToCompare); return matched; })?.[0]; // console.log("node.textContent findTranslationKey", findTranslationKey) if (!findTranslationKey) { return [ ...acc, { value: curr, index: -1 } ]; } const findIndex = fullTextArray.findIndex(key => key.trim() == findTranslationKey.trim()); // console.log("node.textContent findIndex", findIndex) if (findIndex == -1) { return [ ...acc, { value: curr, index: -1 } ]; } return [ ...acc, { value: curr, index: findIndex } ] }, []); // console.log("node.textContent mergedOrphanString", node.textContent, currentIndex, mergedOrphanString) const translatedIndex = mergedOrphanString.findIndex(({ index }) => index == currentIndex) if (translatedIndex == -1) return; let newValue = mergedOrphanString[translatedIndex]?.value; // console.log("node.textContent newValue", node.textContent, text, newValue) // merge to right if (translatedDir == 'ltr') { // if the current index is the first index, and there are still some splitted values left, then concat it if (currentIndex == 0) { newValue = `${mergedOrphanString.slice(0, translatedIndex).map(x => x.value).join(' ')} ${newValue}` } // console.log("node.textContent currentIndex == 0", node.textContent, text, newValue) // find the right newValue, make sure it matched with the text, but start checking from the translatedIndex to the next index for (let i = translatedIndex + 1; i < mergedOrphanString.length; i++) { if (mergedOrphanString[i].index == -1) { newValue = `${newValue} ${mergedOrphanString[i].value}`; // console.log("node.textContent mergedOrphanString", mergedOrphanString, i, node.textContent, text, newValue) } else { break; } } } // merge to left if (translatedDir == 'rtl') { // if the current index is the last index, and there are still some splitted values left, then concat it if (isCurrentIndexTheLastIndex) { newValue = `${newValue} ${mergedOrphanString.slice(translatedIndex + 1, mergedOrphanString.length).map(x => x.value).join(' ')}` } // console.log("node.textContent isCurrentIndexTheLastIndex", node.textContent, text, newValue) // find the right newValue, make sure it matched with the text, but start checking from the translatedIndex to the previous index for (let i = translatedIndex - 1; i >= 0; i--) { if (mergedOrphanString[i].index == -1) { newValue = `${mergedOrphanString[i].value} ${newValue}`; // console.log("node.textContent mergedOrphanString", mergedOrphanString, i, node.textContent, text, newValue) } else { break; } } } // make sure text is still the same before replacing if (node.textContent == text && newValue != text) { // console.log("node.textContent replace", node.textContent, text, newValue) node.textContent = decodeHTMLEntities(window, newValue); // TODO: right now we only replace based on translation position, later we should swap the node position to preserve the styles } } } catch(err) { // do nothing } return; } // console.log("oldText", text) // console.log("newText", newText) // console.log("cache", cache) // console.log("node.textContent", node.textContent == text, node.textContent) if(newText && !isUntranslatedValue(newText)) { // if (node.textContent == "Willkommen im Supermarkt" || text == "Willkommen im Supermarkt") { // console.log("Willkommen im Supermarkt normal", node.textContent == text, node.textContent, text, newText) // } // console.log("isTextStillTheSame", node.textContent == text) // make sure text is still the same before replacing if(node.textContent == text && newText != text) { node.textContent = decodeHTMLEntities(window, newText); } } } module.exports = updateNode;