react-i18next
Version:
Internationalization for react done right. Using the i18next i18n ecosystem.
265 lines (250 loc) • 5.25 kB
JavaScript
/**
* Common HTML entities map for fast lookup
*/
const commonEntities = {
// Basic entities
' ': '\u00A0', // Non-breaking space
'&': '&',
'<': '<',
'>': '>',
'"': '"',
''': "'",
// Copyright, trademark, and registration
'©': '©',
'®': '®',
'™': '™',
// Punctuation
'…': '…',
'–': '–',
'—': '—',
'‘': '\u2018',
'’': '\u2019',
'‚': '\u201A',
'“': '\u201C',
'”': '\u201D',
'„': '\u201E',
'†': '†',
'‡': '‡',
'•': '•',
'′': '′',
'″': '″',
'‹': '‹',
'›': '›',
'§': '§',
'¶': '¶',
'·': '·',
// Spaces
' ': '\u2002',
' ': '\u2003',
' ': '\u2009',
// Currency
'€': '€',
'£': '£',
'¥': '¥',
'¢': '¢',
'¤': '¤',
// Math symbols
'×': '×',
'÷': '÷',
'−': '−',
'±': '±',
'≠': '≠',
'≤': '≤',
'≥': '≥',
'≈': '≈',
'≡': '≡',
'∞': '∞',
'∫': '∫',
'∑': '∑',
'∏': '∏',
'√': '√',
'∂': '∂',
'‰': '‰',
'°': '°',
'µ': 'µ',
// Arrows
'←': '←',
'↑': '↑',
'→': '→',
'↓': '↓',
'↔': '↔',
'↵': '↵',
'⇐': '⇐',
'⇑': '⇑',
'⇒': '⇒',
'⇓': '⇓',
'⇔': '⇔',
// Greek letters (lowercase)
'α': 'α',
'β': 'β',
'γ': 'γ',
'δ': 'δ',
'ε': 'ε',
'ζ': 'ζ',
'η': 'η',
'θ': 'θ',
'ι': 'ι',
'κ': 'κ',
'λ': 'λ',
'μ': 'μ',
'ν': 'ν',
'ξ': 'ξ',
'ο': 'ο',
'π': 'π',
'ρ': 'ρ',
'σ': 'σ',
'τ': 'τ',
'υ': 'υ',
'φ': 'φ',
'χ': 'χ',
'ψ': 'ψ',
'ω': 'ω',
// Greek letters (uppercase)
'Α': 'Α',
'Β': 'Β',
'Γ': 'Γ',
'Δ': 'Δ',
'Ε': 'Ε',
'Ζ': 'Ζ',
'Η': 'Η',
'Θ': 'Θ',
'Ι': 'Ι',
'Κ': 'Κ',
'Λ': 'Λ',
'Μ': 'Μ',
'Ν': 'Ν',
'Ξ': 'Ξ',
'Ο': 'Ο',
'Π': 'Π',
'Ρ': 'Ρ',
'Σ': 'Σ',
'Τ': 'Τ',
'Υ': 'Υ',
'Φ': 'Φ',
'Χ': 'Χ',
'Ψ': 'Ψ',
'Ω': 'Ω',
// Latin extended
'À': 'À',
'Á': 'Á',
'Â': 'Â',
'Ã': 'Ã',
'Ä': 'Ä',
'Å': 'Å',
'Æ': 'Æ',
'Ç': 'Ç',
'È': 'È',
'É': 'É',
'Ê': 'Ê',
'Ë': 'Ë',
'Ì': 'Ì',
'Í': 'Í',
'Î': 'Î',
'Ï': 'Ï',
'Ð': 'Ð',
'Ñ': 'Ñ',
'Ò': 'Ò',
'Ó': 'Ó',
'Ô': 'Ô',
'Õ': 'Õ',
'Ö': 'Ö',
'Ø': 'Ø',
'Ù': 'Ù',
'Ú': 'Ú',
'Û': 'Û',
'Ü': 'Ü',
'Ý': 'Ý',
'Þ': 'Þ',
'ß': 'ß',
'à': 'à',
'á': 'á',
'â': 'â',
'ã': 'ã',
'ä': 'ä',
'å': 'å',
'æ': 'æ',
'ç': 'ç',
'è': 'è',
'é': 'é',
'ê': 'ê',
'ë': 'ë',
'ì': 'ì',
'í': 'í',
'î': 'î',
'ï': 'ï',
'ð': 'ð',
'ñ': 'ñ',
'ò': 'ò',
'ó': 'ó',
'ô': 'ô',
'õ': 'õ',
'ö': 'ö',
'ø': 'ø',
'ù': 'ù',
'ú': 'ú',
'û': 'û',
'ü': 'ü',
'ý': 'ý',
'þ': 'þ',
'ÿ': 'ÿ',
// Special characters
'¡': '¡',
'¿': '¿',
'ƒ': 'ƒ',
'ˆ': 'ˆ',
'˜': '˜',
'Œ': 'Œ',
'œ': 'œ',
'Š': 'Š',
'š': 'š',
'Ÿ': 'Ÿ',
'ª': 'ª',
'º': 'º',
'¯': '¯',
'´': '´',
'¸': '¸',
'¹': '¹',
'²': '²',
'³': '³',
'¼': '¼',
'½': '½',
'¾': '¾',
// Card suits
'♠': '♠',
'♣': '♣',
'♥': '♥',
'♦': '♦',
// Miscellaneous
'◊': '◊',
'‾': '‾',
'⁄': '⁄',
'℘': '℘',
'ℑ': 'ℑ',
'ℜ': 'ℜ',
'ℵ': 'ℵ',
};
// Create regex pattern for all entities
const entityPattern = new RegExp(
Object.keys(commonEntities)
.map((entity) => entity.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|'),
'g',
);
/**
* Decode HTML entities in text
*
* Uses a hybrid approach:
* 1. First pass: decode common named entities using a map
* 2. Second pass: decode numeric entities (decimal and hexadecimal)
*
* @param {string} text - Text with HTML entities
* @returns {string} Decoded text
*/
export const decodeHtmlEntities = (text) =>
text
// First pass: common named entities
.replace(entityPattern, (match) => commonEntities[match])
// Second pass: numeric entities (decimal)
.replace(/&#(\d+);/g, (_, num) => String.fromCharCode(parseInt(num, 10)))
// Third pass: numeric entities (hexadecimal)
.replace(/&#x([0-9a-fA-F]+);/g, (_, hex) => String.fromCharCode(parseInt(hex, 16)));