Skip to content

Instantly share code, notes, and snippets.

@JohnRSim
Created July 11, 2024 10:26
Show Gist options
  • Save JohnRSim/12cbd6751c8c76ae7c79b3627b33dd87 to your computer and use it in GitHub Desktop.
Save JohnRSim/12cbd6751c8c76ae7c79b3627b33dd87 to your computer and use it in GitHub Desktop.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Translation Manager</title>
<style>
/* Modal styles */
.modal {
display: none;
position: fixed;
z-index: 1;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0,0,0,0.4);
}
.modal-content {
background-color: #fefefe;
margin: 15% auto;
padding: 20px;
border: 1px solid #888;
width: 80%;
}
.close {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
}
.close:hover,
.close:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
.highlight {
background-color: yellow;
cursor: pointer;
}
</style>
</head>
<body>
<button id="enableTranslationModeButton">Enable Translation Mode</button>
<div id="translationModal" class="modal">
<div class="modal-content">
<span class="close">&times;</span>
<h2>Edit Translation</h2>
<p>Original: <span id="originalText"></span></p>
<p>Translation: <input type="text" id="translationInput" /></p>
<button id="saveTranslationButton">Save</button>
</div>
</div>
<script>
// Function to collect all text nodes
function getTextNodes(element) {
let textNodes = [];
function recursiveCollect(node) {
if (node.nodeType === Node.TEXT_NODE) {
if (node.nodeValue.trim() !== "") {
textNodes.push(node);
}
} else {
node.childNodes.forEach(recursiveCollect);
}
}
recursiveCollect(element);
return textNodes;
}
// Retrieve translations from local storage or use default
const storedTranslations = localStorage.getItem('translations');
const translations = storedTranslations ? JSON.parse(storedTranslations) : {};
// Function to replace text nodes with translated text
function replaceTextNodes(textNodes, translations) {
textNodes.forEach(node => {
const originalText = node.nodeValue.trim();
if (translations[originalText]) {
node.nodeValue = translations[originalText];
}
});
}
// Function to update the translations object with new strings that need translating
function updateTranslations(textNodes, translations) {
let updated = false;
textNodes.forEach(node => {
const originalText = node.nodeValue.trim();
if (!translations.hasOwnProperty(originalText)) {
translations[originalText] = "";
updated = true;
}
});
if (updated) {
localStorage.setItem('translations', JSON.stringify(translations));
}
}
// Function to clear highlights and remove event listeners
function clearHighlights() {
highlightedElements.forEach(element => {
element.classList.remove('highlight');
element.removeEventListener('click', handleTextClick);
});
highlightedElements = [];
}
// Collect all text nodes in the body
let textNodes = getTextNodes(document.body);
// Update the translations object with new strings that need translating
updateTranslations(textNodes, translations);
// Replace the text nodes with translated text from the JSON object
replaceTextNodes(textNodes, translations);
let translationModeEnabled = false;
const enableTranslationModeButton = document.getElementById('enableTranslationModeButton');
let highlightedElements = [];
// Add event listener to enable/disable translation mode
enableTranslationModeButton.addEventListener('click', () => {
translationModeEnabled = !translationModeEnabled;
if (translationModeEnabled) {
enableTranslationModeButton.textContent = 'Disable Translation Mode';
textNodes = getTextNodes(document.body); // Re-collect text nodes
textNodes.forEach(node => {
const parent = node.parentElement;
if (parent && !parent.classList.contains('highlight') && !parent.closest('.modal')) {
parent.classList.add('highlight');
parent.addEventListener('click', handleTextClick);
highlightedElements.push(parent);
}
});
} else {
enableTranslationModeButton.textContent = 'Enable Translation Mode';
clearHighlights();
}
});
let selectedTextNode = null;
// Handle text element click
function handleTextClick(event) {
// Ensure the click did not happen within the modal
if (event.target.closest('.modal')) {
return;
}
const originalText = event.target.dataset.originalText || event.target.textContent.trim();
selectedTextNode = event.target;
selectedTextNode.dataset.originalText = originalText;
document.getElementById('originalText').textContent = originalText;
document.getElementById('translationInput').value = translations[originalText] || "";
document.getElementById('translationModal').style.display = 'block';
}
// Add event listener to close button to close the modal
document.querySelector('.close').addEventListener('click', () => {
document.getElementById('translationModal').style.display = 'none';
});
// Add event listener to save button to save the translation
document.getElementById('saveTranslationButton').addEventListener('click', () => {
const originalText = document.getElementById('originalText').textContent.trim();
const translation = document.getElementById('translationInput').value.trim();
if (translation) {
translations[originalText] = translation;
selectedTextNode.textContent = translation;
} else {
delete translations[originalText];
selectedTextNode.textContent = originalText;
}
localStorage.setItem('translations', JSON.stringify(translations));
document.getElementById('translationModal').style.display = 'none';
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment