AmberDB
view release on metacpan or search on metacpan
lib/AmberDB/Locale.pm view on Meta::CPAN
my $fmt = $self->{_locale}{date_format}{short} || 'DD.MM.YYYY';
if ( $fmt =~ /^MM/i ) {
$month = $p1;
$day = $p2;
$year = $p3;
}
else {
$day = $p1;
$month = $p2;
$year = $p3;
}
}
elsif ( $str =~ /^(\d{4})[.\/-](\d{1,2})[.\/-](\d{1,2})(?:\s+(\d{1,2}):(\d{1,2}):?(\d{1,2})?)?/ ) {
$year = int($1);
$month = int($2);
$day = int($3);
$hour = int( $4 // 0 );
$min = int( $5 // 0 );
$sec = int( $6 // 0 );
}
return unless $year && $month && $day;
if ( $opts{hash} ) {
return {
year => $year,
month => $month,
day => $day,
hour => $hour,
minute => $min,
second => $sec,
};
}
require Time::Local;
return eval { Time::Local::timelocal( $sec, $min, $hour, $day, $month - 1, $year ) };
}
# Private helper: securely evaluate sanitized CLDR plural expressions
sub _eval_plural_rule {
my ( $self, $cond, $count ) = @_;
return 0 unless defined $cond && length $cond;
my $n = abs( $count // 0 );
# Whitelist strictly only digits, 'n', whitespace, arithmetic and logical operators
return 0 unless $cond =~ /^[n0-9+\-*\/%&|!=<>()\s]+$/;
# Replace 'n' variable token with actual numeric value
( my $expr = $cond ) =~ s/\bn\b/$n/g;
# Safely evaluate numeric expression isolated from global DIE handlers
my $res = eval {
local $SIG{__DIE__} = sub {};
eval $expr; ## no critic
};
return $res ? 1 : 0;
}
# -------------------------------------------------------
# Evaluate CLDR plural rule and select template.
#
# my $text = $lang->plural(1, { one => "{count} ürün", other => "{count} ürün" });
# my $text = $lang->plural(5, { one => "{count} item", other => "{count} items" });
# -------------------------------------------------------
sub plural {
my ( $self, $count, $forms ) = @_;
return '' unless defined $forms;
$count //= 0;
my $form_key = 'other';
my $rule = $self->{_locale}{plural_rule} || 'one{n==1}other';
for my $key (qw(zero one two few many)) {
if ( $rule =~ /\b$key\{([^{}]+)\}/ ) {
my $cond = $1;
if ( $self->_eval_plural_rule( $cond, $count ) ) {
$form_key = $key;
last;
}
}
}
my $template;
if ( ref($forms) eq 'HASH' ) {
$template = $forms->{$form_key} // $forms->{other} // $forms->{one} // '';
}
else {
$template = "$forms";
}
my $fmt_count = $self->format_number($count, decimals => 0);
$template =~ s/\{count\}|\{n\}/$fmt_count/g;
return $template;
}
# -------------------------------------------------------
# Language tag accessor
# my $tag = $lang->language; # "tr", "en", "de" ...
# -------------------------------------------------------
sub language { return $_[0]->{_lang} }
1;
__END__
=encoding utf8
=head1 NAME
AmberDB::Locale - Multilingual text processing, collation, number/currency formatting, and search normalization engine
=head1 SYNOPSIS
# =========================================================================
# 1. DIRECT USAGE VIA AMBERDB INSTANCE ($adb inherits AmberDB::Locale):
# Reads active language from config (default is 'gb' or configured language)
# =========================================================================
my $adb = AmberDB->new(cfg => { language => "gb" });
lib/AmberDB/Locale.pm view on Meta::CPAN
# German rules:
$de->num2text(1000); # "Eins Tausend EUR"
Accepts Eastern Arabic (C<٠١٢٣٤٥٦٧٨٩>) and Persian (C<۰۱۲۳۴۵۶۷۸۹>) digits automatically.
Options:
=over 4
=item * C<currency =E<gt> { main =E<gt> "EUR", sub =E<gt> "cent" }>: Custom currency labels.
$tr->num2text(99.99, currency => { main => "EUR", sub => "cent" });
# => "Doksan Dokuz EUR Doksan Dokuz cent"
=item * C<numbers =E<gt> \%custom_hash>: Overrides number word definitions with custom dictionaries.
=back
=head3 format_number($number [, %options])
Formats C<$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: C<decimals>, C<decimal_sep>, C<group_sep>.
=head3 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"
=head3 ISO 4217 Currency Dictionary
C<AmberDB::Locale> integrates a master dictionary of ISO 4217 currency definitions, numeric codes, currency symbols, and default subunit decimal precision (implemented internally via C<AmberDB::Locale::Currency>).
Direct dictionary lookups, symbol conversions, and select dropdown lists can be accessed via:
use AmberDB::Locale::Currency;
# Symbol and name lookups
my $sym = AmberDB::Locale::Currency->symbol('TRY'); # 'âº'
my $name = AmberDB::Locale::Currency->name('USD'); # 'US Dollar'
my $info = AmberDB::Locale::Currency->by_code('EUR');
# => { num => '978', name => 'Euro', symbol => 'â¬', digits => 2 }
# Dropdown options for UI forms
my @options = AmberDB::Locale::Currency->all();
# => ( [ 'TRY', 'Turkish Lira' ], [ 'USD', 'US Dollar' ], ... )
# List active ISO codes
my @codes = AmberDB::Locale::Currency->active_codes();
# => ('TRY', 'USD', 'EUR', 'GBP', 'RUB', 'AZN', 'SAR', 'JPY', 'CHF', 'CAD', 'AUD', 'CNY')
Supported helper methods:
=over 4
=item * C<AmberDB::Locale::Currency-E<gt>by_code($iso_code)> - Returns the currency definition hash reference for the given 3-letter ISO 4217 code (case-insensitive), containing C<num>, C<name>, C<symbol>, and C<digits>.
=item * C<AmberDB::Locale::Currency-E<gt>symbol($iso_code)> - Returns the currency symbol for the given ISO code (e.g. C<'âº'>, C<'$'>, C<'â¬'>, C<'£'>, C<'â½'>, C<'Â¥'>). If the code is unknown, returns the uppercase code itself.
=item * C<AmberDB::Locale::Currency-E<gt>name($iso_code)> - Returns the English currency name for the given ISO code.
=item * C<AmberDB::Locale::Currency-E<gt>all()> - Returns a list of 2-element array references C<[ $code, $name ]> ordered by priority, suitable for rendering HTML C<E<lt>selectE<gt>> form dropdowns.
=item * C<AmberDB::Locale::Currency-E<gt>active_codes()> - Returns the list of active 3-letter ISO 4217 currency codes supported by the dictionary.
=back
=head2 Date & Time Operations
=head3 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:
=over 4
=item * C<YYYY>, C<YY> - 4-digit / 2-digit year
=item * C<MMMM>, C<MMM>, C<MM>, C<M> - Full month name, short month, 2-digit month, 1-digit month
=item * C<DD>, C<D> - 2-digit day, 1-digit day
=item * C<dddd>, C<ddd> - Full day name, short day name
=item * C<HH>, C<H> - Hour (2-digit / 1-digit)
=item * C<mm>, C<m> - Minute (2-digit / 1-digit)
=item * C<ss>, C<s> - Second (2-digit / 1-digit)
=back
=head3 parse_date($string [, %options])
Parses a localized date string (e.g. C<"28.08.2026"> or C<"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 }
=head2 HTML Entity Decoding
=head3 decode_entities($string)
( run in 0.642 second using v1.01-cache-2.11-cpan-e623d60df62 )