49
|
1 use strict;
|
|
2 use warnings;
|
|
3
|
|
4 package IMPL::Resources::Strings;
|
245
|
5
|
49
|
6 use File::Spec;
|
245
|
7 use List::Util qw(first);
|
|
8 use IMPL::Resources::Format qw(FormatMessage);
|
49
|
9
|
|
10 our $Locale ||= 'default';
|
|
11 our $Encoding ||= 'utf-8';
|
|
12 our @Locations;
|
|
13
|
|
14 sub import {
|
|
15 my ($self,$refStrings,%options) = @_;
|
|
16
|
245
|
17 no strict 'refs';
|
|
18
|
|
19 my $class = caller;
|
|
20
|
|
21 if (ref $refStrings eq 'HASH') {
|
|
22 my %map;
|
|
23 while(my ($name,$format) = each %$refStrings) {
|
|
24 $map{default}{$name} = $format;
|
|
25
|
|
26 *{"${class}::$name"} = sub {
|
|
27 my $args = @_ == 1 ? shift : { @_ };
|
|
28
|
|
29 return _FormatMapMessage($class,$name,\%map,$Locale,$args);
|
|
30 }
|
|
31 }
|
|
32 }
|
|
33 }
|
|
34
|
|
35 sub _FormatMapMessage {
|
|
36 my ($class,$msg,$map,$locale,$args) = @_;
|
49
|
37
|
245
|
38 if (not exists $map->{$locale} ) {
|
|
39 $map->{$locale} = LoadStrings($class,$locale);
|
|
40 }
|
|
41
|
|
42 return FormatMessage( ($map->{$locale} || $map->{default})->{$msg}, $args );
|
|
43 }
|
|
44
|
|
45 sub LoadStrings {
|
|
46 my ($class,$locale) = @_;
|
49
|
47
|
245
|
48 # Foo::Bar -> ('Foo','Bar')
|
|
49 my @classNamespace = split /::/,$class;
|
|
50
|
|
51 my $classShortName = pop @classNamespace;
|
|
52
|
|
53 # Foo::Bar -> 'Foo/Bar.pm'
|
|
54 my $classModuleName = File::Spec->catfile(@classNamespace,"${classShortName}.pm");
|
|
55
|
|
56 # 'Foo/Bar.pm' -> '/full/path/to/Foo/Bar.pm'
|
|
57 my $fullModulePath = first { -f } map( File::Spec->catfile($_,$classModuleName), @INC );
|
49
|
58
|
|
59 my @ways = map {
|
|
60 my @path = ($_);
|
|
61 push @path,$Locale;
|
|
62
|
245
|
63 File::Spec->catfile($_,$Locale,@classNamespace,$classShortName);
|
49
|
64 } @Locations;
|
|
65
|
|
66
|
245
|
67 if ($fullModulePath) {
|
|
68
|
|
69 # '/full/path/to/Foo/Bar.pm' -> '/full/path/to/Foo'
|
|
70 my ($vol,$dir,$file) = File::Spec->splitpath($fullModulePath);
|
|
71 my $baseDir = File::Spec->catpath($vol,$dir,'');
|
|
72
|
|
73 # '/full/path/to/Foo' -> '/full/path/to/Foo/locale/En_US/Bar'
|
|
74 push @ways, File::Spec->catfile($baseDir,'locale',$Locale,$classShortName);
|
|
75 }
|
49
|
76
|
245
|
77 my $mapFile = first { -f } @ways;
|
|
78
|
|
79 return unless $mapFile;
|
|
80
|
|
81 return ParseStringsMap($mapFile);
|
49
|
82 }
|
|
83
|
245
|
84 sub ParseStringsMap {
|
49
|
85 my ($fname) = @_;
|
|
86
|
|
87 open my $hRes, "<:encoding($Encoding)", findFile($fname) or die "Failed to open file $fname: $!";
|
|
88
|
|
89 my %Map;
|
|
90 my $line = 1;
|
|
91 while (<$hRes>) {
|
|
92 chomp;
|
|
93 $line ++ and next if /^\s*$/;
|
|
94
|
|
95 if (/^(\w+)\s*=\s*(.*)$/) {
|
|
96 $Map{$1} = $2;
|
|
97 } else {
|
|
98 die "Invalid resource format in $fname at $line";
|
|
99 }
|
|
100 $line ++;
|
|
101 }
|
|
102
|
|
103 return \%Map;
|
|
104 }
|
|
105
|
|
106 1;
|
|
107
|
|
108 __END__
|
|
109
|
|
110 =pod
|
|
111
|
66
|
112 =head1 NAME
|
|
113
|
180
|
114 C<IMPL::Resources::Strings> - Строковые ресурсы
|
66
|
115
|
49
|
116 =head1 SYNOPSIS
|
|
117
|
66
|
118 =begin code
|
|
119
|
49
|
120 package Foo;
|
|
121
|
|
122 use IMPL::Resources::Strings {
|
245
|
123 msg_say_hello => "Hello, %name%!",
|
49
|
124 msg_module_name => "Simple Foo class"
|
267
|
125 };
|
49
|
126
|
|
127 sub InviteUser {
|
|
128 my ($this,$uname) = @_;
|
|
129
|
|
130 print msg_say_hello(name => $uname);
|
|
131
|
|
132 }
|
|
133
|
66
|
134 =end code
|
|
135
|
|
136 =head1 DESCRIPTION
|
|
137
|
180
|
138 Импортирует в целевой модуль функции, которые возвращают локализованные
|
|
139 параметризованные сообщения.
|
66
|
140
|
180
|
141 При импорте ищутся модули по следующему алгоритму:
|
66
|
142
|
180
|
143 В каталогах из массива C<@Locations> ищется файл с относительным путем
|
245
|
144 C<$Locale/$ModName>, где C<$Locale> - глобальная переменная
|
180
|
145 модуля C<IMPL::Resourses::Strings>, а переменная C<$ModName> получена
|
|
146 путем замены 'C<::>' в имени целевого модуля на 'C</>'.
|
66
|
147
|
180
|
148 Если файл не был найден, то производится поиск в каталоге, где
|
|
149 расположен сам модуль, файла с относительным путем C<locale/$Locale/$ShortModName>,
|
|
150 где C<$ShortModeName> - последняя часть после 'C<::>' из имени целевого модуля.
|
66
|
151
|
180
|
152 Если файл не найден, то используются строки, указанные при объявлении
|
|
153 сообщений в целевом модуле.
|
66
|
154
|
|
155 =head1 FORMAT
|
|
156
|
|
157 =begin code text
|
|
158
|
|
159 msg_name = any text with named %params%
|
|
160 msg_hello = hello, %name%!!!
|
|
161 msg_resolve = this is a value of the property: %user.age%
|
|
162
|
|
163 msg_short_err = %error.Message%
|
|
164 msg_full_err = %error%
|
|
165
|
|
166 =end code text
|
|
167
|
49
|
168 =cut
|