NAME
AmberDB::Locale - Multilingual text processing, collation, number/currency formatting, and search normalization engine
SYNOPSIS
# =========================================================================
# 1. DIRECT USAGE VIA AMBERDB INSTANCE ($adb inherits AmberDB::Locale):
# Reads active language from config (default is 'tr' or configured language)
# =========================================================================
my $adb = AmberDB->new(cfg => { language => "tr" });
# Case Conversions & Comparison
my $upper = $adb->uc("ığdır"); # "IĞDIR"
my $lower = $adb->lc("İSTANBUL"); # "istanbul"
my $title = $adb->ucfirst("istanbul büyükşehir"); # "İstanbul Büyükşehir"
my $folded = $adb->fold("İSTANBUL"); # "istanbul"
my $same = $adb->ieq("İstanbul", "istanbul"); # 1
# Unicode Collation Algorithm (UCA) Sorting
my @sorted = $adb->sort(["İzmir", "Ankara", "Van", "Şanlıurfa", "Bursa", "Çanakkale"]);
# => ("Ankara", "Bursa", "Çanakkale", "İzmir", "Şanlıurfa", "Van")
# Text Normalization & Transliteration (Turkish rules: ü -> u, ç -> c)
my $clean = $adb->normalize("<p>Kâr & zarar çizelgesi</p>"); # "Kar zarar cizelgesi"
my $ascii = $adb->to_ascii("müller"); # "muller" (Turkish: ü -> u)
my $slug = $adb->to_ascii("İstanbul Kâr & Zarar!", 1); # "istanbul_kar_zarar"
# UTF-8 Safe Substring (character-based, safe for multibyte chars)
my $sub = $adb->substring("Çanakkale", 0, 4); # "Çana"
# Number to Written Text (Invoices / Cheques)
my $text = $adb->num2text(1234.56);
# => "Bin İki Yüz Otuz Dört TL Elli Altı KR" (Note: "Bin", not "Bir Bin")
# Number & Currency Formatting
my $num = $adb->format_number(1234567.89); # "1.234.567,89"
my $curr = $adb->format_currency(1234.50, "EUR"); # "1.234,50 €"
# Date Formatting & Parsing
my $date = $adb->format_date(time(), "full"); # "Cuma, 28 Ağustos 2026"
my $ep = $adb->parse_date("28.08.2026"); # Unix timestamp
# Pluralization (CLDR)
my $msg = $adb->plural(5, { one => "{count} ürün", other => "{count} ürün" });
# Search Token Normalization
my $norm = $adb->normalize_word("Türkiye'de", 1); # "turkiye turkiyede"
# =========================================================================
# 2. CROSS-LANGUAGE COMPARISON & STANDALONE USAGE:
# =========================================================================
use AmberDB::Locale;
my $tr = AmberDB::Locale->new(language => "tr");
my $de = AmberDB::Locale->new(language => "de");
my $en = AmberDB::Locale->new(language => "en");
my $fr = AmberDB::Locale->new(language => "fr");
my $ru = AmberDB::Locale->new(language => "ru");
# --- A. ASCII Transliteration Differences (to_ascii) ---
$tr->to_ascii("müller"); # "muller" (Turkish rule: ü -> u)
$de->to_ascii("müller"); # "mueller" (German DIN 5007 rule: ü -> ue)
$de->to_ascii("Große Straße"); # "Grosse Strasse" (ß -> ss)
$tr->to_ascii("çarşı"); # "carsi" (ç -> c, ş -> s, ı -> i)
$fr->to_ascii("façade Noël"); # "facade Noel"
# --- B. Case Conversion Differences (uc / lc) ---
$tr->uc("istanbul"); # "İSTANBUL" (Turkish dotted i -> İ)
$en->uc("istanbul"); # "ISTANBUL" (Standard English i -> I)
$tr->lc("IĞDIR"); # "ığdır" (Turkish dotless I -> ı)
$en->lc("IĞDIR"); # "iğdır" (Standard English I -> i)
$de->uc("weiß"); # "WEISS" (German ß -> SS)
# --- C. Number Formatting Differences (format_number) ---
$tr->format_number(1234567.89); # "1.234.567,89" (Group: dot, Dec: comma)
$de->format_number(1234567.89); # "1.234.567,89" (Group: dot, Dec: comma)
$en->format_number(1234567.89); # "1,234,567.89" (Group: comma, Dec: dot)
$fr->format_number(1234567.89); # "1 234 567,89" (Group: space, Dec: comma)
# --- D. Written Number Differences (num2text) ---
$tr->num2text(1000); # "Bin TL" (Turkish: "Bin", no "Bir" prefix)
$en->num2text(1000); # "One Thousand USD" (English: requires "One" prefix)
$de->num2text(1000); # "Eins Tausend EUR" (German: requires "Eins" prefix)
# --- E. Pluralization Differences (plural) ---
# English: 2 forms (one, other)
$en->plural(1, { one => "{count} item", other => "{count} items" }); # "1 item"
$en->plural(5, { one => "{count} item", other => "{count} items" }); # "5 items"
# Russian: 4 forms (one, few, many, other)
my %ru_apple = (
one => "{count} яблоко",
few => "{count} яблока",
many => "{count} яблок",
other => "{count} яблока"
);
$ru->plural(1, \%ru_apple); # "1 яблоко" (ends in 1, except 11)
$ru->plural(3, \%ru_apple); # "3 яблока" (ends in 2-4, except 12-14)
$ru->plural(5, \%ru_apple); # "5 яблок" (ends in 5-9, 0, or 11-14)
$ru->plural(21, \%ru_apple); # "21 яблоко"
DESCRIPTION
AmberDB::Locale is a comprehensive, high-performance, locale-aware text processing engine designed for multilingual Perl applications. It provides a unified API for:
Locale-aware case conversion (e.g., Turkish
I/ı,İ/i, Germanß -> SS).100% standard Unicode Collation Algorithm (UCA) sorting via
Unicode::Collate::Locale.ASCII transliteration and URL slug generation with language-specific rules (e.g. German DIN 5007-2
ü -> uevs. Turkishü -> u).Written number and cheque printing conversion supporting integer, decimal, negative numbers, and Eastern Arabic/Persian numerals.
Precision number and ISO 4217 currency formatting with customizable decimal and group separators.
Bidirectional date/time formatting with full pattern tokens (
YYYY,MMMM,dddd,HH:mm:ss) and parsing.Unicode NFKC case-folding and phonetic search token normalization for high-performance inverted indexes.
CLDR-standard plural form evaluation across Western, Slavic, and Eastern languages.
UTF-8 character-safe substring extraction preventing multi-byte corruption.
Language-specific datasets and rule tables are decoupled into modular packages (e.g. AmberDB::Locale::Lang::tr, AmberDB::Locale::Lang::de, AmberDB::Locale::Lang::en, AmberDB::Locale::Lang::ru, AmberDB::Locale::Lang::fr, AmberDB::Locale::Lang::es, AmberDB::Locale::Lang::az, AmberDB::Locale::Lang::ar, AmberDB::Locale::Lang::ja).
Inheritance Note: AmberDB inherits from AmberDB::Locale via use parent. When an AmberDB instance is constructed, it automatically initializes its locale subsystem from $adb->config('language'). All methods documented below can be called directly on $adb (e.g. $adb->format_currency(...)).
CONSTRUCTOR
new([%options | $hashref | $language_code])
Creates and returns an AmberDB::Locale instance configured for the specified language. Instances are cached internally for high-throughput reuse.
# 1. Named-parameter API (recommended)
my $lang = AmberDB::Locale->new(language => 'tr');
# 2. Hashref API
my $lang = AmberDB::Locale->new({ language => 'de' });
# 3. Positional string API
my $lang = AmberDB::Locale->new('fr');
# 4. Default (falls back to English 'en')
my $lang = AmberDB::Locale->new();
Supported language codes include "tr", "en", "de", "fr", "es", "ru", "az", "ar", "ja" and common aliases (such as "turkish", "tr_tr", "english", "german", etc.). If an unsupported language is specified, a warning is issued and the instance falls back to "en".
METHODS
Case Conversions & Comparison
uc($string)
Converts $string to uppercase according to locale-specific casing rules.
# Turkish dotted/dotless I handling:
$tr->uc("ığdır"); # "IĞDIR"
$tr->uc("istanbul"); # "İSTANBUL" (Turkish i -> İ)
# English standard casing:
$en->uc("istanbul"); # "ISTANBUL" (English i -> I)
# German sharp S:
$de->uc("straße"); # "STRASSE" (German ß -> SS)
lc($string)
Converts $string to lowercase according to locale-specific casing rules.
# Turkish dotted/dotless I handling:
$tr->lc("İSTANBUL"); # "istanbul" (Turkish İ -> i)
$tr->lc("IĞDIR"); # "ığdır" (Turkish I -> ı)
# English standard casing:
$en->lc("IĞDIR"); # "iğdır" (English I -> i)
ucfirst($string)
Capitalizes the first letter of each word in $string under locale rules. The string is first lowercased, and then the first character following word-starting delimiters (spaces, punctuation, brackets) is uppercased according to locale rules.
$tr->ucfirst("istanbul büyükşehir belediyesi");
# => "İstanbul Büyükşehir Belediyesi"
$tr->ucfirst("ahmet (ısparta) - izmir");
# => "Ahmet (Isparta) - İzmir"
fold($string)
Applies Unicode NFKC normalization and locale lowercasing to produce a case-folded string suitable for search indexing and matching.
my $key = $tr->fold("İSTANBUL"); # "istanbul"
ieq($str1, $str2)
Performs a locale-aware, case-insensitive comparison between $str1 and $str2. Returns 1 if they are equal under locale rules, 0 otherwise.
$tr->ieq("İstanbul", "istanbul"); # 1 (true)
$tr->ieq("IĞDIR", "ığdır"); # 1 (true)
$tr->ieq("Ankara", "İzmir"); # 0 (false)
Sorting
sort(\@list [, $field_or_index])
Sorts an array reference \@list using the Unicode Collation Algorithm (UCA) tailored for the active locale.
# 1. Simple array of strings (respects Turkish alphabetical ordering: Ç, Ğ, İ, Ö, Ş, Ü)
my @sorted = $tr->sort(["İzmir", "Ankara", "Van", "Şanlıurfa", "Bursa", "Çanakkale"]);
# => ("Ankara", "Bursa", "Çanakkale", "İzmir", "Şanlıurfa", "Van")
# 2. Array of hash references (sort by hash key)
my @products = (
{ id => 1, title => "Şemsiye" },
{ id => 2, title => "Ayna" },
{ id => 3, title => "Çanta" }
);
my @sorted_products = $tr->sort(\@products, "title");
# => ({ id => 2, title => "Ayna" }, { id => 3, title => "Çanta" }, { id => 1, title => "Şemsiye" })
# 3. Array of array references (sort by element column index)
my @rows = (
[ 101, "Van" ],
[ 102, "Adana" ],
[ 103, "Çorum" ]
);
my @sorted_rows = $tr->sort(\@rows, 1);
# => ([102, "Adana"], [103, "Çorum"], [101, "Van"])
Text Normalization & Transliteration
normalize($string)
Cleans and normalizes $string by decoding HTML entities, stripping HTML tags, mapping locale-specific accents (such as circumflex vowels â, î, û), filtering characters outside the locale's safe character set, and collapsing whitespace.
my $clean = $tr->normalize('<p>Kâr & zarar çizelgesi</p>');
# => "Kar zarar cizelgesi"
to_ascii($string [, $nonspace])
Transliterates localized text into plain ASCII characters according to per-language phonetic and transliteration conventions.
Turkish (
tr>):ç -> c,ğ -> g,ı/İ -> i,ö -> o,ş -> s,ü -> u$tr->to_ascii("müller"); # "muller" $tr->to_ascii("çarşı"); # "carsi"German (
de>, DIN 5007-2):ä -> ae,ö -> oe,ü -> ue,ß -> ss$de->to_ascii("müller"); # "mueller" $de->to_ascii("Große Straße"); # "Grosse Strasse"French (
fr>):$fr->to_ascii("façade Noël"); # "facade Noel"Spanish (
es>):$es->to_ascii("año niño"); # "ano nino"
If $nonspace = 1 (slug mode), the string is lowercased and spaces/punctuation are converted to single underscores:
$tr->to_ascii("İstanbul", 1); # "istanbul"
$tr->to_ascii("Kâr & Zarar Tablosu!", 1); # "kar_zarar_tablosu"
first_char($string)
Returns the normalized, uppercase first character of $string for alphabetical indexing (e.g. A-Z catalog directories). Returns "0-9" if the string begins with a digit.
$tr->first_char(" çarşı "); # "Ç"
$tr->first_char("123abc"); # "0-9"
$tr->first_char("İzmir"); # "İ"
UTF-8 Encoding & Slicing
utf_encode($string) / utf_decode($string)
Converts a Perl Unicode character string into raw UTF-8 octet bytes (utf_encode) or decodes raw UTF-8 bytes into Perl Unicode characters (utf_decode). Safe against double-encoding.
my $bytes = $lang->utf_encode($unicode_str);
my $chars = $lang->utf_decode($raw_bytes);
substring($string, [$offset], $length)
Extracts a substring from $string based on character count rather than byte count. Prevents cutting multibyte UTF-8 characters in half. Works transparently on both decoded Unicode strings and raw UTF-8 byte strings.
$tr->substring("Çanakkale", 0, 4); # "Çana" (4 characters, 5 bytes)
$tr->substring("İstanbul", 2, 3); # "tan"
# Default offset is 0 if omitted:
$tr->substring("Şanlıurfa", 5); # "Şanlı"
Search Engine Tokenization & Regex
search_pattern($query)
Converts a search query string into a locale-aware regex pattern. Replaces locale-specific casing characters with regex character classes.
my $pat = $tr->search_pattern("sırdaş");
# => pattern matching both "sırdaş", "SIRDAŞ", "Sırdaş" under Turkish rules
search_regex($string, $pattern)
Performs a case-insensitive, locale-aware regex match of $pattern inside $string. Returns 1 on match, 0 otherwise.
my $found = $tr->search_regex("İstanbul Boğazı", "istanbul"); # 1
normalize_word($word, [$mode_write])
Normalizes a single search token according to locale phonetic assimilation, clitic/apostrophe stripping, and final-devoicing rules.
Write Mode (
$mode_write = 1): Generates both the root token and joined compound token to index clitic variants (e.g."Türkiye'de"->"turkiye turkiyede").Read Mode (
$mode_write = 0or omitted): Strips clitics and suffixes to resolve the base root (e.g."Türkiye'de"->"turkiye"; single-letter prefixes like"T-Shirt"resolve to"tshirt").
my $write_tokens = $tr->normalize_word("Türkiye'de", 1); # "turkiye turkiyede"
my $query_token = $tr->normalize_word("Türkiye'de", 0); # "turkiye"
Number & Currency Processing
num2text($number [, %options])
Converts numeric values (integers or floating-point decimals) into written words in the target locale. Ideal for generating formal banking receipts, invoices, and cheques.
# Turkish rules: "1000" is "Bin TL" (NOT "Bir Bin TL")
$tr->num2text(0); # "Sıfır"
$tr->num2text(1); # "Bir TL"
$tr->num2text(100); # "Yüz TL"
$tr->num2text(1000); # "Bin TL"
$tr->num2text(1234.56); # "Bin İki Yüz Otuz Dört TL Elli Altı KR"
$tr->num2text(-42); # "Eksi Kırk İki TL"
# English rules: requires "One Thousand"
$en->num2text(1000); # "One Thousand USD"
$en->num2text(1234.56); # "One Thousand Two Hundred Thirty Four USD Fifty Six cent"
# German rules:
$de->num2text(1000); # "Eins Tausend EUR"
Accepts Eastern Arabic (٠١٢٣٤٥٦٧٨٩) and Persian (۰۱۲۳۴۵۶۷۸۹) digits automatically.
Options:
currency => { main => "EUR", sub => "cent" }: Custom currency labels.$tr->num2text(99.99, currency => { main => "EUR", sub => "cent" }); # => "Doksan Dokuz EUR Doksan Dokuz cent"numbers => \%custom_hash: Overrides number word definitions with custom dictionaries.
format_number($number [, %options])
Formats $number with locale-specific decimal and thousand grouping separators.
# Turkish conventions (group: dot, decimal: comma)
$tr->format_number(1234567.89); # "1.234.567,89"
$tr->format_number(1234567.89, decimals => 0); # "1.234.568"
$tr->format_number(1234567.89, decimals => 3); # "1.234.567,890"
# German conventions (group: dot, decimal: comma)
$de->format_number(1234567.89); # "1.234.567,89"
# English conventions (group: comma, decimal: dot)
my $en = AmberDB::Locale->new(language => "en");
$en->format_number(1234567.89); # "1,234,567.89"
# French conventions (group: space, decimal: comma)
my $fr = AmberDB::Locale->new(language => "fr");
$fr->format_number(1234567.89); # "1 234 567,89"
Available options: decimals, decimal_sep, group_sep.
format_currency($amount [, $currency_code | %options])
Formats monetary amounts using locale conventions or specific ISO 4217 currency settings.
# Default Turkish currency (TRY)
$tr->format_currency(1234.50); # "₺1.234,50"
# Explicit ISO code
$tr->format_currency(1234.50, 'EUR'); # "1.234,50 €"
$tr->format_currency(1234.50, currency => 'USD'); # "$1.234,50"
Custom formatting overrides:
$tr->format_currency(100, symbol => 'TL', position => 'suffix', space => 1);
# => "100,00 TL"
Date & Time Operations
format_date($time_or_string [, $pattern_or_style])
Formats a Unix epoch timestamp or ISO date string into a localized date/time representation.
my $epoch = 1787832600; # 2026-08-28 14:30:00
# Standard styles:
$tr->format_date($epoch); # "28.08.2026" (short, default)
$tr->format_date($epoch, 'medium'); # "28 Ağu 2026"
$tr->format_date($epoch, 'long'); # "28 Ağustos 2026"
$tr->format_date($epoch, 'full'); # "Cuma, 28 Ağustos 2026"
$tr->format_date($epoch, 'time'); # "14:30"
$tr->format_date($epoch, 'datetime'); # "28.08.2026 14:30"
# Custom format pattern tokens:
$tr->format_date($epoch, 'YYYY-MM-DD'); # "2026-08-28"
$tr->format_date($epoch, 'DD/MM/YYYY'); # "28/08/2026"
# Input can also be ISO date strings:
$tr->format_date("2026-08-28", 'full'); # "Cuma, 28 Ağustos 2026"
Supported pattern tokens:
YYYY,YY- 4-digit / 2-digit yearMMMM,MMM,MM,M- Full month name, short month, 2-digit month, 1-digit monthDD,D- 2-digit day, 1-digit daydddd,ddd- Full day name, short day nameHH,H- Hour (2-digit / 1-digit)mm,m- Minute (2-digit / 1-digit)ss,s- Second (2-digit / 1-digit)
parse_date($string [, %options])
Parses a localized date string (e.g. "28.08.2026" or "2026-08-28 14:30:00") back into a Unix epoch timestamp or component hash.
my $epoch = $tr->parse_date("28.08.2026"); # Unix timestamp
my $hash = $tr->parse_date("28.08.2026", hash => 1);
# => { year => 2026, month => 8, day => 28, hour => 0, minute => 0, second => 0 }
HTML Entity Decoding
decode_entities($string)
Decodes numeric (hex &#x...;, decimal &#...;) and named HTML entities in $string, incorporating both universal entities and locale-specific extra entities.
$tr->decode_entities("& < > € ç");
# => "& < > € ç"
Pluralization
plural($count, \%forms)
Selects and interpolates the appropriate plural form from \%forms based on CLDR plural rules for the active locale.
# English (2 forms: one, other)
my $en = AmberDB::Locale->new(language => "en");
$en->plural(1, { one => "{count} item", other => "{count} items" }); # "1 item"
$en->plural(5, { one => "{count} item", other => "{count} items" }); # "5 items"
# Russian (4 forms: one, few, many, other)
my $ru = AmberDB::Locale->new(language => "ru");
my %ru_apple = (
one => "{count} яблоко",
few => "{count} яблока",
many => "{count} яблок",
other => "{count} яблока"
);
$ru->plural(1, \%ru_apple); # "1 яблоко" (ends in 1, except 11)
$ru->plural(3, \%ru_apple); # "3 яблока" (ends in 2-4, except 12-14)
$ru->plural(5, \%ru_apple); # "5 яблок" (ends in 5-9, 0, or 11-14)
$ru->plural(21, \%ru_apple); # "21 яблоко" (ends in 1, except 11)
# Turkish (regular count)
my $tr = AmberDB::Locale->new(language => "tr");
$tr->plural(5, { one => "{count} ürün", other => "{count} ürün" }); # "5 ürün"
Placeholders {count} or {n} in template strings are automatically replaced with formatted number values.
Accessors
language()
Returns the active language tag (e.g., "tr", "en", "de").
months()
Returns an array reference containing the 12 localized month names.
days()
Returns an array reference containing the 7 localized day names starting from Sunday.
AUTHOR
Maruf Cetin <marufcetin@gmail.com>
LICENSE AND COPYRIGHT
Copyright (C) 2017-2026 Maruf Cetin.
This library is free software; you can redistribute it and/or modify it under the terms of the Artistic License 2.0.
1 POD Error
The following errors were encountered while parsing the POD:
- Around line 1163:
Non-ASCII character seen before =encoding in '$adb->uc("ığdır");'. Assuming UTF-8