Skip to main content

Pure-Python Unicode-aware casing for Turkic Latin text

Project description

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

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

turkicase-0.1.1.tar.gz (7.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

turkicase-0.1.1-py3-none-any.whl (7.4 kB view details)

Uploaded Python 3

File details

Details for the file turkicase-0.1.1.tar.gz.

File metadata

  • Download URL: turkicase-0.1.1.tar.gz
  • Upload date:
  • Size: 7.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for turkicase-0.1.1.tar.gz
Algorithm Hash digest
SHA256 79eea6af30216dd9dc0a91f157fbf321a11cba71bec85f145a2670957d3d94ff
MD5 e91f3c1556c647c2cc763ecc2eb23b1c
BLAKE2b-256 1131b6123a66515031808ff020d86b4b458aedd8148fc7a190554dab6d924a49

See more details on using hashes here.

File details

Details for the file turkicase-0.1.1-py3-none-any.whl.

File metadata

  • Download URL: turkicase-0.1.1-py3-none-any.whl
  • Upload date:
  • Size: 7.4 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.9

File hashes

Hashes for turkicase-0.1.1-py3-none-any.whl
Algorithm Hash digest
SHA256 ee7603874592b18df7d1c208352496fa1f1dd0446a8ffb975e471774ac37b3d3
MD5 1f1739ad185069d82090e935f05f3ba8
BLAKE2b-256 1621ac4dd4e0f579fc52054079f766b857759d82219c3ed8e00e2852989819f6

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page