view Lib/IMPL/Resources/Strings.pm @ 318:1838bdb4d238

corrected support of resources localization
author cin
date Tue, 14 May 2013 03:38:47 +0400
parents 6dc1c369eb71
children f4e14f32cf54
line wrap: on
line source

package IMPL::Resources::Strings;
use strict;

use File::Spec;
use List::Util qw(first);
use IMPL::Resources::Format qw(FormatMessage);
use IMPL::require {
    Resources => 'IMPL::Resources'
};

our $Encoding ||= 'utf-8';
our @Locations;
my %maps;

sub import {
    my ($self,$refStrings,%options) = @_;
    
    no strict 'refs';
    
    my $class = caller;
    my $methods = $options{methods};
    
    if (ref $refStrings eq 'HASH') {
        my $map = ( $maps{$class} ||= {} );
        while(my ($name,$format) = each %$refStrings) {
            $map->{default}{$name} = $format;
            
            *{"${class}::$name"} = sub {
                shift if $methods;
                my $args = @_ == 1 ? shift : { @_ };
                
                return _FormatMapMessage($class,$name,$map,Resources->currentLocale,$args);
            }
        }
    }    
}

sub _FormatMapMessage {
    my ($class,$msg,$map,$locale,$args) = @_;
    
    if (not exists $map->{$locale} ) {
        $map->{$locale} = LoadStrings($class,$locale);        
    }
    
    return FormatMessage( ($map->{$locale} || $map->{default})->{$msg}, $args );
}

sub LoadStrings {
    my ($class,$locale) = @_;
    
    # Foo::Bar -> ('Foo','Bar')
    my @classNamespace = split /::/,$class;
    
    my $classShortName = pop @classNamespace;

    # Foo::Bar -> 'Foo/Bar.pm'    
    my $classModuleName = File::Spec->catfile(@classNamespace,"${classShortName}.pm");
    
    # 'Foo/Bar.pm' -> '/full/path/to/Foo/Bar.pm'
    my $fullModulePath = first { -f } map( File::Spec->catfile($_,$classModuleName), @INC );
    
    my @ways = map {
        my @path = ($_);
        push @path,Resources->currentLocale;
        
        File::Spec->catfile($_,Resources->currentLocale,@classNamespace,$classShortName);
    } @Locations;
    
    
    if ($fullModulePath) {

        # '/full/path/to/Foo/Bar.pm' -> '/full/path/to/Foo' 
        my ($vol,$dir,$file) = File::Spec->splitpath($fullModulePath);
        my $baseDir = File::Spec->catpath($vol,$dir,'');

        # '/full/path/to/Foo' -> '/full/path/to/Foo/locale/En_US/Bar' 
        push @ways, File::Spec->catfile($baseDir,'locale',Resources->currentLocale,$classShortName);
    }
    
    my $mapFile = first { -f } @ways;
    
    return unless $mapFile;
    
    return ParseStringsMap($mapFile);
}

sub ParseStringsMap {
    my ($fname) = @_;
    
    open my $hRes, "<:encoding($Encoding)", findFile($fname) or die "Failed to open file $fname: $!";
    
    my %Map;
    my $line = 1;
    while (<$hRes>) {
        chomp;
        $line ++ and next if /^\s*$/;
        
        if (/^(\w+)\s*=\s*(.*)$/) {
            $Map{$1} = $2;
        } else {
            die "Invalid resource format in $fname at $line";
        }
        $line ++;
    }
    
    return \%Map;
}

1;

__END__

=pod

=head1 NAME

C<IMPL::Resources::Strings> - Строковые ресурсы

=head1 SYNOPSIS

=begin code

package Foo;

use IMPL::Resources::Strings {
    msg_say_hello => "Hello, %name%!",
    msg_module_name => "Simple Foo class"
};

sub InviteUser {
    my ($this,$uname) = @_;
    
    print msg_say_hello(name => $uname);
    
}

=end code

=head1 DESCRIPTION

Импортирует в целевой модуль функции, которые возвращают локализованные
параметризованные сообщения.

При импорте ищутся модули по следующему алгоритму:

В каталогах из массива C<@Locations> ищется файл с относительным путем
C<$Locale/$ModName>, где C<$Locale> - глобальная переменная
модуля C<IMPL::Resourses::Strings>, а переменная C<$ModName> получена
путем замены 'C<::>' в имени целевого модуля на 'C</>'.

Если файл не был найден, то производится поиск в каталоге, где
расположен сам модуль, файла с относительным путем C<locale/$Locale/$ShortModName>,
где C<$ShortModeName> - последняя часть после 'C<::>' из имени целевого модуля.

Если файл не найден, то используются строки, указанные при объявлении
сообщений в целевом модуле.

=head1 FORMAT

=begin code text

msg_name = any text with named %params%
msg_hello = hello, %name%!!!
msg_resolve = this is a value of the property: %user.age%

msg_short_err = %error.Message%
msg_full_err = %error% 

=end code text 

=cut