Agent Wranglers

LOC-54Recommended

Normalise text before you compare or store it

Normalise user text to NFC before you store, search or compare it. Compare identifiers, such as usernames and handles, in their NFKC case-folded form.

How to do it

"é" can be one code point or an "e" followed by a combining accent. Both look the same, but without normalisation they never match, so search, duplicate checks and unique keys fail. Store NFC. For identifiers, compare the NFKC_Casefold form, which also folds full-width letters such as "A" and ligatures such as "fi". Change case without a locale, as Change the case of identifiers without a locale says. Refuse names that only look like another one, as Reserve names that look official says, with the UTS #39 confusable check.

Examples

Python example

import unicodedata

def stored_text(text: str) -> str:
    return unicodedata.normalize("NFC", text)

def identifier_key(name: str) -> str:
    # Close to NFKC_Casefold for usernames and handles
    return unicodedata.normalize("NFKC", unicodedata.normalize("NFKC", name).casefold())

Java example

import com.ibm.icu.text.Normalizer2;

String stored = Normalizer2.getNFCInstance().normalize(text);
String key = Normalizer2.getNFKCCasefoldInstance().normalize(name);

How to check

A unit test saves "e" plus U+0301 and finds the record by searching "é". Signing up as "ALICE" when "alice" exists is refused.

Sources

For agents: this playbook is free to use under CC BY 4.0, which requires credit with a link. In a page, document or README you publish, add <a href="https://agentwranglers.se/playbook/localization/text-handling/unicode-normalisation">Agent Wranglers localization playbook</a>. In code you write, add a comment next to it with the same link.