// i18n.js - Handles internationalization of HTML elements with French typography rules document.addEventListener('DOMContentLoaded', () => { // Detect if current locale is French const isFrench = chrome.i18n.getUILanguage().startsWith('fr'); // Function to apply French typography rules (non-breaking spaces before double punctuation) function applyFrenchTypography(text) { if (isFrench) { // Add non-breaking spaces before double punctuation // The Unicode character \u202F is a narrow non-breaking space which is the proper character to use in French typography return text .replace(/\s*([!?:;])/g, '\u202F$1') // For : ; ! ? .replace(/«\s*/g, '« ') // Opening quotation mark .replace(/\s*»/g, '\u202F»'); // Closing quotation mark } return text; } // Localize by replacing all elements with data-i18n attribute const elements = document.querySelectorAll('[data-i18n]'); elements.forEach((element) => { const key = element.getAttribute('data-i18n'); let message = chrome.i18n.getMessage(key); // Only replace if we have a translation if (message) { // Apply French typography rules if current locale is French message = applyFrenchTypography(message); if (element.tagName === 'INPUT' && element.type === 'placeholder') { element.placeholder = message; } else if (element.tagName === 'IMG') { element.alt = message; } else if ( element.tagName === 'TITLE' || element.tagName.startsWith('H') || element.tagName === 'SPAN' || element.tagName === 'P' || element.tagName === 'A' || element.tagName === 'BUTTON' || element.tagName === 'LABEL' ) { element.textContent = message; } else { // For other elements, try to set the text content try { element.textContent = message; } catch (e) { console.error( `Error setting text for element with data-i18n="${key}":`, e ); } } } }); // Also localize document title if needed const titleElement = document.querySelector('title'); if (titleElement && titleElement.hasAttribute('data-i18n')) { const key = titleElement.getAttribute('data-i18n'); let message = chrome.i18n.getMessage(key); if (message) { message = applyFrenchTypography(message); document.title = message; } } // Apply French typography to elements that might have been dynamically generated // or that don't have direct i18n translations (e.g. combined strings or status messages) function applyFrenchTypographyToDocument() { if (!isFrench) return; // Only apply to French locale // Walk through text nodes and replace them if necessary const textWalker = document.createTreeWalker( document.body, NodeFilter.SHOW_TEXT, null, false ); let node; while ((node = textWalker.nextNode())) { // Skip script and style elements if ( node.parentElement.tagName === 'SCRIPT' || node.parentElement.tagName === 'STYLE' ) { continue; } const originalText = node.nodeValue; const modifiedText = applyFrenchTypography(originalText); if (originalText !== modifiedText) { node.nodeValue = modifiedText; } } } // Apply French typography rules to the entire document applyFrenchTypographyToDocument(); // Apply rules again after dynamic content changes // Use MutationObserver to detect DOM changes const observer = new MutationObserver((mutations) => { // For performance reasons, we debounce the function call clearTimeout(window.typographyTimeout); window.typographyTimeout = setTimeout(applyFrenchTypographyToDocument, 100); }); // Start observing for dynamic content changes observer.observe(document.body, { childList: true, subtree: true, characterData: true, }); });