UNPKG

textconvert

Version:

Public library to convert text into many conventions and formats.

1,673 lines (1,662 loc) 41.8 kB
/** * Make a regex list to split words based on these patterns */ const regex = { values: { upperCaseKeepLetter: /(?=[A-Z])/g, nonAlphabetic: /[^A-Za-z]/g, nonAlphaTest: /[^A-Za-z]/, punctuation: /[!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]/g, }, }; const { values: values$1 } = regex; /** * Convert a string from any convention to Camel Case convention. * @param text A string to be converted to Camel Case. * @returns A string in camelCase convention. * @example * camelCase('hello world'); // 'helloWorld' */ function camelCase(text) { var _a; // Make sure there's an input if (!text) return 'Please provide a valid input text'; // Make an array of words after splitting them const wordsArray = text.split(values$1.nonAlphabetic); // Get the first word out of the array const firstWord = (_a = wordsArray.shift()) === null || _a === void 0 ? void 0 : _a.toLowerCase(); // convert the words to camelCase const cCaseArray = wordsArray.map((word) => { word = word.charAt(0).toUpperCase() + word.slice(1); return word; }); // Join the words and return them return firstWord + cCaseArray.join(''); } /** * Convert a string from any convention to Pascal Case convention. * @param text A string to be converted to Camel Case. * @returns A string in PascalCase convention. * @example * pascalCase('hello world'); // 'HelloWorld' */ function pascalCase(text) { // Make sure there's an input if (!text) return 'Please provide a valid input text'; // Make an array of words after splitting them const wordsArray = text.split(values$1.nonAlphabetic); // convert the words to camelCase const pCaseArray = wordsArray.map((word) => { word = word.charAt(0).toUpperCase() + word.slice(1); return word; }); // Join the words and return them return pCaseArray.join(''); } /** * Convert a string from any convention to Snake Case convention. * @param text A string to be converted to Snake Case. * @returns A string in snake_case convention. * @example * snakeCase('hello world'); // 'hello_world' */ function snakeCase(text) { // Make sure there's an input if (!text) return 'Please provide a valid input text'; // Make an array of words after splitting them depending on the input case const wordsArray = values$1.nonAlphaTest.test(text) ? text.split(values$1.nonAlphabetic) : text.split(values$1.upperCaseKeepLetter); // Filter the words to 1 letter minimum length and convert the words to lowerCase const sCaseArray = wordsArray .filter((word) => word.length > 0) .map((word) => { word = word.charAt(0).toLowerCase() + word.slice(1); return word; }); // Join the words with "_" and return them return sCaseArray.join('_'); } /** * Convert a string from any convention to Kebab Case convention. * @param text A string to be converted to Kebab Case. * @returns A string in kebab-case convention. * @example * kebabCase('hello world'); // 'hello-world' */ function kebabCase(text) { // Make sure there's an input if (!text) return 'Please provide a valid input text'; // Make an array of words after splitting them depending on the input case const wordsArray = values$1.nonAlphaTest.test(text) ? text.split(values$1.nonAlphabetic) : text.split(values$1.upperCaseKeepLetter); // Filter the words to 1 letter minimum length and convert the words to lowerCase const kCaseArray = wordsArray .filter((word) => word.length > 0) .map((word) => { word = word.charAt(0).toLowerCase() + word.slice(1); return word; }); // Join the words with "-" and return them return kCaseArray.join('-'); } /** * Supported languages for detection */ var Language; (function (Language) { Language["English"] = "english"; Language["French"] = "french"; Language["Spanish"] = "spanish"; Language["German"] = "german"; Language["Italian"] = "italian"; Language["Portuguese"] = "portuguese"; Language["Dutch"] = "dutch"; Language["Unknown"] = "unknown"; })(Language || (Language = {})); // Language profiles (character frequency data) const languageProfiles = { [Language.English]: { e: 12.7, t: 9.1, a: 8.2, o: 7.5, i: 7.0, n: 6.7, s: 6.3, h: 6.1, r: 6.0, d: 4.3, l: 4.0, u: 2.8, c: 2.8, m: 2.4, w: 2.4, f: 2.2, g: 2.0, y: 2.0, p: 1.9, b: 1.5, v: 1.0, k: 0.8, j: 0.2, x: 0.2, q: 0.1, z: 0.1, th: 3.0, he: 2.5, in: 2.0, er: 1.8, an: 1.6, re: 1.4, on: 1.3, at: 1.2, }, [Language.French]: { e: 14.7, a: 8.0, i: 7.5, s: 7.9, n: 7.1, t: 7.0, r: 6.5, l: 5.5, u: 6.0, o: 5.3, d: 3.5, c: 3.0, m: 2.6, p: 2.5, v: 1.6, h: 0.8, g: 1.0, f: 1.0, b: 0.9, q: 1.3, j: 0.3, z: 0.1, x: 0.4, k: 0.0, w: 0.0, y: 0.3, é: 1.9, è: 0.3, ê: 0.2, à: 0.5, â: 0.1, ç: 0.3, ai: 1.0, oi: 0.4, ou: 0.9, }, [Language.Spanish]: { e: 13.7, a: 12.5, o: 8.7, s: 7.8, n: 7.0, r: 6.4, i: 6.2, l: 5.8, d: 5.8, t: 4.6, c: 4.0, u: 3.9, m: 3.1, p: 2.5, b: 1.4, g: 1.0, v: 1.0, f: 0.7, h: 0.7, q: 0.9, j: 0.4, z: 0.5, ñ: 0.3, y: 1.0, x: 0.2, k: 0.0, w: 0.0, á: 0.5, é: 0.4, í: 0.4, ó: 0.9, ú: 0.4, qu: 0.8, ue: 0.6, ch: 0.4, }, [Language.German]: { e: 16.9, n: 10.0, i: 7.6, s: 7.3, r: 7.0, t: 6.1, a: 6.5, d: 5.1, h: 4.8, u: 4.4, l: 3.4, c: 2.7, g: 3.0, m: 2.5, o: 2.5, b: 1.9, w: 1.9, f: 1.7, k: 1.4, z: 1.1, v: 0.7, p: 0.7, j: 0.3, y: 0.1, q: 0.02, x: 0.03, ä: 0.5, ö: 0.3, ü: 0.6, ß: 0.3, ch: 2.6, ei: 1.8, en: 3.8, er: 3.6, ie: 1.7, }, [Language.Italian]: { e: 11.8, a: 11.7, i: 10.1, o: 9.8, n: 6.9, t: 5.6, r: 6.4, l: 6.3, s: 5.0, c: 4.5, d: 3.7, p: 3.0, u: 3.0, m: 2.5, v: 2.1, g: 1.6, h: 1.5, b: 0.9, f: 0.9, z: 0.5, q: 0.5, à: 0.6, è: 0.4, ì: 0.1, ò: 0.2, ù: 0.1, ch: 0.5, di: 0.9, la: 0.7, re: 0.6, }, [Language.Portuguese]: { a: 14.6, e: 12.6, o: 10.7, s: 7.8, r: 6.5, i: 6.2, n: 5.0, t: 4.7, d: 4.9, m: 4.7, u: 4.0, c: 3.7, l: 2.8, p: 2.5, v: 1.7, g: 1.3, b: 1.0, f: 1.0, h: 0.8, q: 1.2, ã: 0.7, á: 0.5, é: 0.4, ó: 0.4, ç: 0.5, õ: 0.3, j: 0.3, z: 0.5, ê: 0.1, â: 0.1, qu: 0.8, de: 0.7, es: 0.7, }, [Language.Dutch]: { e: 19.0, n: 10.0, a: 7.5, t: 6.5, i: 6.5, r: 6.0, o: 6.0, d: 5.8, s: 3.7, l: 3.5, g: 3.4, v: 2.8, h: 2.4, k: 2.2, m: 2.2, u: 2.0, j: 1.5, w: 1.5, z: 1.4, p: 1.2, b: 1.6, c: 1.2, f: 0.8, y: 0.0, x: 0.0, q: 0.0, ij: 1.0, en: 3.5, de: 3.0, er: 2.0, ee: 1.8, oo: 1.0, }, [Language.Unknown]: {}, }; // Common stopwords for each language const stopwords = { [Language.English]: [ 'the', 'be', 'to', 'of', 'and', 'a', 'in', 'that', 'have', 'i', 'it', 'for', 'not', 'on', 'with', 'he', 'as', 'you', 'do', 'at', 'this', 'but', 'his', 'by', 'from', 'they', 'we', 'say', 'her', 'she', 'or', 'an', 'will', 'my', 'one', 'all', 'would', 'there', 'their', 'what', 'so', 'up', 'out', 'if', 'about', 'who', 'get', 'which', 'go', 'me', 'when', 'make', 'can', 'like', 'time', 'no', 'just', 'him', 'know', 'take', 'people', 'into', 'year', 'your', 'good', 'some', 'could', 'them', 'see', 'other', 'than', 'then', 'now', 'look', 'only', 'come', 'its', 'over', 'think', 'also', 'back', 'after', 'use', 'two', 'how', 'our', 'work', 'first', 'well', 'way', 'even', 'new', 'want', 'because', 'any', 'these', 'give', 'day', 'most', 'us', 'is', 'am', 'are', 'was', 'were', 'been', 'has', 'had', 'would', 'should', 'could', 'hello', 'world', ], [Language.French]: [ 'le', 'la', 'les', 'un', 'une', 'des', 'et', 'est', 'en', 'que', 'qui', 'dans', 'à', 'pour', 'pas', 'de', 'ce', 'il', 'elle', 'je', 'nous', 'vous', 'ils', 'elles', 'son', 'sa', 'ses', 'mon', 'ma', 'mes', 'ton', 'ta', 'tes', 'notre', 'votre', 'leur', 'leurs', 'du', 'au', 'aux', 'sur', 'sous', 'avec', 'sans', 'mais', 'ou', 'où', 'donc', 'car', 'comme', 'comment', 'quand', 'pourquoi', 'parce', 'plus', 'moins', 'très', 'trop', 'peu', 'aussi', 'bien', 'mal', 'si', 'tout', 'tous', 'toute', 'toutes', 'autre', 'autres', 'même', 'ici', 'là', 'cela', 'ceci', 'celui', 'celle', 'ceux', 'celles', 'rien', 'personne', 'quelque', 'quelques', 'plusieurs', 'beaucoup', 'être', 'avoir', 'faire', 'dire', 'venir', 'voir', 'vouloir', 'pouvoir', 'bonjour', 'monde', ], [Language.Spanish]: [ 'el', 'la', 'los', 'las', 'un', 'una', 'unos', 'unas', 'y', 'o', 'que', 'en', 'de', 'a', 'por', 'con', 'no', 'es', 'son', 'para', 'como', 'su', 'sus', 'mi', 'mis', 'tu', 'tus', 'nuestro', 'nuestra', 'nuestros', 'nuestras', 'vuestro', 'vuestra', 'vuestros', 'vuestras', 'del', 'al', 'este', 'esta', 'estos', 'estas', 'ese', 'esa', 'esos', 'esas', 'aquel', 'aquella', 'aquellos', 'aquellas', 'pero', 'más', 'menos', 'mucho', 'muchos', 'mucha', 'muchas', 'poco', 'pocos', 'poca', 'pocas', 'algún', 'alguna', 'algunos', 'algunas', 'ningún', 'ninguna', 'ningunos', 'ningunas', 'otro', 'otra', 'otros', 'otras', 'mismo', 'misma', 'mismos', 'mismas', 'tan', 'tanto', 'tanta', 'tantos', 'tantas', 'así', 'también', 'solo', 'solamente', 'hola', 'mundo', ], [Language.German]: [ 'der', 'die', 'das', 'ein', 'eine', 'einen', 'dem', 'den', 'des', 'einer', 'eines', 'und', 'ist', 'sind', 'war', 'waren', 'wird', 'werden', 'in', 'zu', 'mit', 'für', 'von', 'auf', 'als', 'um', 'an', 'aus', 'wie', 'bei', 'nach', 'bis', 'seit', 'vor', 'durch', 'über', 'unter', 'gegen', 'ohne', 'dass', 'weil', 'wenn', 'aber', 'oder', 'nur', 'noch', 'schon', 'auch', 'selbst', 'mir', 'dir', 'ihm', 'ihr', 'uns', 'euch', 'ihnen', 'mein', 'dein', 'sein', 'ihr', 'unser', 'euer', 'ihre', 'nicht', 'kein', 'keine', 'hallo', 'welt', ], [Language.Italian]: [ 'il', 'lo', 'la', 'i', 'gli', 'le', 'un', 'uno', 'una', 'e', 'è', 'che', 'di', 'a', 'per', 'in', 'con', 'su', 'non', 'sono', 'ho', 'mi', 'ha', 'si', 'ti', 'ci', 'vi', 'lo', 'la', 'li', 'le', 'ne', 'mio', 'mia', 'miei', 'mie', 'tuo', 'tua', 'tuoi', 'tue', 'suo', 'sua', 'suoi', 'sue', 'nostro', 'nostra', 'nostri', 'nostre', 'vostro', 'vostra', 'vostri', 'vostre', 'loro', 'questo', 'questa', 'questi', 'queste', 'quello', 'quella', 'quelli', 'quelle', 'ma', 'più', 'meno', 'molto', 'molti', 'molta', 'molte', 'poco', 'pochi', 'poca', 'poche', 'grande', 'grandi', 'piccolo', 'piccoli', 'piccola', 'piccole', 'anche', 'ciao', 'mondo', ], [Language.Portuguese]: [ 'o', 'a', 'os', 'as', 'um', 'uma', 'uns', 'umas', 'e', 'é', 'que', 'de', 'em', 'para', 'com', 'não', 'por', 'se', 'na', 'do', 'da', 'dos', 'das', 'no', 'nas', 'ao', 'aos', 'à', 'às', 'pelo', 'pela', 'pelos', 'pelas', 'este', 'esta', 'estes', 'estas', 'esse', 'essa', 'esses', 'essas', 'aquele', 'aquela', 'aqueles', 'aquelas', 'isto', 'isso', 'aquilo', 'meu', 'minha', 'meus', 'minhas', 'teu', 'tua', 'teus', 'tuas', 'seu', 'sua', 'seus', 'suas', 'nosso', 'nossa', 'nossos', 'nossas', 'vosso', 'vossa', 'vossos', 'vossas', 'dele', 'dela', 'deles', 'delas', 'nele', 'nela', 'neles', 'nelas', 'olá', 'mundo', ], [Language.Dutch]: [ 'de', 'het', 'een', 'is', 'en', 'van', 'in', 'te', 'dat', 'op', 'voor', 'niet', 'met', 'zijn', 'worden', 'deze', 'dit', 'door', 'er', 'ook', 'als', 'aan', 'maar', 'bij', 'nog', 'om', 'uit', 'zo', 'dan', 'over', 'na', 'toen', 'tot', 'werd', 'wel', 'nu', 'je', 'jij', 'jou', 'jouw', 'ik', 'mij', 'mijn', 'hij', 'hem', 'zijn', 'zij', 'haar', 'we', 'wij', 'ons', 'onze', 'jullie', 'hun', 'hen', 'hallo', 'wereld', ], [Language.Unknown]: [], }; // Common English words that should strongly bias toward English const commonEnglishWords = new Set([ 'hello', 'world', 'the', 'this', 'that', 'there', 'their', 'they', 'them', 'then', 'and', 'but', 'or', 'not', 'what', 'when', 'where', 'who', 'why', 'how', 'all', 'any', 'every', 'some', 'many', 'much', 'few', 'little', 'other', 'another', 'such', 'even', 'only', 'just', 'also', 'very', 'too', 'quite', 'rather', 'enough', ]); // Cache for previously analyzed texts const resultCache = new Map(); // Normalizes a language profile for cosine similarity const normalizedProfiles = Object.entries(languageProfiles).reduce((acc, [lang, profile]) => { if (lang !== Language.Unknown) { // Calculate magnitude for normalization const entries = Object.entries(profile); const magnitude = Math.sqrt(entries.reduce((sum, [, value]) => sum + value * value, 0)); // Create normalized map const normalizedMap = new Map(); entries.forEach(([char, value]) => { normalizedMap.set(char, value / magnitude); }); acc[lang] = normalizedMap; } else { acc[Language.Unknown] = new Map(); } return acc; }, {}); // Handle very common language expressions directly const commonPhrases = new Map([ // English phrases ['hello world', Language.English], ['hello', Language.English], ['hi there', Language.English], ['good morning', Language.English], ['good evening', Language.English], ['good night', Language.English], ['thank you', Language.English], ['how are you', Language.English], ['nice to meet you', Language.English], // Spanish phrases ['hola mundo', Language.Spanish], ['hola', Language.Spanish], ['buenos días', Language.Spanish], ['buenas tardes', Language.Spanish], ['buenas noches', Language.Spanish], ['gracias', Language.Spanish], ['cómo estás', Language.Spanish], ['mucho gusto', Language.Spanish], // German phrases ['hallo welt', Language.German], ['hallo', Language.German], ['guten morgen', Language.German], ['guten tag', Language.German], ['guten abend', Language.German], ['danke', Language.German], ['wie geht es dir', Language.German], // French phrases ['bonjour le monde', Language.French], ['bonjour', Language.French], ['bonsoir', Language.French], ['merci', Language.French], ['comment allez-vous', Language.French], ['enchanté', Language.French], // Italian phrases ['ciao mondo', Language.Italian], ['ciao', Language.Italian], ['buongiorno', Language.Italian], ['buonasera', Language.Italian], ['grazie', Language.Italian], ['come stai', Language.Italian], ['piacere', Language.Italian], // Portuguese phrases ['olá mundo', Language.Portuguese], ['olá', Language.Portuguese], ['bom dia', Language.Portuguese], ['boa tarde', Language.Portuguese], ['boa noite', Language.Portuguese], ['obrigado', Language.Portuguese], ['como vai', Language.Portuguese], ['prazer em conhecê-lo', Language.Portuguese], // Dutch phrases ['hallo wereld', Language.Dutch], ['hallo', Language.Dutch], ['goedemorgen', Language.Dutch], ['goedemiddag', Language.Dutch], ['goedenavond', Language.Dutch], ['dank je', Language.Dutch], ['hoe gaat het', Language.Dutch], ]); // Special handling for languages with unique characters const uniqueChars = { [Language.German]: ['ä', 'ö', 'ü', 'ß'], [Language.French]: ['é', 'è', 'ê', 'ë', 'à', 'â', 'ù', 'û', 'î', 'ï', 'ô', 'œ', 'ç'], [Language.Spanish]: ['ñ', 'á', 'é', 'í', 'ó', 'ú', 'ü', '¿', '¡'], [Language.Italian]: ['à', 'è', 'é', 'ì', 'í', 'î', 'ò', 'ó', 'ù', 'ú'], [Language.Portuguese]: ['ã', 'õ', 'á', 'à', 'â', 'é', 'ê', 'í', 'ó', 'ô', 'ú', 'ç'], }; /** * Detects the most likely language of a given text * * @param text The text to analyze * @param minLength Minimum text length for reliable detection (default: 4) * @param options Additional options for detection * @returns Language detection result with confidence score * @example * detectLanguage('Bonjour le monde'); // { language: 'French', ... } */ function detectLanguage(text, minLength = 4, options = { maxCharsToAnalyze: 500, useCache: true, }) { const { maxCharsToAnalyze = 500, useCache = true } = options; // Caching shortcut if (useCache && resultCache.has(text)) { return resultCache.get(text); } // Empty string case if (!text || text.trim().length === 0) { return { language: Language.Unknown, confidence: 0, scores: Object.fromEntries(Object.values(Language).map((l) => [l, l === Language.Unknown ? 1 : 0])), }; } const lowerText = text.toLowerCase().trim(); // Phrase shortcut (exact match) if (commonPhrases.has(lowerText)) { const detected = commonPhrases.get(lowerText); const result = { language: detected, confidence: 0.95, scores: Object.fromEntries(Object.values(Language).map((l) => [ l, l === detected ? 0.95 : 0.05 / (Object.values(Language).length - 1), ])), }; if (useCache) cacheResult(text, result); return result; } const isShort = text.length < minLength; const analyzedText = text.slice(0, maxCharsToAnalyze).toLowerCase().replace(/[0-9]/g, ''); const charCount = new Map(); let totalChars = 0; const bigramSet = new Set([ 'th', 'he', 'in', 'er', 'an', 'en', 'ch', 'de', 'ei', 'te', 'st', 'le', 'ou', 'qu', 'je', 'ai', 'ui', 'ie', 're', 'oo', ]); for (let i = 0; i < analyzedText.length; i++) { const char = analyzedText[i]; if (/[a-zäöüßàáâéèêëïîìíñóòôùúûç]/i.test(char)) { charCount.set(char, (charCount.get(char) || 0) + 1); totalChars++; if (i < analyzedText.length - 1) { const bigram = analyzedText.substring(i, i + 2); if (bigramSet.has(bigram)) { charCount.set(bigram, (charCount.get(bigram) || 0) + 0.5); totalChars += 0.25; } } } } if (totalChars < 2) { return { language: Language.Unknown, confidence: 0.1, scores: Object.fromEntries(Object.values(Language).map((l) => [ l, l === Language.Unknown ? 0.9 : 0.1 / (Object.values(Language).length - 1), ])), }; } for (const [lang, chars] of Object.entries(uniqueChars)) { for (const char of chars) { if (analyzedText.includes(char)) { charCount.set('unique_' + lang, (charCount.get('unique_' + lang) || 0) + 18); totalChars += 3; } } } const words = analyzedText.split(/\s+/); // Skip gibberish check if any language keyword is present const languageKeywords = [ ['english', Language.English], ['deutsch', Language.German], ['german', Language.German], ['français', Language.French], ['french', Language.French], ['español', Language.Spanish], ['spanish', Language.Spanish], ['italiano', Language.Italian], ['italian', Language.Italian], ['português', Language.Portuguese], ['portuguese', Language.Portuguese], ['nederlands', Language.Dutch], ['dutch', Language.Dutch], ]; const hasKeyword = languageKeywords.some(([kw]) => analyzedText.includes(kw)); // Gibberish check: if no stopwords from any language and more than 2 words, return unknown if (!hasKeyword) { const stopwordHits = Object.values(Language) .filter((l) => l !== Language.Unknown) .some((lang) => words.some((word) => stopwords[lang].includes(word))); if (!stopwordHits && words.length > 2) { return { language: Language.Unknown, confidence: 0, scores: Object.fromEntries(Object.values(Language).map((l) => [l, l === Language.Unknown ? 1 : 0])), }; } } for (const lang of Object.values(Language).filter((l) => l !== Language.Unknown)) { for (const word of words) { if (stopwords[lang].includes(word)) { const boost = lang === Language.English && commonEnglishWords.has(word) ? 36 : 28; charCount.set(`stopword_${lang}`, (charCount.get(`stopword_${lang}`) || 0) + boost); totalChars += 3; } } } const keywords = [ ['english', Language.English], ['deutsch', Language.German], ['german', Language.German], ['français', Language.French], ['french', Language.French], ['español', Language.Spanish], ['spanish', Language.Spanish], ['italiano', Language.Italian], ['italian', Language.Italian], ['português', Language.Portuguese], ['portuguese', Language.Portuguese], ['nederlands', Language.Dutch], ['dutch', Language.Dutch], ]; for (const [kw, lang] of keywords) { if (analyzedText.includes(kw)) { charCount.set(`keyword_${lang}`, (charCount.get(`keyword_${lang}`) || 0) + 70); totalChars += 8; } } const freqMap = normalizeVector(charCount); const scores = {}; let maxScore = 0; let detected = Language.Unknown; for (const lang of Object.values(Language)) { if (lang === Language.Unknown) { scores[lang] = 0; continue; } const profile = normalizedProfiles[lang]; let score = 0; for (const [token, normFreq] of freqMap.entries()) { if (token.startsWith(`unique_${lang}`)) score += normFreq * 2; else if (token.startsWith(`stopword_${lang}`)) score += normFreq * 3; else if (token.startsWith(`keyword_${lang}`)) score += normFreq * 5; else if (profile.has(token)) score += normFreq * profile.get(token); } scores[lang] = score; if (score > maxScore) { maxScore = score; detected = lang; } } // Set gibberish threshold to 0.4 if (maxScore < 0.4) { detected = Language.Unknown; scores[Language.Unknown] = 1; } const sumScores = Object.values(scores).reduce((a, b) => a + b, 0); let confidence = sumScores > 0 ? maxScore / sumScores : 0; if (isShort) confidence *= 0.8; // For long texts, boost confidence if (analyzedText.length > 40) confidence = Math.min(confidence * 1.1, 1.0); confidence = confidence > 0.7 ? 0.95 : confidence > 0.55 ? 0.85 : confidence > 0.4 ? 0.7 : confidence; const result = { language: detected, confidence, scores, }; if (useCache) cacheResult(text, result); return result; } // --- UTILS --- function normalizeVector(map) { const magnitude = Math.sqrt([...map.values()].reduce((sum, val) => sum + val * val, 0)); const normMap = new Map(); for (const [key, val] of map.entries()) { normMap.set(key, val / magnitude); } return normMap; } function cacheResult(text, result) { if (resultCache.size >= 100) { const firstKey = resultCache.keys().next().value; if (typeof firstKey === 'string') { resultCache.delete(firstKey); } } resultCache.set(text, result); } const { values } = regex; /** * Clear punctuations from a string and replaces it with a whitespace character or returns an array of strings. * * @param text String input to clear from punctuation. * @returns Either a string or an array of strings cleared from punctuations based on the arguments passed. * @example * clear('Hello, world!'); // 'hello world' */ function clear(text) { // Make sure there's an input if (!text) return 'Please provide a valid input text'; // Create an array of words const wordsArray = text.split(values.punctuation); // Filter wordsArray and remove punctuations const clearWords = wordsArray.map((word) => { word = word.trim().toLowerCase().replace(values.punctuation, ''); return word; }); return clearWords.join(' '); } /** * Return a boolean value number of the letters in a string. * * @param text String input to get letters count from. * @param countNumbers boolean value to determine if numbers should be counted as letters. * * @returns Number of letters and numbers (if requested) in a string. * @example * count('Hello, world!'); // 10 * count('Hello0 world', true); // 11 */ function count(text, countNumbers = false) { // Check string length if (!text.length) return 0; // Create a temp number. let temp = 0; // Clear the string and split the letters into an array. const cleared = clear(text).split(''); // Loop through the letters array. cleared.forEach((letter) => { // Check if countNumbers is included if (!countNumbers) { // Count letters only if (!(/[a-z]/g.test(letter) || /[A-Z]/g.test(letter))) { return; } temp++; return; } // Count letters with numbers. if (!(/[a-z]/g.test(letter) || /[A-Z]/g.test(letter) || /[0-9]/g.test(letter))) return; temp++; }); // Return the number of letters. return temp; } /** * Counts the number of words in a string. * Words are defined as sequences of letters, numbers, and apostrophes separated by whitespace. * * @param text String input to count words from * @returns Number of words in the string * @example * countWords('Hello, world!'); // 2 */ function countWords(text) { if (!(text === null || text === void 0 ? void 0 : text.trim())) return 0; // If the text contains only punctuation, return 0 if (!/[a-zA-Z0-9]/.test(text)) return 0; return text .trim() .split(/\s+/) .filter((word) => word.length > 0 && /[a-zA-Z0-9]/.test(word)).length; } /** * Counts the number of sentences in a string. * Sentences are defined as sequences of text ending with ., !, or ? followed by whitespace or end of string. * Text without any sentence-ending punctuation is considered to be a single sentence. * * @param text String input to count sentences from * @returns Number of sentences in the string * @example * countSentences('Hello world! How are you?'); // 2 */ function countSentences(text) { if (!(text === null || text === void 0 ? void 0 : text.trim())) return 0; // If there's no sentence-ending punctuation but there is text, it's considered one sentence if (!/[.!?]/.test(text) && text.trim().length > 0) return 1; return text .trim() .split(/[.!?]+(?=\s|$)/) .filter((sentence) => sentence.trim().length > 0).length; } /** * Analyzes text and returns comprehensive statistics about it * * @param text The text to analyze * @param wordsPerMinute Reading speed in words per minute (default: 200) * @returns TextStatistics object with various metrics * @example * getTextStats('Hello world! This is a test.'); * // { * // characterCount: 28, * // characterCountNoSpaces: 24, * // letterCount: 20, * // alphanumericCount: 20, * // wordCount: 6, * // sentenceCount: 2, * // paragraphCount: 1, * // averageWordLength: 3.3, * // averageSentenceLength: 3, * // readingTimeSeconds: 2, * // readingTimeFormatted: '2 sec' * // } */ function getTextStats(text, wordsPerMinute = 200) { // Handle empty input if (!text || !text.trim()) { return { characterCount: 0, characterCountNoSpaces: 0, letterCount: 0, alphanumericCount: 0, wordCount: 0, sentenceCount: 0, paragraphCount: 0, averageWordLength: 0, averageSentenceLength: 0, readingTimeSeconds: 0, readingTimeFormatted: '0 sec', }; } // Basic counts const characterCount = text.length; const characterCountNoSpaces = text.replace(/\s/g, '').length; const letterCount = count(text); const alphanumericCount = count(text, true); const wordCount = countWords(text); const sentenceCount = countSentences(text); // Paragraph count (separated by 2+ newlines) const paragraphCount = text.split(/\n\s*\n/).filter((p) => p.trim().length > 0).length || 1; // Calculate averages - fix for more accurate calculation let totalWordLength = 0; const words = text.split(/\s+/).filter((word) => word.length > 0); for (const word of words) { // Count only alphanumeric characters in words totalWordLength += word.replace(/[^a-zA-Z0-9]/g, '').length; } const averageWordLength = wordCount > 0 ? Math.round((totalWordLength / wordCount) * 10) / 10 : 0; const averageSentenceLength = sentenceCount > 0 ? Math.round((wordCount / sentenceCount) * 10) / 10 : 0; // Calculate reading time const wordsPerSecond = wordsPerMinute / 60; const readingTimeSeconds = Math.round(wordCount / wordsPerSecond); // Format reading time const readingTimeFormatted = formatReadingTime(readingTimeSeconds); return { characterCount, characterCountNoSpaces, letterCount, alphanumericCount, wordCount, sentenceCount, paragraphCount, averageWordLength, averageSentenceLength, readingTimeSeconds, readingTimeFormatted, }; } /** * Formats reading time in seconds to a readable string * * @param seconds Total seconds * @returns Formatted string (e.g., "2 min 30 sec") */ function formatReadingTime(seconds) { if (seconds === 0) return '0 sec'; const minutes = Math.floor(seconds / 60); const remainingSeconds = seconds % 60; if (minutes === 0) { return `${remainingSeconds} sec`; } else if (remainingSeconds === 0) { return `${minutes} min`; } else { return `${minutes} min ${remainingSeconds} sec`; } } /** * Reverses all characters in a string. * @param text A string to reverse. * @returns Reversed string. * @example * reverse('Hello, world!'); // '!dlrow ,olleH' */ function reverse(text) { // return type is inferred // Make sure input is valid if (!text) return 'Please provide a valid input text'; // Split string into characters, reverse all of them and join them back together return text.split('').reverse().join(''); } /** * Returns an array of characters from the provided string. * @param text A string to spread. * @param clear Whether to clear punctuation from the text. Default is false. * @returns Array of characters or an error message. * @example * spread('Hello, world!'); // ['H', 'e', ...] * spread('Hello, world!', true); // ['H', 'e', ...] */ function spread(text, clear$1 = false) { // Check if clearing punctuation is necessary. if (clear$1) { text = clear(text); } // Make sure input is valid. if (typeof text !== 'string') { return 'Input text should be a string!'; } if (!text.trim()) { return 'Please provide a valid text input'; } // Spread string into characters and return them in an array. const characters = [...(text.charAt(0).toLocaleUpperCase() + text.slice(1)).replace(/\s/g, '')]; return characters; } /** * Validates if a string is a valid email address according to RFC 5322 standards. * * This function performs comprehensive validation including: * - Basic email structure (local part + @ + domain) * - Valid characters in local part and domain * - Domain must contain at least one dot * - No consecutive dots * - No trailing dots or hyphens * - Length limits (local part ≤ 64 chars, domain ≤ 255 chars) * - Non-ASCII character rejection * * @param text - The string to validate as an email address * @returns boolean indicating if the string is a valid email address * * @example * ```typescript * isEmail('user@example.com') // true * isEmail('user.name@example.com') // true * isEmail('user+tag@example.com') // true * isEmail('plainaddress') // false * isEmail('@example.com') // false * isEmail('user@') // false * ``` */ function isEmail(text) { if (!text || typeof text !== 'string') { return false; } const email = text.trim(); // Reject non-ASCII characters if (/[^\x20-\x7E]/.test(email)) { return false; } const emailRegex = /^(?!.*\.{2})[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9-]+(\.[a-zA-Z0-9-]+)*\.[a-zA-Z]{2,}$/; if (!emailRegex.test(email)) { return false; } const [localPart, domain] = email.split('@'); if (!localPart || !domain) return false; if (localPart.length > 64 || domain.length > 255) return false; if (domain.startsWith('-') || domain.endsWith('-')) return false; if (domain.startsWith('.') || domain.endsWith('.')) return false; return true; } // Create digits in words const numbers = 'zero one two three four five six seven eight nine ten eleven twelve thirteen fourteen fifteen sixteen seventeen eighteen nineteen'.split(' '); const tens = 'twenty thirty forty fifty sixty seventy eighty ninety'.split(' '); /** * Get any number below 100 million converted to words. * @param number Integer input to turn into text. * @returns A string of numbers converted to words. * @example * numbersToWords(12345); // 'twelve thousand three hundred and forty-five' */ function numbersToWords(number) { // Check if the number is less than 100 million if (number >= 100000000) { throw new Error('Please enter a number under 100 million!'); } // Check if the input is between 0-19 if (number < 20) return numbers[number]; // Create a digit variable const digit = number % 10; // Check if the input is between 20-99 if (number < 100) return tens[~~(number / 10) - 2] + (digit ? '-' + numbers[digit] : ''); // Check if the input is between 100 and 999 if (number < 1000) return (numbers[~~(number / 100)] + ' hundred' + (number % 100 == 0 ? '' : ' and ' + numbersToWords(number % 100))); // Check if the input is between 1,000 and 999,999 if (number < 1000000) { const thousands = ~~(number / 1000); const remainder = number % 1000; return (numbersToWords(thousands) + ' thousand' + (remainder != 0 ? ' ' + numbersToWords(remainder) : '')); } // Handle millions (1,000,000 to 99,999,999) const millions = ~~(number / 1000000); const remainder = number % 1000000; return (numbersToWords(millions) + ' million' + (remainder != 0 ? ' ' + numbersToWords(remainder) : '')); } export { Language, camelCase, clear, count, countSentences, countWords, detectLanguage, getTextStats, isEmail, kebabCase, numbersToWords, pascalCase, reverse, snakeCase, spread }; //# sourceMappingURL=textConvert.mjs.map