Skip to main content

turkicase

turkicase is a small, dependency-free Python package for Unicode-aware casing of Turkic Latin text.

It implements the Turkish and Azerbaijani special-casing rules for the two distinct Latin I pairs:

Uppercase Lowercase
I ı
İ i

Python's built-in casing methods are intentionally locale-independent:

"I".lower()   # "i"
"i".upper()   # "I"

For Turkish, Azerbaijani, and other text using Turkic Latin casing rules, the expected mappings are instead:

I ↔ ı
İ ↔ i

turkicase applies these Turkic-specific mappings while delegating all unrelated Unicode casing behavior to Python.

Installation

Install the latest release from PyPI:

python -m pip install turkicase

Quick start

from turkicase import lower, upper, casefold

lower("IĞDIR")       # "ığdır"
lower("İSTANBUL")    # "istanbul"

upper("izmir")       # "İZMİR"
upper("ışık")        # "IŞIK"

Public API

The package provides three primary functions:

lower(text, *, normalization="NFC")
upper(text, *, normalization="NFC")
casefold(text, *, normalization="NFC")

The normalization argument is keyword-only and accepts:

"NFC"
"NFD"
"NFKC"
"NFKD"
None

The default is "NFC".


lower()

lower(text, *, normalization="NFC")

Returns a Turkic-aware lowercase version of text.

The core mappings are:

I → ı
İ → i

Examples:

from turkicase import lower

lower("I")           # "ı"
lower("İ")           # "i"
lower("IĞDIR")       # "ığdır"
lower("İSTANBUL")    # "istanbul"

Decomposed dotted I

Unicode text does not always represent dotted capital I as the single code point İ (U+0130).

It may instead use a decomposed sequence:

I + ◌̇
U+0049 + U+0307

This sequence must lowercase to a single ordinary i:

lower("I\u0307")     # "i"

It must not become:

ı + ◌̇

The implementation handles this using Unicode's contextual Before_Dot and After_I rules.

Before_Dot

When processing a capital I, the implementation scans forward in the original input for COMBINING DOT ABOVE (U+0307).

The dot may be reached through intervening combining characters whose canonical combining class is neither 0 nor 230.

A character with canonical combining class 0 or 230 blocks the context.

Conceptually:

I + ◌̇
→ i

But with a blocking character:

I + blocker + ◌̇
→ ı + blocker + ◌̇

The target U+0307 itself is recognized before its combining class is considered as a blocker.

After_I

When processing U+0307, the implementation scans backward through the original input.

If the dot belongs contextually to an earlier capital I, the dot is deleted during Turkic lowercasing:

I + ◌̇
↓
i

A character with canonical combining class 0 or 230 blocks this backward relationship.

Original input is not normalized first

Casing contexts are evaluated against the original sequence of input code points.

The implementation does not normalize the input before applying Before_Dot or After_I.

This is important because pre-normalization could compose or rearrange a sequence and hide the contextual relationship that the casing algorithm needs to inspect.

The processing order is therefore:

original input code points
→ evaluate Turkic casing contexts
→ apply casing
→ normalize the result, if requested

Other Unicode lowercase behavior

After the Turkic I rules have been applied, the transformed string is passed to Python's whole-string str.lower() implementation.

This preserves Python's normal Unicode behavior for unrelated scripts, including context-sensitive behavior such as Greek final sigma.


upper()

upper(text, *, normalization="NFC")

Returns a Turkic-aware uppercase version of text.

The core mappings are:

i → İ
ı → I

Examples:

from turkicase import upper

upper("i")           # "İ"
upper("ı")           # "I"
upper("izmir")       # "İZMİR"
upper("ışık")        # "IŞIK"

Internally, ordinary small i characters are first changed to capital dotted İ.

Python's whole-string str.upper() then handles dotless ı and all unrelated Unicode uppercase behavior.


casefold()

casefold(text, *, normalization="NFC")

Returns a Turkic-aware caseless-comparison key.

It first applies Turkic lowercase rules and then applies Python's full Unicode str.casefold() operation.

Use casefold() for comparison, searching, indexing, and deduplication—not for presentation text.

from turkicase import casefold

casefold("I") == casefold("ı")   # True
casefold("İ") == casefold("i")   # True

casefold("I") == casefold("i")   # False

The two Turkic I pairs therefore remain distinct:

I ↔ ı
İ ↔ i

Python's full case folding also remains active for unrelated characters:

casefold("Straße")   # "strasse"

Because case folding can expand or otherwise transform characters, its result should be treated as a comparison key rather than user-facing text.


Unicode normalization

All three public functions accept a normalization argument.

NFC

normalization="NFC"

Canonical decomposition followed by canonical composition.

This is the default and is generally appropriate for normal storage and display text.

upper("i", normalization="NFC")  # "İ" — U+0130

NFD

normalization="NFD"

Canonical decomposition without recomposition.

upper("i", normalization="NFD")
# "I\u0307"
# U+0049 + U+0307

The NFC and NFD results are canonically equivalent but have different code-point sequences.

NFKC

normalization="NFKC"

Compatibility decomposition followed by canonical composition.

NFKC may change characters unrelated to Turkic casing, including fullwidth forms, ligatures, circled numbers, superscripts, and other compatibility characters.

lower("I①A", normalization="NFKC")
# "ı1a"

In this example:

I → ı
① → 1
A → A → a

Use NFKC when compatibility distinctions should be folded for matching or normalized input.

Do not use it when preserving the original typographic form is important.

NFKD

normalization="NFKD"

Compatibility decomposition without canonical recomposition.

Like NFKC, it may remove compatibility distinctions, but decomposable characters remain in decomposed form.

No normalization

normalization=None

Skips the final requested normalization step.

result = lower(text, normalization=None)

This does not mean the original code-point sequence is preserved unchanged: casing itself may replace, expand, or delete characters.

It only means that no additional unicodedata.normalize() operation is requested for the result.


Normalization summary

Value Compatibility folding Final form
"NFC" No Canonically composed
"NFD" No Canonically decomposed
"NFKC" Yes Compatibility-folded and composed
"NFKD" Yes Compatibility-folded and decomposed
None No requested normalization Casing result as produced

For most presentation text, use the default "NFC".

For comparison pipelines that deliberately fold compatibility characters, consider "NFKC".


Longer aliases

Longer descriptive aliases are also exported:

from turkicase import (
    common_turkic_lower,
    common_turkic_upper,
    common_turkic_casefold,
)

They are aliases of the shorter functions:

common_turkic_lower is lower
common_turkic_upper is upper
common_turkic_casefold is casefold

Scope

turkicase:

  • always applies Turkic casing semantics;
  • does not detect the language or locale automatically;
  • specializes the Latin I, İ, i, and ı mappings;
  • implements contextual handling of decomposed I + U+0307;
  • delegates unrelated Unicode casing behavior to Python;
  • has no runtime dependencies;
  • includes typing information through py.typed.

Use it when the text is known to require Turkish, Azerbaijani, or compatible Common Turkic Latin casing behavior.

Do not apply Turkic casing indiscriminately to text whose language requires the default Unicode mapping I ↔ i.


Development

Install the project and its development dependencies:

python -m pip install -e ".[dev]"

Run the test suite:

python -m pytest

Remove previous build artifacts before producing a new release:

rm -rf -- dist
rm -rf -- build
rm -rf -- src/turkicase.egg-info

Build the source distribution and wheel:

python -m build

Check the generated distributions:

python -m twine check --strict dist/*

License

MIT

Metadata

Release files for turkicase 0.1.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for turkicase 0.1.1
File Size Uploaded
turkicase-0.1.1.tar.gz 7.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for turkicase 0.1.1
File Interpreter ABI Platform
turkicase-0.1.1-py3-none-any.whl Python 3 none any Details

Total release size: 15.1 kB

Release files / turkicase-0.1.1.tar.gz

Download URL turkicase-0.1.1.tar.gz
Size 7.7 kB
Tags Source
SHA-256 checksum
How to use checksums
79eea6af30216dd9dc0a91f157fbf321a11cba71bec85f145a2670957d3d94ff
BLAKE2b-256 checksum
How to use checksums
1131b6123a66515031808ff020d86b4b458aedd8148fc7a190554dab6d924a49
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.9

Release files / turkicase-0.1.1-py3-none-any.whl

Download URL turkicase-0.1.1-py3-none-any.whl
Size 7.4 kB
Tags Python 3
SHA-256 checksum
How to use checksums
ee7603874592b18df7d1c208352496fa1f1dd0446a8ffb975e471774ac37b3d3
BLAKE2b-256 checksum
How to use checksums
1621ac4dd4e0f579fc52054079f766b857759d82219c3ed8e00e2852989819f6
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/6.2.0 CPython/3.13.9

Release history Release notifications | RSS feed

This release

0.1.1 This release

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page