var currentScript = document.currentScript;
// const TRANSLATION_PLUGIN_API_KEY = currentScript.getAttribute('secretKey');
const posX = currentScript.getAttribute("data-pos-x") || 100;
const posY = currentScript.getAttribute("data-pos-y") || 5;
let defaultTranslatedLanguage = currentScript.getAttribute(
"default-translated-language"
);
const languageListAttribute = currentScript.getAttribute(
"translation-language-list"
);
var initialPreferredLanguage = currentScript.getAttribute("initial_preferred_language")
const pageSourceLanguage = currentScript.getAttribute("page-source-language") || "en";
const TRANSLATION_PLUGIN_API_BASE_URL = new URL(
currentScript.getAttribute("src")
).origin;
const languageDetection = currentScript.getAttribute("language-detection") || false;
// const TRANSLATION_PLUGIN_API_BASE_URL = "https://translation-plugin.bhashini.co.in"
let mixedCode = currentScript.getAttribute("mixed-code") || false;
const supportedTargetLangArr = [
{ code: "en", label: "English" },
{ code: "as", label: "Assamese (অসমীয়া)" },
{ code: "bn", label: "Bengali (বাংলা)" },
{ code: "brx", label: "Bodo (बड़ो)" },
{ code: "doi", label: "Dogri (डोगरी)" },
{ code: "gom", label: "Goan Konkani (गोवा कोंकणी)" },
{ code: "gu", label: "Gujarati (ગુજરાતી)" },
{ code: "hi", label: "Hindi (हिन्दी)" },
{ code: "kn", label: "Kannada (ಕನ್ನಡ)" },
{ code: "ks", label: "Kashmiri (کٲشُر)" },
{ code: "mai", label: "Maithili (मैथिली)" },
{ code: "ml", label: "Malayalam (മലയാളം)" },
{ code: "mni", label: "Manipuri (মণিপুরী)" },
{ code: "mr", label: "Marathi (मराठी)" },
{ code: "ne", label: "Nepali (नेपाली)" },
{ code: "or", label: "Odia (ଓଡ଼ିଆ)" },
{ code: "pa", label: "Punjabi (ਪੰਜਾਬੀ)" },
{ code: "sa", label: "Sanskrit (संस्कृत)" },
{ code: "sat", label: "Santali (संताली)" },
{ code: "sd", label: "Sindhi (سنڌي)" },
{ code: "ta", label: "Tamil (தமிழ்)" },
{ code: "te", label: "Telugu (తెలుగు)" },
{ code: "ur", label: "Urdu (اردو)" },
];
const CHUNK_SIZE = 25;
// Define translationCache object to store original text
var translationCache = {};
// Flag to track whether content has been translated initially
var isContentTranslated = false;
// Selected target language for translation
let selectedTargetLanguageCode =
localStorage.getItem("preferredLanguage") || initialPreferredLanguage
// Retrieve translationCache from session storage if available
if (sessionStorage.getItem("translationCache")) {
translationCache = JSON.parse(sessionStorage.getItem("translationCache"));
}
var cssLink = document.createElement("link");
cssLink.rel = "stylesheet";
cssLink.href = `${TRANSLATION_PLUGIN_API_BASE_URL}/v2/website_translation_utility.css`;
// cssLink.href = `./plugin.css`;
// Append link to the head
document.head.appendChild(cssLink);
let selectedRating = 0;
const getPoweredByText = (lang) => {
switch (lang) {
case "kn":
return "ಮೂಲಕ ನಡೆಸಲ್ಪಡುತ್ತಿದೆ";
case "te":
return "ఆధారితం";
default:
return "Powered by";
}
};
function toggleDropdown() {
const dropdown = document.getElementById("bhashiniLanguageDropdown");
dropdown.style.display =
dropdown.style.display === "block" ? "none" : "block";
const dropdownHeight = dropdown.clientHeight;
const windowHeight = window.innerHeight;
const dropdownTop = dropdown.getBoundingClientRect().top;
if (windowHeight - dropdownTop < dropdownHeight) {
dropdown.style.bottom = "100%";
dropdown.style.top = "auto";
} else {
dropdown.style.top = "100%";
dropdown.style.bottom = "auto";
}
}
// Fetch supported translation languages
function fetchTranslationSupportedLanguages() {
const targetLangSelectElement = document.getElementById(
"bhashiniLanguageDropdown"
);
const brandingDiv = document.createElement("div");
brandingDiv.setAttribute("class", "bhashini-branding");
const poweredBy = document.createElement("span");
poweredBy.textContent = getPoweredByText(selectedTargetLanguageCode);
const bhashiniLogo = document.createElement("img");
bhashiniLogo.src = `${TRANSLATION_PLUGIN_API_BASE_URL}/v2/bhashini-logo.png`;
bhashiniLogo.alt = "Bhashini Logo";
// feedback button
// if (selectedTargetLanguageCode !== "en") {
const feedbackDiv = document.createElement('div');
feedbackDiv.setAttribute("class", "bhashini-feedback-div");
feedbackDiv.setAttribute("title", "Feedback");
// feedbackButton.innerHTML = ``;
const feedbackButton = document.createElement('button');
feedbackButton.setAttribute("class", "bhashini-feedback-button ");
feedbackButton.setAttribute("title", "Feedback");
feedbackButton.addEventListener("click", function () {
const feedbackModal = document.querySelector('.bhashini-feedback-modal');
feedbackModal.style.display = "block";
});
feedbackButton.innerHTML = `
`;
// feedbackButton.innerHTML = `
`;
feedbackDiv.appendChild(feedbackButton);
brandingDiv.appendChild(feedbackDiv);
// }
brandingDiv.appendChild(poweredBy);
brandingDiv.appendChild(bhashiniLogo);
if (languageListAttribute) {
// If the languageList attribute is present
// remove extra spaces and split the string into an array
// so if the attribute is "en, hi, ta", it will be converted to ["en", "hi", "ta"]
const languageList = languageListAttribute
.split(",")
.map((lang) => lang.trim());
const filteredLanguages = supportedTargetLangArr.filter((lang) =>
languageList.includes(lang.code)
);
// Loop through the filtered languages and create of ption elements for the dropdown
filteredLanguages.forEach((element, index) => {
let option_element = document.createElement("div");
option_element.setAttribute("class", "dont-translate language-option");
option_element.setAttribute("data-value", element.code);
option_element.textContent = element.label;
// Set the first language as the default selected option
if (index === 0) {
option_element.setAttribute("selected", "selected");
}
targetLangSelectElement.appendChild(option_element);
});
} else {
supportedTargetLangArr.forEach((element) => {
let option_element = document.createElement("div");
option_element.setAttribute("class", "dont-translate language-option");
option_element.setAttribute("data-value", element.code);
option_element.textContent = element.label;
targetLangSelectElement.appendChild(option_element);
});
}
targetLangSelectElement.appendChild(brandingDiv);
// Add single event listener to parent container using event delegation
targetLangSelectElement.addEventListener("click", function (event) {
const languageOption = event.target.closest(".language-option");
if (languageOption) {
selectLanguage(languageOption.textContent);
}
});
}
// Function to split an array into chunks of a specific size
function chunkArray(array, size) {
const chunkedArray = [];
for (let i = 0; i < array.length; i += size) {
chunkedArray.push(array.slice(i, i + size));
}
return chunkedArray;
}
// Function to get all input and textArea element with placeholders
function getInputElementsWithPlaceholders() {
return Array.from(
document.querySelectorAll("input[placeholder], textarea[placeholder]")
);
}
async function translateTitleAttributes(element, target_lang) {
const elementsWithTitle = element.querySelectorAll("[title]");
const titleTexts = Array.from(elementsWithTitle).map((el) =>
el.getAttribute("title")
);
if (titleTexts.length > 0) {
const translatedTitles = await translateTextChunks(titleTexts, target_lang);
elementsWithTitle.forEach((el, index) => {
const translatedTitle =
translatedTitles[index].target || titleTexts[index];
el.setAttribute("title", translatedTitle);
});
}
}
// Function to translate all input elements with placeholders
async function translatePlaceholders(target_lang) {
const inputs = getInputElementsWithPlaceholders();
const placeholders = inputs.map((input) => input.placeholder);
if (placeholders.length > 0) {
const translatedPlaceholders = await translateTextChunks(
placeholders,
target_lang
);
inputs.forEach((input, index) => {
const translatedPlaceholder =
translatedPlaceholders[index].target || placeholders[index];
input.placeholder = translatedPlaceholder;
});
}
}
// Function to translate text chunks using custom API
async function translateTextChunks(chunks, target_lang) {
if (target_lang === "en") {
return chunks.map((chunk) => ({ source: chunk, target: chunk }));
}
const payload = {
targetLanguage: target_lang,
textData: chunks,
};
if (mixedCode === "true") {
payload.mixed_code = true;
}
if (languageDetection === "true") {
payload.languageDetection = true;
}
else {
payload.sourceLanguage = "en"
}
try {
const response = await fetch(
`${TRANSLATION_PLUGIN_API_BASE_URL}/v2/translate-text`,
{
method: "POST",
headers: {
// 'auth-token': TRANSLATION_PLUGIN_API_KEY,
"Content-Type": "application/json",
},
body: JSON.stringify(payload),
}
);
const data = await response.json();
return data;
} catch (error) {
console.error("Error translating text:", error);
return [];
}
}
// Function to recursively traverse DOM tree and get text nodes while skipping elements with "dont-translate" class
function getTextNodesToTranslate(rootNode) {
const translatableContent = [];
function isSkippableElement(node) {
return (
node.nodeType === Node.ELEMENT_NODE &&
(node.classList.contains("dont-translate") ||
node.classList.contains("bhashini-skip-translation") ||
node.tagName === "SCRIPT" ||
node.tagName === "STYLE" ||
node.tagName === "NOSCRIPT")
);
}
function isNodeOrAncestorsSkippable(node, maxLevels = 5) {
let currentNode = node;
let level = 0;
while (currentNode && level < maxLevels) {
if (isSkippableElement(currentNode)) {
return true;
}
currentNode = currentNode.parentElement;
level++;
}
return false;
}
function traverseNode(node) {
// Handle the case when node is an array or object with node property
if (Array.isArray(node)) {
// Process each node in the array
node.forEach(item => {
// Check if it's an object with a node property (from nodesToTranslate structure)
if (item && typeof item === 'object' && item.node) {
traverseNode(item.node);
} else {
traverseNode(item);
}
});
return;
}
// Check for valid DOM node
if (!node || !node.nodeType) {
return;
}
// Skip the entire subtree if this node or any of its ancestors are skippable
if (isNodeOrAncestorsSkippable(node)) {
return;
}
// Process this node
if (node.nodeType === Node.TEXT_NODE) {
const text = node.textContent;
const isNumeric = /^[\d.]+$/.test(text);
if (text && !isIgnoredNode(node) && !isNumeric) {
translatableContent.push({
type: "text",
node: node,
content: text,
});
}
} else if (node.nodeType === Node.ELEMENT_NODE) {
if (node.hasAttribute("placeholder")) {
translatableContent.push({
type: "placeholder",
node: node,
content: node.getAttribute("placeholder"),
});
}
if (node.hasAttribute("title")) {
translatableContent.push({
type: "title",
node: node,
content: node.getAttribute("title"),
});
}
// Process all child nodes
for (let i = 0; i < node.childNodes.length; i++) {
traverseNode(node.childNodes[i]);
}
}
}
traverseNode(rootNode);
return translatableContent;
}
function isIgnoredNode(node) {
const emailRegex = /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,7}\b/;
const nonEnglishRegex = /^[^A-Za-z0-9]+$/;
const isValidGovtEmail = (email) => {
let normalizedEmail = email
.replace(/\[dot]/g, '.')
.replace(/\[at]/g, '@');
return emailRegex.test(normalizedEmail);
}
var onlyNewLinesOrWhiteSpaceRegex = /^[\n\s\r\t]*$/;
return (
node.parentNode &&
(node.parentNode.tagName === "STYLE" ||
node.parentNode.tagName === "SCRIPT" ||
node.parentNode.tagName === "NOSCRIPT" ||
node.parentNode.classList.contains("dont-translate") ||
node.parentNode.classList.contains("bhashini-skip-translation") ||
emailRegex.test(node.textContent) || isValidGovtEmail(node.textContent) ||
(((languageDetection !== "true") && pageSourceLanguage === "en") && nonEnglishRegex.test(node.textContent))) ||
onlyNewLinesOrWhiteSpaceRegex.test(node.textContent)
);
}
// Global instance
async function translateElementText(element, target_lang) {
const promises = [];
const textNodes = getTextNodesToTranslate(element);
if (textNodes.length > 0) {
const textContentArray = textNodes.map((node, index) => {
const id = `translation-${Date.now()}-${index}`;
// Store original text in session storage
if (node.parentNode) {
node.parentNode.setAttribute("data-translation-id", id);
}
return { text: node.content, id, node };
});
const textChunks = chunkArray(textContentArray, CHUNK_SIZE);
// Create an array to hold promises for each chunk translation
const textNodePromises = textChunks.map(async (chunk) => {
const texts = chunk.map(({ text }) => text);
// if (target_lang === "en") {
// return;
// }
const translatedTexts = await translateTextChunks(texts, target_lang);
chunk.forEach(({ node }, index) => {
const translatedText = translatedTexts[index].target || texts[index];
if (node.type === "text") {
node.node.nodeValue = translatedText;
}
if (node.type === "value") {
node.node.value = translatedText;
}
if (node.type === "placeholder") {
node.node.placeholder = translatedText;
}
if (node.type === "title") {
node.node.setAttribute("title", translatedText);
}
});
});
promises.push(textNodePromises);
await Promise.all(promises);
}
}
function selectLanguage(language) {
document.querySelector(".bhashini-dropdown-btn-text").textContent = language;
document.getElementById("bhashiniLanguageDropdown").classList.remove("show");
const selectedLang = supportedTargetLangArr.find(
(lang) => lang.label === language
);
if (selectedLang) {
onDropdownChange({ target: { value: selectedLang.code } });
}
}
window.onclick = function (event) {
if (!event.target.matches(".bhashini-dropdown-btn")) {
var dropdowns = document.getElementsByClassName(
"bhashini-dropdown-content"
);
for (var i = 0; i < dropdowns.length; i++) {
var openDropdown = dropdowns[i];
if (openDropdown.classList.contains('show')) {
openDropdown.classList.remove('show');
}
}
}
}
const handleCloseFeedbackModal = () => {
const feedbackModal = document.querySelector('.bhashini-feedback-modal');
feedbackModal.style.display = "none";
const feedbackTextArea = document.querySelector('.feedback-textarea')
feedbackTextArea.style.display = "none";
selectedRating = 0;
document.querySelectorAll('.star').forEach(star => {
star.classList.remove('selected');
}
)
const suggestedResponseCheckbox = document.getElementById('suggested-feedback-checkbox');
suggestedResponseCheckbox.checked = false;
const suggestedFeedbackContainer = document.querySelector('.suggested-feedback-container');
suggestedFeedbackContainer.style.display = "none";
}
const handleFeedbackSubmission = async (rating, feedback, suggestedResponse) => {
if (!rating) {
showToast("Please provide rating")
return
}
if (rating <= 3 && !feedback) {
showToast("Please describe your issue")
return
}
const suggestedResponseCheckbox = document.getElementById('suggested-feedback-checkbox');
if (suggestedResponseCheckbox.checked && !suggestedResponse) {
showToast("Please provide suggested response")
return
}
const submitButton = document.querySelector('.submit-feedback');
submitButton.disabled = true;
submitButton.textContent = "Submitting...";
const payload = {
"feedbackTimeStamp": Math.floor(new Date().getTime() / 1000),
"feedbackLanguage": "en",
"pipelineInput": {
"pipelineTasks": [
{
"taskType": "translation",
"config": {
"language": {
"sourceLanguage": "en",
"targetLanguage": selectLanguage
},
"serviceId": "ai4bharat/indictrans-v2-all-gpu--t4/btp"
}
}
],
"inputData": {
"input": [
{
"source": ""
}
],
"audio": []
}
},
"pipelineOutput": {
"pipelineResponse": [
{
"taskType": "translation",
"config": null,
"output": [
{
"source": "",
"target": ""
}
],
"audio": null
}
]
},
"pipelineFeedback": {
"commonFeedback": [
{
"question": "Are you satisfied with the pipeline response",
"feedbackType": "rating",
"rating": rating
},
{
"question": "Describe your issue",
"feedbackType": "comment",
"comment": feedback
},
{
"question": "Suggested Response",
"feedbackType": "comment",
"comment": suggestedResponse
}
]
},
"feedbackSource": {
"application": "Bhashini Translation Plugin",
"website": window.location.href
}
}
try {
const res = await fetch(`${TRANSLATION_PLUGIN_API_BASE_URL}/v1/feedback`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(payload)
})
const data = await res.json();
showToast("Feedback Submitted Successfully")
submitButton.textContent = "Submit";
submitButton.disabled = false;
handleCloseFeedbackModal();
} catch (err) {
console.log(err)
showToast("Error submitting feedback. Please try again later")
}
}
const showFeedbackdiv = () => {
const feedbackdiv = document.querySelector('.bhashini-feedback-div');
feedbackdiv.style.visibility = 'visible';
};
const hideFeedbackdiv = () => {
const feedbackdiv = document.querySelector('.bhashini-feedback-div');
feedbackdiv.style.visibility = 'hidden';
};
function processIframeContent(iframe, targetLang) {
try {
// Make sure we can access the iframe's content (same-origin check)
if (iframe.contentDocument && iframe.contentDocument.body) {
// Translate all text nodes within the iframe
translateElementText(iframe.contentDocument.body, targetLang);
// Translate placeholders within the iframe
const iframeInputs = iframe.contentDocument.querySelectorAll('input[placeholder], textarea[placeholder]');
const placeholders = Array.from(iframeInputs).map(input => input.placeholder);
if (placeholders.length > 0) {
translateTextChunks(placeholders, targetLang).then(translatedPlaceholders => {
iframeInputs.forEach((input, index) => {
input.placeholder = translatedPlaceholders[index].target || placeholders[index];
});
});
}
// Set up mutation observer for the iframe to handle dynamic content
const iframeObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
translateElementText(node, targetLang);
}
});
}
});
});
// Start observing the iframe's document
iframeObserver.observe(iframe.contentDocument.body, {
childList: true,
subtree: true
});
}
} catch (e) {
console.error("Error accessing iframe content:", e);
}
}
function translateSameOriginIframes(targetLang) {
// Find all iframes in the document
const iframes = document.querySelectorAll('iframe');
// Process each iframe
iframes.forEach(iframe => {
// Handle already loaded iframes
if (iframe.contentDocument && iframe.contentDocument.readyState === 'complete') {
processIframeContent(iframe, targetLang);
}
// Also set up a load event listener for iframes that haven't loaded yet
iframe.addEventListener('load', function () {
processIframeContent(iframe, targetLang);
});
});
}
document.addEventListener("DOMContentLoaded", function () {
// check if isSelectedLangEnglish is present in sessionStorage
const isSelectedLang = sessionStorage.getItem("selectedLang");
if (isSelectedLang) {
sessionStorage.removeItem("selectedLang");
defaultTranslatedLanguage = null;
}
/**
* Check if the defaultTranslatedLanguage is present and not equal to "en", then set the language to the defaultTranslatedLanguage
* Otherwise, set the language to the preferred language stored in localStorage
*/
const languageToUse =
defaultTranslatedLanguage && defaultTranslatedLanguage !== "en"
? defaultTranslatedLanguage
: localStorage.getItem("preferredLanguage") || initialPreferredLanguage;
// Create translation popup elements
const wrapperButton = document.createElement("div");
wrapperButton.setAttribute(
"class",
"dont-translate bhashini-skip-translation bhashini-dropdown"
);
wrapperButton.setAttribute("id", "bhashini-translation");
wrapperButton.setAttribute("title", "Translate this page!");
// wrapperButton.innerHTML = `
`;
wrapperButton.innerHTML = `