Last active
December 20, 2024 20:24
-
-
Save ajmas/0330c14944beba6b2f291e5bda42a82b to your computer and use it in GitHub Desktop.
Generates a normalised email address, with support for extended latin as input
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function removeAccents (text: string) { | |
return text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); | |
} | |
/** | |
* Converts a string, that may include extended latin characters | |
* and accents, to the more limited range supported by the English | |
* alphabet and 128 bit ASCII. | |
* | |
* There may be some disagreement in the character mapping, but we | |
* weren't able to find a good "go to" reference for this, to it is | |
* based on discussing with people. | |
* | |
* @params text the input text | |
* @return the flattened string | |
*/ | |
function flattenString (text: string) { | |
const extendedChar = ['ß', 'Œ', 'œ', 'æ', 'ø']; | |
const englishChar = ['ss', 'oe', 'oe', 'ae', 'oe']; | |
text = removeAccents('NFD'); | |
let newText = ''; | |
for (let i = 0; i < text.length; i++) { | |
const charIdx = extendedChar.indexOf(text.charAt(i)); | |
if (charIdx > -1) { | |
newText += englishChar[charIdx]; | |
} else { | |
newText += text.charAt(i); | |
} | |
} | |
return newText; | |
} | |
/** | |
* Checks to see if the input string is a | |
* valid latin name. | |
* | |
* The logic is based on eliminating characters | |
* that aren't in the 'a-z' range and then seeing | |
* if the string is not of zero length. | |
* | |
* @params text the input text | |
* @return true if the name is valid | |
*/ | |
function isValidLatinName (name: string) { | |
name = removeAccents(name); | |
name = name.replace(/[^a-z]+/gui, '') | |
return name.length > 0; | |
} | |
/** | |
* Creates an email address, based on normalising first name and last name, and | |
* then joing the domain name. Support for normalising extended-latin, in the english | |
* language subset, is also included. | |
* | |
* Output format: | |
* ``` | |
* <first character of first name>.<last name>@<domain name> | |
* ``` | |
* | |
* Notes: | |
* - If the firstname is hyphenated, then the first name initial include each of | |
* hyphenated characters of the parts. For example: 'Marie-Claire' beceomes 'mc'. | |
* - In the case of a name clash, use the 'namePartSuffix' to provide a differentiator | |
* - This does not support names that do not contain latin characters. These will | |
* need to be transliterated before hand. | |
* - Domain names are used 'as is', without modification. | |
* | |
* @param firstName the person's first name | |
* @param lastName the person's last name | |
* @param domain the name of the domain | |
* @param namePartSuffix an optional suffix to be provided in the case of geneated | |
* address clash. | |
* @return the flattened string | |
*/ | |
function createEmailAddress (firstName: string, lastName: string, domain: string, namePartSuffix: string = '') { | |
if (!isValidLatinName(firstName) || !isValidLatinName(lastName)) { | |
throw new Error('First name or last name did not have any valid latin characters'); | |
} | |
firstName = firstName.trim(); | |
firstName = firstName.toLowerCase(); | |
firstName = flattenString(firstName); | |
if (firstName.indexOf('-') > 0) { | |
const parts = firstName.split('-'); | |
firstName = ''; | |
parts.forEach(part => firstName += part.charAt(0)); | |
} else { | |
firstName = firstName.charAt(0); | |
} | |
firstName = firstName.replace(/[^a-z]+/gui, ''); | |
lastName = lastName.trim(); | |
lastName = lastName.toLowerCase(); | |
lastName = flattenString(lastName); | |
lastName = lastName.replace(/[^a-z]+/gui, ''); | |
return `${firstName}.${lastName},${namePartSuffix}@${domain.toLowerCase()}`; | |
} | |
export { createEmailAddress }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { createEmailAddress } from 'src/utils/EmailAddressUtils.ts'; | |
const domain = 'mytestdomain.com'; | |
const testData: string[][] = [ | |
['Albert', 'Einstein', '[email protected]'], | |
['Jean-Marc', 'Chapeaux', '[email protected]'], | |
['Marc-André', 'Éléphant', '[email protected]'], | |
['Jager', 'Mozart-Shito', '[email protected]'], | |
['Édourd', 'Mazimbi!', '[email protected]'], | |
['士郎', '正宗', 'error'], | |
['Øyvind', 'Johansen', '[email protected]'], | |
['Aabjørn', 'Andersen', '[email protected]'], | |
['Adelbjørn', 'Jensen', '[email protected]'], | |
['Ásgerð', 'Abrahamsson', '[email protected]'], | |
['Auðbjörg', 'Berg', '[email protected]'], | |
['Emmý', 'Larsen', '[email protected]'], | |
['Aabjørn', 'Karlsson', '[email protected]'], | |
['Sælaug', 'Hämäläinen', '[email protected]'], | |
['Sæmann', 'Korhonen', '[email protected]'], | |
['Salvør', 'Mäkelä', '[email protected]'], | |
['Lara', 'Sælaugson', '[email protected]'], | |
] | |
let results: Record<string, unknown>[] = []; | |
for (let i = 0; i < testData.length; i++) { | |
const resultObject: Record<string, unknown> = { | |
firstName: testData[i][0], | |
lastName: testData[i][1], | |
expectedResult: testData[i][2], | |
}; | |
try { | |
const result = generateEmailAddress(testData[i][0], testData[i][1], domain); | |
resultObject.actualResult = result; | |
resultObject.success = result === testData[i][2]; | |
} catch (error) { | |
if (testData[i][2] === 'error') { | |
resultObject.actualResult = `error: ${error?.message}`; | |
resultObject.success = testData[i][2] === 'error'; | |
} else { | |
throw error; | |
} | |
} | |
results.push(resultObject); | |
} | |
console.table(results, ['firstName', 'lastName', 'expectedResult', 'actualResult', 'success']); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment