Skip to content

Instantly share code, notes, and snippets.

@ajmas
Last active December 20, 2024 20:24
Show Gist options
  • Save ajmas/0330c14944beba6b2f291e5bda42a82b to your computer and use it in GitHub Desktop.
Save ajmas/0330c14944beba6b2f291e5bda42a82b to your computer and use it in GitHub Desktop.
Generates a normalised email address, with support for extended latin as input
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 };
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