The Turkish İ broke my portfolio
My site said 'ADİDAS / NİKE', and I never typed that. On why uppercase is a language decision in CSS, the Turkey test, and the one-attribute fix.
While reviewing my own site, I found a typo I had never typed. The hero lists three proof points, and one of them is the brands I built AR lenses for. In the source it read Adidas / Nike. On the page it read ADİDAS / NİKE, with a dot over every capital I. The contact page had the same problem: LİNKEDIN.
Nobody wrote those dots. The browser added them, and it was right to, given what I had told it.
Uppercase is a language decision
The site's labels are written in normal case and styled with text-transform: uppercase. That transform is not a simple character swap: browsers apply the casing rules of the element's language. The Turkish version of the site declares <html lang="tr">, and Turkish has two distinct letter i's:
- dotted
ibecomes dotted capitalİ - dotless
ıbecomes plain capitalI
So for Turkish words, the transform is exactly what I want: "Deneyim" becomes DENEYİM, which is correct. But "Nike" is not a Turkish word, and the browser had no way of knowing that. LinkedIn is a nice detail: only the first i gained a dot, because the second one is already a capital I in the brand name.
JavaScript does the same thing
This is not just a CSS quirk. Any locale-aware casing behaves the same way:
"nike".toUpperCase(); // "NIKE"
"nike".toLocaleUpperCase("tr"); // "NİKE"
"TITLE".toLocaleLowerCase("tr"); // "tıtle"The last line is the classic version of this bug. Code that lowercases a keyword with the user's locale and then compares it to "title"quietly fails on Turkish machines. It happens often enough that it has a name, the "Turkey test": if your string handling survives a Turkish locale, it probably survives most others.
The fix: tell the browser which words are not Turkish
I did not want to remove the uppercase styling. It is part of the site's editorial look, and for Turkish text it is correct. The real problem was missing information, so the fix is to provide it. A foreign name inside Turkish content gets its own language:
<dt lang="en">Adidas / Nike</dt>
<span lang="en">LinkedIn</span>Now the browser applies English casing to those elements and Turkish casing everywhere else. There is a second benefit: screen readers use the same attribute to switch pronunciation, so "Nike" is no longer read with Turkish phonetics. My project titles already had a titleLang field for exactly this reason. The hero stats and the social links had simply slipped through.
Keeping it fixed
A bug like this comes back the next time someone adds a brand name, so I added a small end-to-end check. It reads the rendered text the way a visitor sees it, after CSS, and asserts that the hero shows ADIDAS / NIKE and the contact page shows LINKEDIN.
What I took away from it:
text-transformandtoLocaleUpperCasefollow the language, not just the letters.- Mark foreign names with
lang. It fixes casing and helps screen readers at the same time. - For identifiers and comparisons, use locale-independent methods or an explicit locale. Never rely on the user's default.
- Run the Turkey test on anything that handles text.
It was a two-character bug on a portfolio. But every product that ships in more than one market has its own version of the Turkish İ, and I would rather find it in my own code first.