ToolsGrid
File Privacy
String Theory & Unicode Standards

Unicode Case Folding, String Algorithms & Software Naming Conventions

Why simple character casing breaks in multi-lingual architectures, how Unicode NFKD normalization works, and why choosing the right identifier convention saves distributed systems from fatal data bugs.

6 min read • October 2026 • In-Memory Processing

Most developers assume that changing the case of a string is a trivial mathematical operation: subtract 32 from the character code of lowercase 'a' (97) to get uppercase 'A' (65). That works for 26 ASCII letters and nothing else.

Modern applications process global UTF-8 text. In that world naive casing produces failed logins, duplicate accounts, corrupted lookups and truncated data. This article covers the traps that actually cause incidents, and how to name things so that the data layers of a system stay consistent.

Need to transform sentences, titles, or code variables between camelCase, snake_case, and Title Case?
Read Step-by-Step Case Guide

01. The Infamous "Turkish i" Bug

Turkish and Azeri use two distinct letters for the i sound: dotted i / İ and dotless ı / I. Under Turkish rules:

  • lowercase dotted i uppercases to dotted capital İ (U+0130), not I;
  • lowercase dotless ı (U+0131) uppercases to I.

The bug appears when a library silently picks the machine's locale. In Java, "admin".toUpperCase() without an explicit locale gives "ADMİN" on a Turkish system, and C# ToUpper() behaves the same way with the current culture. A check like role.toUpperCase().equals("ADMIN") then fails only for Turkish users, and only in production.

JavaScript is safer here: toUpperCase() and toLowerCase() ignore the host locale, and only toLocaleUpperCase("tr") applies Turkish rules. The rule that follows is simple: use locale-independent conversions for identifiers, keys and protocol strings, and locale-aware conversions only for text shown to people.

// JavaScript
"title".toUpperCase()              // "TITLE"  (always)
"title".toLocaleUpperCase("tr")    // "TİTLE"  (explicit Turkish rules)
"I".toLocaleLowerCase("tr")        // "ı"      (dotless)

02. When Uppercase Changes the Length

Unicode case mappings are not always one character to one character. Three examples show up regularly:

  • German sharp s: "straße".toUpperCase() returns "STRASSE". One character became two.
  • Ligatures: the single character fi (U+FB01) uppercases to FI.
  • Greek final sigma: Σ lowercases to σ in the middle of a word but to ς at the end, so lowercasing depends on context.

If you store an uppercased value in a fixed-width column or a fixed-size buffer, a string that fit before conversion may be truncated or rejected after it. Always measure the converted string, not the original.

03. Case Folding: Comparing Strings Without Case

Lowercasing and case folding are different jobs. Lowercasing prepares text for display. Case folding, defined in the Unicode CaseFolding.txt data file, prepares text for caseless comparison. The difference is visible with the German example:

// JavaScript
"STRASSE".toLowerCase() === "straße".toLowerCase()   // false: "strasse" vs "straße"
// Full case folding maps both to "strasse", so they compare equal.

JavaScript has no built-in caseFold(). Practical options are to compare with Intl.Collator(undefined, { sensitivity: "base" }) for human-facing matching, or to normalize and lowercase consistently on both sides of every comparison for identifiers. The important habit is applying one canonical transformation everywhere a value is stored and looked up.

04. Normalization: One Character, Two Representations

The letter é can be a single code point (U+00E9) or an e followed by a combining acute accent (U+0065 U+0301). They look identical and compare as different strings:

// JavaScript
"\u00e9" === "e\u0301"                              // false
"\u00e9" === "e\u0301".normalize("NFC")               // true
"\uFB01".normalize("NFKD")                            // "fi"
"\uFF21".normalize("NFKD")                            // "A" (full-width A)
FormWhat it doesTypical use
NFCComposes sequences into single code pointsStoring and comparing user text
NFDDecomposes into base letter plus combining marksStripping accents, e.g. building URL slugs
NFKC / NFKDAlso folds compatibility characters (ligatures, full-width forms, circled digits)Search indexes and username uniqueness checks

For usernames and similar identifiers, a common safe pipeline is normalize (NFKC), then lowercase or case-fold, then compare. Skipping the first step lets two visually identical names coexist as different accounts.

05. Naming Conventions Across the Stack

Modern projects pass data across several environments, each with its own convention. Mixing them inside one layer is what causes mapping bugs:

Stack LayerStandard ConventionWhy It Was Chosen
SQL & Relational Databasessnake_caseMany databases fold unquoted identifiers to a single case, so underscores keep word boundaries safe
REST & GraphQL APIscamelCaseJavaScript object keys deserialize directly into frontend state
CSS & Web URLskebab-caseHyphens are legal in selectors and URL paths and are read as word separators by search engines
Classes & ComponentsPascalCaseMarks types and UI components in JavaScript, TypeScript, C# and React

Bridging these conventions needs a deterministic splitter that breaks words on uppercase transitions, digits and punctuation, then re-joins them in the target style. That is what the ToolsGrid Case Converter does, using locale-independent JavaScript conversions so the output is identical on every device. Its slug mode applies NFD and strips combining accents, so Café Menu becomes cafe-menu.

Case Converter

Convert Case Locally With ToolsGrid

Switch between Title Case, camelCase, PascalCase, snake_case, kebab-case and URL slugs in one click. Text is processed in your browser and never uploaded.

Frequently Asked Unicode Casing Questions

What is the Turkish 'i' problem in Unicode case conversion?

Turkish and Azeri have two distinct letters: dotted i/İ and dotless ı/I. In Turkish locale rules, lowercase i uppercases to İ (U+0130), not I. APIs that use the machine's default locale, such as Java's String.toUpperCase() or C#'s ToUpper(), can therefore turn "admin" into "ADMİN" and break identifier comparisons. JavaScript's toUpperCase() is locale-independent; only toLocaleUpperCase('tr') applies the Turkish mapping.

Why can lowercase to uppercase change the length of a string?

Case conversion is not a 1:1 character mapping in Unicode. For instance, the German lowercase sharp s ('ß') uppercases to two capital letters ('SS'), and the ligature 'fi' uppercases to 'FI'. The string grows, which can break fixed-width buffers, database column limits and index arithmetic.

What is the difference between lowercasing and case folding?

Lowercasing is a presentation operation. Case folding, defined by Unicode in CaseFolding.txt, is designed for caseless comparison: it maps 'ß' and 'SS' to the same form ('ss') where simple lowercasing does not. JavaScript has no built-in caseFold(), so comparisons that must be robust usually combine normalize() with toLowerCase() or use Intl.Collator with base sensitivity.

Does ToolsGrid's case converter use locale-specific rules?

No. It uses JavaScript's locale-independent toUpperCase() and toLowerCase(), so the result is the same on every device. Slug generation first decomposes text with NFD and removes combining accents, which turns 'café' into 'cafe'.

TG
Written by The ToolsGrid Developer Team

ToolsGrid engineers build private, client-side utilities for developers. We write about strings, encodings and web standards from the point of view of tools that run entirely in your browser.

Previous: Markdown AST & XSS

Explore Free Career & Document Utilities

Discover our full suite of fast, private, browser-based web tools

View All Tools