UTF-8 and Unicode

string.upper/lower only handle ASCII (locale-dependent). For full Unicode case mapping, normalization, and graphemes, use libraries like luautf8 (lua-utf8) or ICU bindings.

Lua 5.1/LuaJIT have no utf8 library. Use the luautf8 rock or a compat module.

-- Lua strings are byte sequences. The utf8 library (5.3+) handles UTF-8.
local s = "héllo wörld ❤"
print(#s)                          --> byte count (more than the character count)
print(utf8.len(s))                 --> character (code point) count
for pos, code in utf8.codes(s) do
    io.write(utf8.char(code), "(", code, ") ")
end
print()
print(utf8.char(72, 233, 10084))   --> Hé❤
print(utf8.codepoint(s, 1, -1))    -- all code points
print(utf8.offset(s, 3))           --> byte position of the 3rd character
print(s:sub(utf8.offset(s, 2), utf8.offset(s, 3) - 1))   --> é (substring by characters)
print(utf8.charpattern)            --> pattern matching one UTF-8 character
for ch in s:gmatch(utf8.charpattern) do io.write("[", ch, "]") end
print(utf8.len("\xff"))            --> nil  1 (invalid byte position)