| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
#! /bin/false |
|
2
|
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
# vim: set autoindent shiftwidth=4 tabstop=4: |
|
4
|
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
# High-level interface to Perl i18n. |
|
6
|
|
|
|
|
|
|
# Copyright (C) 2002-2026 Guido Flohr <guido.flohr@cantanea.com>, |
|
7
|
|
|
|
|
|
|
# all rights reserved. |
|
8
|
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
# This program is free software: you can redistribute it and/or modify |
|
10
|
|
|
|
|
|
|
# it under the terms of the GNU General Public License as published by |
|
11
|
|
|
|
|
|
|
# the Free Software Foundation; either version 3 of the License, or |
|
12
|
|
|
|
|
|
|
# (at your option) any later version. |
|
13
|
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
# This program is distributed in the hope that it will be useful, |
|
15
|
|
|
|
|
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
16
|
|
|
|
|
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
17
|
|
|
|
|
|
|
# GNU General Public License for more details. |
|
18
|
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# You should have received a copy of the GNU General Public License |
|
20
|
|
|
|
|
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>. |
|
21
|
|
|
|
|
|
|
|
|
22
|
|
|
|
|
|
|
package __TiedTextDomain; |
|
23
|
|
|
|
|
|
|
|
|
24
|
3
|
|
|
3
|
|
200718
|
use strict; |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
832
|
|
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
sub TIEHASH |
|
27
|
|
|
|
|
|
|
{ |
|
28
|
3
|
|
|
3
|
|
37
|
my ($class, $function) = @_; |
|
29
|
3
|
|
|
|
|
19
|
bless { |
|
30
|
|
|
|
|
|
|
__function => $function, |
|
31
|
|
|
|
|
|
|
}, $class; |
|
32
|
|
|
|
|
|
|
} |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
sub FETCH |
|
35
|
|
|
|
|
|
|
{ |
|
36
|
0
|
|
|
0
|
|
0
|
my ($self, $msg) = @_; |
|
37
|
|
|
|
|
|
|
|
|
38
|
0
|
|
|
|
|
0
|
&{$self->{__function}} ($msg); |
|
|
0
|
|
|
|
|
0
|
|
|
39
|
|
|
|
|
|
|
} |
|
40
|
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub FIRSTKEY |
|
42
|
|
|
|
|
|
|
{ |
|
43
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
44
|
0
|
|
|
|
|
0
|
my $reset_iterator = keys %$self; |
|
45
|
0
|
|
|
|
|
0
|
return scalar each %$self; |
|
46
|
|
|
|
|
|
|
} |
|
47
|
|
|
|
|
|
|
|
|
48
|
|
|
|
|
|
|
sub NEXTKEY |
|
49
|
|
|
|
|
|
|
{ |
|
50
|
0
|
|
|
0
|
|
0
|
my $self = shift; |
|
51
|
0
|
|
|
|
|
0
|
return scalar each %$self; |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
0
|
|
|
sub CLEAR {} |
|
55
|
|
|
|
0
|
|
|
sub STORE {} |
|
56
|
|
|
|
0
|
|
|
sub DELETE {} |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
1; |
|
59
|
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
package Locale::TextDomain; |
|
61
|
|
|
|
|
|
|
|
|
62
|
3
|
|
|
3
|
|
24
|
use strict; |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
159
|
|
|
63
|
|
|
|
|
|
|
|
|
64
|
3
|
|
|
3
|
|
1497
|
use Locale::Messages qw (textdomain bindtextdomain dgettext dngettext dpgettext dnpgettext); |
|
|
3
|
|
|
|
|
10
|
|
|
|
3
|
|
|
|
|
484
|
|
|
65
|
3
|
|
|
3
|
|
23
|
use Cwd qw (abs_path); |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
210
|
|
|
66
|
|
|
|
|
|
|
|
|
67
|
3
|
|
|
3
|
|
22
|
use vars qw ($VERSION); |
|
|
3
|
|
|
|
|
6
|
|
|
|
3
|
|
|
|
|
211
|
|
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
$VERSION = '1.37'; |
|
70
|
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
require Exporter; |
|
72
|
|
|
|
|
|
|
|
|
73
|
3
|
|
|
3
|
|
24
|
use vars qw (@ISA @EXPORT %__ $__); |
|
|
3
|
|
|
|
|
7
|
|
|
|
3
|
|
|
|
|
830
|
|
|
74
|
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
@ISA = ('Exporter'); |
|
76
|
|
|
|
|
|
|
@EXPORT = qw (__ __x __n __nx __xn __p __px __np __npx $__ %__ |
|
77
|
|
|
|
|
|
|
N__ N__n N__p N__np); |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
my %textdomains = (); |
|
80
|
|
|
|
|
|
|
my %bound_dirs = (); |
|
81
|
|
|
|
|
|
|
my @default_dirs = (); |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
sub __ ($); |
|
84
|
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
sub __find_domain ($); |
|
86
|
|
|
|
|
|
|
sub __expand ($%); |
|
87
|
|
|
|
|
|
|
sub __tied_gettext ($$); |
|
88
|
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
BEGIN { |
|
90
|
|
|
|
|
|
|
# Tie the hash to gettext(). |
|
91
|
3
|
|
|
3
|
|
30
|
tie %__, '__TiedTextDomain', \&__tied_gettext; |
|
92
|
3
|
|
|
|
|
7
|
$__ = \%__; |
|
93
|
|
|
|
|
|
|
|
|
94
|
|
|
|
|
|
|
# Add default search directories, but only if they exist. |
|
95
|
3
|
|
|
|
|
8
|
for my $dir (qw (/usr/share/locale /usr/local/share/locale)) { |
|
96
|
3
|
50
|
|
|
|
122
|
if (-d $dir) { |
|
97
|
3
|
|
|
|
|
12
|
@default_dirs = ($dir); |
|
98
|
3
|
|
|
|
|
7609
|
last; |
|
99
|
|
|
|
|
|
|
} |
|
100
|
|
|
|
|
|
|
} |
|
101
|
|
|
|
|
|
|
} |
|
102
|
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
# Class methods. |
|
104
|
|
|
|
|
|
|
sub keywords { |
|
105
|
2
|
|
|
2
|
1
|
15
|
join ' ', ( |
|
106
|
|
|
|
|
|
|
'--keyword=__', |
|
107
|
|
|
|
|
|
|
'--keyword=%__', |
|
108
|
|
|
|
|
|
|
'--keyword=$__', |
|
109
|
|
|
|
|
|
|
'--keyword=__x', |
|
110
|
|
|
|
|
|
|
'--keyword=__n:1,2', |
|
111
|
|
|
|
|
|
|
'--keyword=__nx:1,2', |
|
112
|
|
|
|
|
|
|
'--keyword=__xn:1,2', |
|
113
|
|
|
|
|
|
|
'--keyword=__p:1c,2', |
|
114
|
|
|
|
|
|
|
'--keyword=__px:1c,2', |
|
115
|
|
|
|
|
|
|
'--keyword=__np:1c,2,3', |
|
116
|
|
|
|
|
|
|
'--keyword=__npx:1c,2,3', |
|
117
|
|
|
|
|
|
|
'--keyword=N__', |
|
118
|
|
|
|
|
|
|
'--keyword=N__n:1,2', |
|
119
|
|
|
|
|
|
|
'--keyword=N__p:1c,2', |
|
120
|
|
|
|
|
|
|
'--keyword=N__np:1c,2,3', |
|
121
|
|
|
|
|
|
|
); |
|
122
|
|
|
|
|
|
|
} |
|
123
|
|
|
|
|
|
|
|
|
124
|
|
|
|
|
|
|
sub flags { |
|
125
|
2
|
|
|
2
|
1
|
106
|
join ' ', ( |
|
126
|
|
|
|
|
|
|
'--flag=__:1:pass-perl-format', |
|
127
|
|
|
|
|
|
|
'--flag=%__:1:pass-perl-format', |
|
128
|
|
|
|
|
|
|
'--flag=$__:1:pass-perl-format', |
|
129
|
|
|
|
|
|
|
'--flag=__x:1:perl-brace-format', |
|
130
|
|
|
|
|
|
|
'--flag=__x:1:pass-perl-format', |
|
131
|
|
|
|
|
|
|
'--flag=__n:1:pass-perl-format', |
|
132
|
|
|
|
|
|
|
'--flag=__n:2:pass-perl-format', |
|
133
|
|
|
|
|
|
|
'--flag=__nx:1:perl-brace-format', |
|
134
|
|
|
|
|
|
|
'--flag=__nx:1:pass-perl-format', |
|
135
|
|
|
|
|
|
|
'--flag=__nx:2:perl-brace-format', |
|
136
|
|
|
|
|
|
|
'--flag=__nx:2:pass-perl-format', |
|
137
|
|
|
|
|
|
|
'--flag=__xn:1:perl-brace-format', |
|
138
|
|
|
|
|
|
|
'--flag=__xn:1:pass-perl-format', |
|
139
|
|
|
|
|
|
|
'--flag=__xn:2:perl-brace-format', |
|
140
|
|
|
|
|
|
|
'--flag=__xn:2:pass-perl-format', |
|
141
|
|
|
|
|
|
|
'--flag=__p:2:pass-perl-format', |
|
142
|
|
|
|
|
|
|
'--flag=__px:2:perl-brace-format', |
|
143
|
|
|
|
|
|
|
'--flag=__px:2:pass-perl-format', |
|
144
|
|
|
|
|
|
|
'--flag=__np:2:pass-perl-format', |
|
145
|
|
|
|
|
|
|
'--flag=__np:3:pass-perl-format', |
|
146
|
|
|
|
|
|
|
'--flag=__npx:2:perl-brace-format', |
|
147
|
|
|
|
|
|
|
'--flag=__npx:2:pass-perl-format', |
|
148
|
|
|
|
|
|
|
'--flag=__npx:3:perl-brace-format', |
|
149
|
|
|
|
|
|
|
'--flag=__npx:3:pass-perl-format', |
|
150
|
|
|
|
|
|
|
'--flag=N__:1:pass-perl-format', |
|
151
|
|
|
|
|
|
|
'--flag=N__n:1:pass-perl-format', |
|
152
|
|
|
|
|
|
|
'--flag=N__n:2:pass-perl-format', |
|
153
|
|
|
|
|
|
|
'--flag=N__p:2:pass-perl-format', |
|
154
|
|
|
|
|
|
|
'--flag=N__np:2:pass-perl-format', |
|
155
|
|
|
|
|
|
|
'--flag=N__np:3:pass-perl-format', |
|
156
|
|
|
|
|
|
|
); |
|
157
|
|
|
|
|
|
|
} |
|
158
|
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
sub options { |
|
160
|
1
|
|
|
1
|
1
|
49
|
my ($class) = @_; |
|
161
|
|
|
|
|
|
|
|
|
162
|
1
|
|
|
|
|
4
|
join ' ', $class->keywords, $class->flags; |
|
163
|
|
|
|
|
|
|
} |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
# Normal gettext. |
|
166
|
|
|
|
|
|
|
sub __ ($) |
|
167
|
|
|
|
|
|
|
{ |
|
168
|
2
|
|
|
2
|
|
189687
|
my $msgid = shift; |
|
169
|
|
|
|
|
|
|
|
|
170
|
2
|
|
|
|
|
6
|
my $package = caller; |
|
171
|
|
|
|
|
|
|
|
|
172
|
2
|
|
|
|
|
5
|
my $textdomain = $textdomains{$package}; |
|
173
|
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
175
|
2
|
100
|
66
|
|
|
18
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
176
|
|
|
|
|
|
|
|
|
177
|
2
|
|
|
|
|
6
|
return dgettext $textdomain => $msgid; |
|
178
|
|
|
|
|
|
|
} |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
# Called from tied hash. |
|
181
|
|
|
|
|
|
|
sub __tied_gettext ($$) |
|
182
|
|
|
|
|
|
|
{ |
|
183
|
0
|
|
|
0
|
|
0
|
my ($msgid) = @_; |
|
184
|
|
|
|
|
|
|
|
|
185
|
0
|
|
|
|
|
0
|
my ($package) = caller (1); |
|
186
|
|
|
|
|
|
|
|
|
187
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
188
|
0
|
0
|
|
|
|
0
|
unless (defined $textdomain) { |
|
189
|
0
|
|
|
|
|
0
|
my ($maybe_package, $filename, $line) = caller (2); |
|
190
|
0
|
0
|
|
|
|
0
|
if (exists $textdomains{$maybe_package}) { |
|
191
|
0
|
|
|
|
|
0
|
warn <<EOF; |
|
192
|
|
|
|
|
|
|
Probable use of \$__ or \%__ where __() should be used at $filename:$line. |
|
193
|
|
|
|
|
|
|
EOF |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
197
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
198
|
|
|
|
|
|
|
|
|
199
|
0
|
|
|
|
|
0
|
return dgettext $textdomain => $msgid; |
|
200
|
|
|
|
|
|
|
} |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
# With interpolation. |
|
203
|
|
|
|
|
|
|
sub __x ($@) |
|
204
|
|
|
|
|
|
|
{ |
|
205
|
0
|
|
|
0
|
|
0
|
my ($msgid, %vars) = @_; |
|
206
|
|
|
|
|
|
|
|
|
207
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
208
|
|
|
|
|
|
|
|
|
209
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
210
|
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
212
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
213
|
|
|
|
|
|
|
|
|
214
|
0
|
|
|
|
|
0
|
return __expand ((dgettext $textdomain => $msgid), %vars); |
|
215
|
|
|
|
|
|
|
} |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
# Plural. |
|
218
|
|
|
|
|
|
|
sub __n ($$$) |
|
219
|
|
|
|
|
|
|
{ |
|
220
|
0
|
|
|
0
|
|
0
|
my ($msgid, $msgid_plural, $count) = @_; |
|
221
|
|
|
|
|
|
|
|
|
222
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
223
|
|
|
|
|
|
|
|
|
224
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
225
|
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
227
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
228
|
|
|
|
|
|
|
|
|
229
|
0
|
|
|
|
|
0
|
return dngettext $textdomain, $msgid, $msgid_plural, $count; |
|
230
|
|
|
|
|
|
|
} |
|
231
|
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# Plural with interpolation. |
|
233
|
|
|
|
|
|
|
sub __nx ($$$@) |
|
234
|
|
|
|
|
|
|
{ |
|
235
|
0
|
|
|
0
|
|
0
|
my ($msgid, $msgid_plural, $count, %args) = @_; |
|
236
|
|
|
|
|
|
|
|
|
237
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
238
|
|
|
|
|
|
|
|
|
239
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
240
|
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
242
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
243
|
|
|
|
|
|
|
|
|
244
|
0
|
|
|
|
|
0
|
return __expand ((dngettext $textdomain, $msgid, $msgid_plural, $count), |
|
245
|
|
|
|
|
|
|
%args); |
|
246
|
|
|
|
|
|
|
} |
|
247
|
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
# Plural with interpolation. |
|
249
|
|
|
|
|
|
|
sub __xn ($$$@) |
|
250
|
|
|
|
|
|
|
{ |
|
251
|
0
|
|
|
0
|
|
0
|
my ($msgid, $msgid_plural, $count, %args) = @_; |
|
252
|
|
|
|
|
|
|
|
|
253
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
254
|
|
|
|
|
|
|
|
|
255
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
256
|
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
258
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
259
|
|
|
|
|
|
|
|
|
260
|
0
|
|
|
|
|
0
|
return __expand ((dngettext $textdomain, $msgid, $msgid_plural, $count), |
|
261
|
|
|
|
|
|
|
%args); |
|
262
|
|
|
|
|
|
|
} |
|
263
|
|
|
|
|
|
|
|
|
264
|
|
|
|
|
|
|
# Context. (p is for particular or special) |
|
265
|
|
|
|
|
|
|
sub __p ($$) |
|
266
|
|
|
|
|
|
|
{ |
|
267
|
0
|
|
|
0
|
|
0
|
my $msgctxt = shift; |
|
268
|
0
|
|
|
|
|
0
|
my $msgid = shift; |
|
269
|
|
|
|
|
|
|
|
|
270
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
271
|
|
|
|
|
|
|
|
|
272
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
275
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
276
|
|
|
|
|
|
|
|
|
277
|
0
|
|
|
|
|
0
|
return dpgettext $textdomain => $msgctxt, $msgid; |
|
278
|
|
|
|
|
|
|
} |
|
279
|
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
# With interpolation. |
|
281
|
|
|
|
|
|
|
sub __px ($$@) |
|
282
|
|
|
|
|
|
|
{ |
|
283
|
0
|
|
|
0
|
|
0
|
my ($msgctxt, $msgid, %vars) = @_; |
|
284
|
|
|
|
|
|
|
|
|
285
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
286
|
|
|
|
|
|
|
|
|
287
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
288
|
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
290
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
291
|
|
|
|
|
|
|
|
|
292
|
0
|
|
|
|
|
0
|
return __expand ((dpgettext $textdomain => $msgctxt, $msgid), %vars); |
|
293
|
|
|
|
|
|
|
} |
|
294
|
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
# Context + Plural. |
|
296
|
|
|
|
|
|
|
sub __np ($$$$) |
|
297
|
|
|
|
|
|
|
{ |
|
298
|
0
|
|
|
0
|
|
0
|
my ($msgctxt, $msgid, $msgid_plural, $count) = @_; |
|
299
|
|
|
|
|
|
|
|
|
300
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
301
|
|
|
|
|
|
|
|
|
302
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
303
|
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
305
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
306
|
|
|
|
|
|
|
|
|
307
|
0
|
|
|
|
|
0
|
return dnpgettext $textdomain, $msgctxt, $msgid, $msgid_plural, $count; |
|
308
|
|
|
|
|
|
|
} |
|
309
|
|
|
|
|
|
|
|
|
310
|
|
|
|
|
|
|
# Plural with interpolation. |
|
311
|
|
|
|
|
|
|
sub __npx ($$$$@) |
|
312
|
|
|
|
|
|
|
{ |
|
313
|
0
|
|
|
0
|
|
0
|
my ($msgctxt, $msgid, $msgid_plural, $count, %args) = @_; |
|
314
|
|
|
|
|
|
|
|
|
315
|
0
|
|
|
|
|
0
|
my $package = caller; |
|
316
|
|
|
|
|
|
|
|
|
317
|
0
|
|
|
|
|
0
|
my $textdomain = $textdomains{$package}; |
|
318
|
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
__find_domain $textdomain if |
|
320
|
0
|
0
|
0
|
|
|
0
|
defined $textdomain && defined $bound_dirs{$textdomain}; |
|
321
|
|
|
|
|
|
|
|
|
322
|
0
|
|
|
|
|
0
|
return __expand ((dnpgettext $textdomain, $msgctxt, $msgid, $msgid_plural, $count), |
|
323
|
|
|
|
|
|
|
%args); |
|
324
|
|
|
|
|
|
|
} |
|
325
|
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
# Dummy functions for string marking. |
|
327
|
|
|
|
|
|
|
sub N__($) |
|
328
|
|
|
|
|
|
|
{ |
|
329
|
2
|
|
|
2
|
1
|
214318
|
return shift; |
|
330
|
|
|
|
|
|
|
} |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
sub N__n($$$) |
|
333
|
|
|
|
|
|
|
{ |
|
334
|
1
|
|
|
1
|
1
|
120
|
return @_; |
|
335
|
|
|
|
|
|
|
} |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
sub N__p($$) { |
|
338
|
1
|
|
|
1
|
1
|
63
|
return @_; |
|
339
|
|
|
|
|
|
|
} |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
sub N__np($$$$) { |
|
342
|
1
|
|
|
1
|
1
|
47
|
return @_; |
|
343
|
|
|
|
|
|
|
} |
|
344
|
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
sub import |
|
346
|
|
|
|
|
|
|
{ |
|
347
|
2
|
|
|
2
|
|
24
|
my ($self, $textdomain, @search_dirs) = @_; |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
# Check our caller. |
|
350
|
2
|
|
|
|
|
6
|
my $package = caller; |
|
351
|
2
|
50
|
|
|
|
10
|
return if exists $textdomains{$package}; |
|
352
|
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
# Was a textdomain specified? |
|
354
|
2
|
100
|
66
|
|
|
14
|
$textdomain = textdomain unless defined $textdomain && length $textdomain; |
|
355
|
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
# Remember the textdomain of that package. |
|
357
|
2
|
|
|
|
|
7
|
$textdomains{$package} = $textdomain; |
|
358
|
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
# Remember that we still have to bind that textdomain to |
|
360
|
|
|
|
|
|
|
# a directory. |
|
361
|
2
|
50
|
|
|
|
13
|
unless (exists $bound_dirs{$textdomain}) { |
|
362
|
2
|
50
|
|
|
|
6
|
unless (@search_dirs) { |
|
363
|
2
|
50
|
|
|
|
26
|
@search_dirs = ((map $_ . '/LocaleData', @INC), @default_dirs) |
|
364
|
|
|
|
|
|
|
unless @search_dirs; |
|
365
|
2
|
50
|
|
|
|
6
|
if (my $share = eval { |
|
366
|
2
|
|
|
|
|
1445
|
require File::ShareDir; |
|
367
|
2
|
|
|
|
|
85484
|
File::ShareDir::dist_dir ($textdomain); |
|
368
|
|
|
|
|
|
|
}) { |
|
369
|
|
|
|
|
|
|
unshift @search_dirs, |
|
370
|
0
|
|
|
|
|
0
|
map { "$share/$_" } |
|
|
0
|
|
|
|
|
0
|
|
|
371
|
|
|
|
|
|
|
qw (locale LocaleData); |
|
372
|
|
|
|
|
|
|
} |
|
373
|
|
|
|
|
|
|
} |
|
374
|
2
|
|
|
|
|
2524
|
$bound_dirs{$textdomain} = [grep { -d $_ } @search_dirs]; |
|
|
21
|
|
|
|
|
400
|
|
|
375
|
|
|
|
|
|
|
} |
|
376
|
|
|
|
|
|
|
|
|
377
|
2
|
|
|
|
|
431
|
Locale::TextDomain->export_to_level (1, $package, @EXPORT); |
|
378
|
|
|
|
|
|
|
|
|
379
|
2
|
|
|
|
|
2563
|
return; |
|
380
|
|
|
|
|
|
|
} |
|
381
|
|
|
|
|
|
|
|
|
382
|
|
|
|
|
|
|
# Private functions. |
|
383
|
|
|
|
|
|
|
sub __find_domain ($) |
|
384
|
|
|
|
|
|
|
{ |
|
385
|
1
|
|
|
1
|
|
4
|
my $domain = shift; |
|
386
|
|
|
|
|
|
|
|
|
387
|
1
|
|
|
|
|
3
|
my $try_dirs = $bound_dirs{$domain}; |
|
388
|
|
|
|
|
|
|
|
|
389
|
1
|
50
|
|
|
|
21
|
if (defined $try_dirs) { |
|
390
|
1
|
|
|
|
|
2
|
my $found_dir; |
|
391
|
|
|
|
|
|
|
|
|
392
|
1
|
|
|
|
|
2
|
TRYDIR: foreach my $dir (grep { -d $_ } @$try_dirs) { |
|
|
2
|
|
|
|
|
39
|
|
|
393
|
|
|
|
|
|
|
# Is there a message catalog? We have to search recursively |
|
394
|
|
|
|
|
|
|
# for it. Since globbing is reported to be buggy under |
|
395
|
|
|
|
|
|
|
# MS-DOS, we roll our own version. |
|
396
|
1
|
|
|
|
|
4
|
local *DIR; |
|
397
|
1
|
50
|
|
|
|
63
|
if (opendir DIR, $dir) { |
|
398
|
4
|
|
|
|
|
9
|
my @files = map { "$dir/$_/LC_MESSAGES/$domain.mo" } |
|
399
|
1
|
|
|
|
|
31
|
grep { ! /^\.\.?$/ } readdir DIR; |
|
|
6
|
|
|
|
|
14
|
|
|
400
|
|
|
|
|
|
|
|
|
401
|
1
|
|
|
|
|
4
|
foreach my $file (@files) { |
|
402
|
1
|
50
|
33
|
|
|
39
|
if (-f $file || -l $file) { |
|
403
|
|
|
|
|
|
|
# If we find a non-readable file on our way, |
|
404
|
|
|
|
|
|
|
# we access has been disabled on purpose. |
|
405
|
|
|
|
|
|
|
# Therefore no -r check here. |
|
406
|
1
|
|
|
|
|
4
|
$found_dir = $dir; |
|
407
|
1
|
|
|
|
|
19
|
last TRYDIR; |
|
408
|
|
|
|
|
|
|
} |
|
409
|
|
|
|
|
|
|
} |
|
410
|
|
|
|
|
|
|
} |
|
411
|
|
|
|
|
|
|
} |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
# If there was no success, this will fall back to the default search |
|
414
|
|
|
|
|
|
|
# directories. |
|
415
|
1
|
50
|
|
|
|
36
|
bindtextdomain $domain => abs_path $found_dir if defined $found_dir; |
|
416
|
|
|
|
|
|
|
} |
|
417
|
|
|
|
|
|
|
|
|
418
|
|
|
|
|
|
|
# The search has completed. |
|
419
|
1
|
|
|
|
|
2
|
undef $bound_dirs{$domain}; |
|
420
|
|
|
|
|
|
|
|
|
421
|
1
|
|
|
|
|
3
|
return 1; |
|
422
|
|
|
|
|
|
|
} |
|
423
|
|
|
|
|
|
|
|
|
424
|
|
|
|
|
|
|
sub __expand ($%) |
|
425
|
|
|
|
|
|
|
{ |
|
426
|
0
|
|
|
0
|
|
|
my ($translation, %args) = @_; |
|
427
|
|
|
|
|
|
|
|
|
428
|
0
|
|
|
|
|
|
my $re = join '|', map { quotemeta $_ } keys %args; |
|
|
0
|
|
|
|
|
|
|
|
429
|
0
|
0
|
|
|
|
|
$translation =~ s/\{($re)\}/defined $args{$1} ? $args{$1} : "{$1}"/ge; |
|
|
0
|
|
|
|
|
|
|
|
430
|
|
|
|
|
|
|
|
|
431
|
0
|
|
|
|
|
|
return $translation; |
|
432
|
|
|
|
|
|
|
} |
|
433
|
|
|
|
|
|
|
|
|
434
|
|
|
|
|
|
|
1; |
|
435
|
|
|
|
|
|
|
|
|
436
|
|
|
|
|
|
|
__END__ |
|
437
|
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=head1 NAME |
|
439
|
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
Locale::TextDomain - Perl Interface to Uniforum Message Translation |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=head1 SYNOPSIS |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
use Locale::TextDomain ('my-package', @locale_dirs); |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
use Locale::TextDomain qw (my-package); |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
my $translated = __"Hello World!\n"; |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
my $alt = $__{"Hello World!\n"}; |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
my $alt2 = $__->{"Hello World!\n"}; |
|
453
|
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
my @list = (N__"Hello", |
|
455
|
|
|
|
|
|
|
N__"World"); |
|
456
|
|
|
|
|
|
|
|
|
457
|
|
|
|
|
|
|
printf (__n ("one file read", |
|
458
|
|
|
|
|
|
|
"%d files read", |
|
459
|
|
|
|
|
|
|
$num_files), |
|
460
|
|
|
|
|
|
|
$num_files); |
|
461
|
|
|
|
|
|
|
|
|
462
|
|
|
|
|
|
|
print __nx ("one file read", "{num} files read", $num_files, |
|
463
|
|
|
|
|
|
|
num => $num_files); |
|
464
|
|
|
|
|
|
|
|
|
465
|
|
|
|
|
|
|
my $translated_context = __p ("Verb, to view", "View"); |
|
466
|
|
|
|
|
|
|
|
|
467
|
|
|
|
|
|
|
printf (__np ("Files read from filesystems", |
|
468
|
|
|
|
|
|
|
"one file read", |
|
469
|
|
|
|
|
|
|
"%d files read", |
|
470
|
|
|
|
|
|
|
$num_files), |
|
471
|
|
|
|
|
|
|
$num_files); |
|
472
|
|
|
|
|
|
|
|
|
473
|
|
|
|
|
|
|
print __npx ("Files read from filesystems", |
|
474
|
|
|
|
|
|
|
"one file read", |
|
475
|
|
|
|
|
|
|
"{num} files read", |
|
476
|
|
|
|
|
|
|
$num_files, |
|
477
|
|
|
|
|
|
|
num => $num_files); |
|
478
|
|
|
|
|
|
|
|
|
479
|
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
481
|
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
The module Locale::TextDomain(3pm) provides a high-level interface |
|
483
|
|
|
|
|
|
|
to Perl message translation. |
|
484
|
|
|
|
|
|
|
|
|
485
|
|
|
|
|
|
|
=head2 Textdomains |
|
486
|
|
|
|
|
|
|
|
|
487
|
|
|
|
|
|
|
When you request a translation for a given string, the system used |
|
488
|
|
|
|
|
|
|
in libintl-perl follows a standard strategy to find a suitable message |
|
489
|
|
|
|
|
|
|
catalog containing the translation: Unless you explicitely define |
|
490
|
|
|
|
|
|
|
a name for the message catalog, libintl-perl will assume that your |
|
491
|
|
|
|
|
|
|
catalog is called 'messages' (unless you have changed the default |
|
492
|
|
|
|
|
|
|
value to something else via Locale::Messages(3pm), method textdomain()). |
|
493
|
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
You might think that his default strategy leaves room for optimization |
|
495
|
|
|
|
|
|
|
and you are right. It would be a lot smarter if multiple software |
|
496
|
|
|
|
|
|
|
packages, all with their individual message catalogs, could be installed |
|
497
|
|
|
|
|
|
|
on one system, and it should also be possible that third-party |
|
498
|
|
|
|
|
|
|
components of your software (like Perl modules) can load their |
|
499
|
|
|
|
|
|
|
message catalogs, too, without interfering with yours. |
|
500
|
|
|
|
|
|
|
|
|
501
|
|
|
|
|
|
|
The solution is clear, you have to assign a unique name to your message |
|
502
|
|
|
|
|
|
|
database, and you have to specify that name at run-time. That unique |
|
503
|
|
|
|
|
|
|
name is the so-called I<textdomain> of your software package. The name is |
|
504
|
|
|
|
|
|
|
actually arbitrary but you should follow these best-practice guidelines |
|
505
|
|
|
|
|
|
|
to ensure maximum interoperability: |
|
506
|
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
=over 8 |
|
508
|
|
|
|
|
|
|
|
|
509
|
|
|
|
|
|
|
=item File System Safety |
|
510
|
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
In practice, textdomains get mapped into file names, and you should |
|
512
|
|
|
|
|
|
|
therefore make sure that the textdomain you choose is a valid filename |
|
513
|
|
|
|
|
|
|
on every system that will run your software. |
|
514
|
|
|
|
|
|
|
|
|
515
|
|
|
|
|
|
|
=item Case-sensitivity |
|
516
|
|
|
|
|
|
|
|
|
517
|
|
|
|
|
|
|
Textdomains are always case-sensitive (i. e. 'Package' and 'PACKAGE' |
|
518
|
|
|
|
|
|
|
are not the same). However, since the message catalogs will be stored |
|
519
|
|
|
|
|
|
|
on file systems, that may or may not distinguish case when looking |
|
520
|
|
|
|
|
|
|
up file names, you should avoid potential conflicts here. |
|
521
|
|
|
|
|
|
|
|
|
522
|
|
|
|
|
|
|
=item Textdomain Should Match CPAN Name |
|
523
|
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
If your software is listed as a module on CPAN, you should simply |
|
525
|
|
|
|
|
|
|
choose the name on CPAN as your textdomain. The textdomain for |
|
526
|
|
|
|
|
|
|
libintl-perl is hence 'libintl-perl'. But please replace all |
|
527
|
|
|
|
|
|
|
periods ('.') in your package name with an underscore because ... |
|
528
|
|
|
|
|
|
|
|
|
529
|
|
|
|
|
|
|
=item Internet Domain Names as a Fallback |
|
530
|
|
|
|
|
|
|
|
|
531
|
|
|
|
|
|
|
... if your software is I<not> a module listed on CPAN, as a last |
|
532
|
|
|
|
|
|
|
resort you should use the Java(tm) package scheme, i. e. choose |
|
533
|
|
|
|
|
|
|
an internet domain that you are owner of (or ask the owner of an |
|
534
|
|
|
|
|
|
|
internet domain) and concatenate your preferred textdomain with the |
|
535
|
|
|
|
|
|
|
reversed internet domain. Example: Your company runs the web-site |
|
536
|
|
|
|
|
|
|
'www.foobar.org' and is the owner of the domain 'foobar.org'. The |
|
537
|
|
|
|
|
|
|
textdomain for your company's software 'barfoos' should hence be |
|
538
|
|
|
|
|
|
|
'org.foobar.barfoos'. |
|
539
|
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
=back |
|
541
|
|
|
|
|
|
|
|
|
542
|
|
|
|
|
|
|
If your software is likely to be installed in different versions on |
|
543
|
|
|
|
|
|
|
the same system, it is probably a good idea to append some version |
|
544
|
|
|
|
|
|
|
information to your textdomain. |
|
545
|
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
Other systems are less strict with the naming scheme for textdomains |
|
547
|
|
|
|
|
|
|
but the phenomena known as Perl is actually a plethora of small, |
|
548
|
|
|
|
|
|
|
specialized modules and it is probably wisest to postulate some |
|
549
|
|
|
|
|
|
|
namespace model in order to avoid chaos. |
|
550
|
|
|
|
|
|
|
|
|
551
|
|
|
|
|
|
|
=head2 Binding textdomains to directories |
|
552
|
|
|
|
|
|
|
|
|
553
|
|
|
|
|
|
|
Once the system knows the I<textdomain> of the message that you |
|
554
|
|
|
|
|
|
|
want to get translated into the user's language, it still has to |
|
555
|
|
|
|
|
|
|
find the correct message catalog. By default, libintl-perl will |
|
556
|
|
|
|
|
|
|
look up the string in the translation database found in the |
|
557
|
|
|
|
|
|
|
directories F</usr/share/locale> and F</usr/local/share/locale> |
|
558
|
|
|
|
|
|
|
(in that order). |
|
559
|
|
|
|
|
|
|
|
|
560
|
|
|
|
|
|
|
It is neither guaranteed that these directories exist on the target |
|
561
|
|
|
|
|
|
|
machine, nor can you be sure that the installation routine has write |
|
562
|
|
|
|
|
|
|
access to these locations. You can therefore instruct libintl-perl |
|
563
|
|
|
|
|
|
|
to search other directories prior to the default directories. Specifying |
|
564
|
|
|
|
|
|
|
a differnt search directory is called I<binding> a textdomain to a |
|
565
|
|
|
|
|
|
|
directory. |
|
566
|
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
Beginning with version 1.20, B<Locale::TextDomain> extends the default |
|
568
|
|
|
|
|
|
|
strategy by a Perl-specific approach. If L<File::ShareDir> is installed, it |
|
569
|
|
|
|
|
|
|
will look in the subdirectories named F<locale> and F<LocaleData> (in that |
|
570
|
|
|
|
|
|
|
order) in the directory returned by C<File::ShareDir::dist_dir ($textdomain)> |
|
571
|
|
|
|
|
|
|
(if L<File::ShareDir> is installed), |
|
572
|
|
|
|
|
|
|
and check for a database containing the message for your textdomain there. |
|
573
|
|
|
|
|
|
|
This allows you to install your database in the Perl-specific shared directory |
|
574
|
|
|
|
|
|
|
using L<Module::Install>'s C<install_share> directive or the Dist::Zilla |
|
575
|
|
|
|
|
|
|
L<ShareDir plugin|Dist::Zilla::Plugin::ShareDir>. |
|
576
|
|
|
|
|
|
|
|
|
577
|
|
|
|
|
|
|
If L<File::ShareDir> is not availabe, or if Locale::TextDomain fails to find |
|
578
|
|
|
|
|
|
|
the translation files in the L<File::ShareDir> directory, it will next look in |
|
579
|
|
|
|
|
|
|
every directory found in the standard include path C<@INC>, and check for a |
|
580
|
|
|
|
|
|
|
database containing the message for your textdomain there. Example: If the |
|
581
|
|
|
|
|
|
|
path F</usr/lib/perl/5.8.0/site_perl> is in your C<@INC>, you can install your |
|
582
|
|
|
|
|
|
|
translation files in F</usr/lib/perl/5.8.0/site_perl/LocaleData>, and they |
|
583
|
|
|
|
|
|
|
will be found at run-time. |
|
584
|
|
|
|
|
|
|
|
|
585
|
|
|
|
|
|
|
=head1 USAGE |
|
586
|
|
|
|
|
|
|
|
|
587
|
|
|
|
|
|
|
It is crucial to remember that you use Locale::TextDomain(3) as |
|
588
|
|
|
|
|
|
|
specified in the section L</SYNOPSIS>, that means you have to |
|
589
|
|
|
|
|
|
|
B<use> it, not B<require> it. The module behaves quite differently |
|
590
|
|
|
|
|
|
|
compared to other modules. |
|
591
|
|
|
|
|
|
|
|
|
592
|
|
|
|
|
|
|
The most significant difference is the meaning of the list passed |
|
593
|
|
|
|
|
|
|
as an argument to the use() function. It actually works like this: |
|
594
|
|
|
|
|
|
|
|
|
595
|
|
|
|
|
|
|
use Locale::TextDomain (TEXTDOMAIN, DIRECTORY, ...) |
|
596
|
|
|
|
|
|
|
|
|
597
|
|
|
|
|
|
|
The first argument (the first string passed to use()) is the textdomain |
|
598
|
|
|
|
|
|
|
of your package, optionally followed by a list of directories to search |
|
599
|
|
|
|
|
|
|
I<instead> of the Perl-specific directories (see above: F</LocaleData> |
|
600
|
|
|
|
|
|
|
appended to a F<File::ShareDir> directory and every path in C<@INC>). |
|
601
|
|
|
|
|
|
|
|
|
602
|
|
|
|
|
|
|
If you are the author of a package 'barfoos', you will probably put |
|
603
|
|
|
|
|
|
|
the line |
|
604
|
|
|
|
|
|
|
|
|
605
|
|
|
|
|
|
|
use Locale::TextDomain 'barfoos'; |
|
606
|
|
|
|
|
|
|
|
|
607
|
|
|
|
|
|
|
resp. for non-CPAN modules |
|
608
|
|
|
|
|
|
|
|
|
609
|
|
|
|
|
|
|
use Locale::TextDomain 'org.foobar.barfoos'; |
|
610
|
|
|
|
|
|
|
|
|
611
|
|
|
|
|
|
|
in every module of your package that contains translatable strings. If |
|
612
|
|
|
|
|
|
|
your module has been installed properly, including the message catalogs, |
|
613
|
|
|
|
|
|
|
it will then be able to retrieve these translations at run-time. |
|
614
|
|
|
|
|
|
|
|
|
615
|
|
|
|
|
|
|
If you have not installed the translation database in a directory |
|
616
|
|
|
|
|
|
|
F<LocaleData> in the L<File::ShareDir> directory or the standard include |
|
617
|
|
|
|
|
|
|
path C<@INC> (or in the system directories F</usr/share/locale> resp. |
|
618
|
|
|
|
|
|
|
F</usr/local/share/locale>), you have to explicitely specify a search |
|
619
|
|
|
|
|
|
|
path by giving the names of directories (as strings!) as additional |
|
620
|
|
|
|
|
|
|
arguments to use(): |
|
621
|
|
|
|
|
|
|
|
|
622
|
|
|
|
|
|
|
use Locale::TextDomain qw (barfoos ./dir1 ./dir2); |
|
623
|
|
|
|
|
|
|
|
|
624
|
|
|
|
|
|
|
Alternatively you can call the function bindtextdomain() with suitable |
|
625
|
|
|
|
|
|
|
arguments (see the entry for bindtextdomain() in |
|
626
|
|
|
|
|
|
|
L<Locale::Messages/FUNCTIONS>). If you do so, you should pass |
|
627
|
|
|
|
|
|
|
C<undef> as an additional argument in order to avoid unnecessary |
|
628
|
|
|
|
|
|
|
lookups: |
|
629
|
|
|
|
|
|
|
|
|
630
|
|
|
|
|
|
|
use Locale::TextDomain ('barfoos', undef); |
|
631
|
|
|
|
|
|
|
|
|
632
|
|
|
|
|
|
|
You see that the arguments given to use() have nothing to do with |
|
633
|
|
|
|
|
|
|
what is imported into your namespace, but they are rather arguments |
|
634
|
|
|
|
|
|
|
to textdomain(), resp. bindtextdomain(). Does that mean that |
|
635
|
|
|
|
|
|
|
B<Locale::TextDomain> exports nothing into your namespace? Umh, not |
|
636
|
|
|
|
|
|
|
exactly ... in fact it imports I<all> functions listed below into |
|
637
|
|
|
|
|
|
|
your namespace, and hence you should not define conflicting functions |
|
638
|
|
|
|
|
|
|
(and variables) yourself. |
|
639
|
|
|
|
|
|
|
|
|
640
|
|
|
|
|
|
|
So, why has Locale::TextDomain to be different from other modules? |
|
641
|
|
|
|
|
|
|
If you have ever written software in C and prepared it for |
|
642
|
|
|
|
|
|
|
internationalization (i18n), you will probably have defined some |
|
643
|
|
|
|
|
|
|
preprocessor macros like: |
|
644
|
|
|
|
|
|
|
|
|
645
|
|
|
|
|
|
|
#define _(String) dgettext ("my-textdomain", String) |
|
646
|
|
|
|
|
|
|
#define N_(String) String |
|
647
|
|
|
|
|
|
|
|
|
648
|
|
|
|
|
|
|
You only have to define that once in C, and the textdomain for your |
|
649
|
|
|
|
|
|
|
package is automatically inserted into all gettext functions. In |
|
650
|
|
|
|
|
|
|
Perl there is no such mechanism (at least it is not portable, |
|
651
|
|
|
|
|
|
|
option -P) and using the gettext functions could become quite |
|
652
|
|
|
|
|
|
|
cumbersome without some extra fiddling: |
|
653
|
|
|
|
|
|
|
|
|
654
|
|
|
|
|
|
|
print dgettext ("my-textdomain", "Hello world!\n"); |
|
655
|
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
This is no fun. In C it would merely be a |
|
657
|
|
|
|
|
|
|
|
|
658
|
|
|
|
|
|
|
printf (_("Hello world!\n")); |
|
659
|
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
Perl has to be more concise and shorter than C ... see the next |
|
661
|
|
|
|
|
|
|
section for how you can use B<Locale::TextDomain> to end up in Perl |
|
662
|
|
|
|
|
|
|
with a mere |
|
663
|
|
|
|
|
|
|
|
|
664
|
|
|
|
|
|
|
print __"Hello World!\n"; |
|
665
|
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
=head1 EXPORTED FUNCTIONS |
|
667
|
|
|
|
|
|
|
|
|
668
|
|
|
|
|
|
|
All functions have quite funny names on purpose. In fact the |
|
669
|
|
|
|
|
|
|
purpose for that is quite clear: They should be short, operator-like, |
|
670
|
|
|
|
|
|
|
and they should not yell for conflicts with existing functions in |
|
671
|
|
|
|
|
|
|
I<your> namespace. You will understand it, when you internationalize |
|
672
|
|
|
|
|
|
|
your first Perl program or module. Preparing it is more like marking |
|
673
|
|
|
|
|
|
|
strings as being translatable than inserting function calls. Here |
|
674
|
|
|
|
|
|
|
we go: |
|
675
|
|
|
|
|
|
|
|
|
676
|
|
|
|
|
|
|
=over 4 |
|
677
|
|
|
|
|
|
|
|
|
678
|
|
|
|
|
|
|
=item B<__ MSGID> |
|
679
|
|
|
|
|
|
|
|
|
680
|
|
|
|
|
|
|
B<NOTE:> This is a I<double> underscore! |
|
681
|
|
|
|
|
|
|
|
|
682
|
|
|
|
|
|
|
The basic and most-used function. It is a short-cut for a call |
|
683
|
|
|
|
|
|
|
to gettext() resp. dgettext(), and simply returns the translation for |
|
684
|
|
|
|
|
|
|
B<MSGID>. If your old code reads like this: |
|
685
|
|
|
|
|
|
|
|
|
686
|
|
|
|
|
|
|
print "permission denied"; |
|
687
|
|
|
|
|
|
|
|
|
688
|
|
|
|
|
|
|
You will now write: |
|
689
|
|
|
|
|
|
|
|
|
690
|
|
|
|
|
|
|
print __"permission denied"; |
|
691
|
|
|
|
|
|
|
|
|
692
|
|
|
|
|
|
|
That's all, the string will be output in the user's preferred language, |
|
693
|
|
|
|
|
|
|
provided that you have installed a translation for it. |
|
694
|
|
|
|
|
|
|
|
|
695
|
|
|
|
|
|
|
Of course you can also use parentheses: |
|
696
|
|
|
|
|
|
|
|
|
697
|
|
|
|
|
|
|
print __("permission denied"); |
|
698
|
|
|
|
|
|
|
|
|
699
|
|
|
|
|
|
|
Or even: |
|
700
|
|
|
|
|
|
|
|
|
701
|
|
|
|
|
|
|
print (__("permission denied")); |
|
702
|
|
|
|
|
|
|
|
|
703
|
|
|
|
|
|
|
In my eyes, the first version without parentheses looks best. |
|
704
|
|
|
|
|
|
|
|
|
705
|
|
|
|
|
|
|
=item B<__x MSGID, ID1 =E<gt> VAL1, ID2 =E<gt> VAL2, ...> |
|
706
|
|
|
|
|
|
|
|
|
707
|
|
|
|
|
|
|
One of the nicest features in Perl is its capability to interpolate |
|
708
|
|
|
|
|
|
|
variables into strings: |
|
709
|
|
|
|
|
|
|
|
|
710
|
|
|
|
|
|
|
print "This is the $color $thing.\n"; |
|
711
|
|
|
|
|
|
|
|
|
712
|
|
|
|
|
|
|
This nice feature might con you into thinking that you could now |
|
713
|
|
|
|
|
|
|
write |
|
714
|
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
print __"This is the $color $thing.\n"; |
|
716
|
|
|
|
|
|
|
|
|
717
|
|
|
|
|
|
|
Alas, that would be nice, but it is not possible. Remember that the |
|
718
|
|
|
|
|
|
|
function __() serves both as an operator for translating strings |
|
719
|
|
|
|
|
|
|
I<and> as a mark for translatable strings. If the above string would |
|
720
|
|
|
|
|
|
|
get extracted from your Perl code, the un-interpolated form would |
|
721
|
|
|
|
|
|
|
end up in the message catalog because when parsing your code it |
|
722
|
|
|
|
|
|
|
is unpredictable what values the variables C<$thing> and C<$color> |
|
723
|
|
|
|
|
|
|
will have at run-time (this fact is most probably one of the reasons |
|
724
|
|
|
|
|
|
|
you have written your program for). |
|
725
|
|
|
|
|
|
|
|
|
726
|
|
|
|
|
|
|
However, at run-time, Perl will have interpolated the values already |
|
727
|
|
|
|
|
|
|
I<before> __() (resp. the underlying gettext() function) has seen the |
|
728
|
|
|
|
|
|
|
original string. Consequently something like "This is the red car.\n" |
|
729
|
|
|
|
|
|
|
will be looked up in the message catalog, it will not be found (because |
|
730
|
|
|
|
|
|
|
only "This is the $color $thing.\n" is included in the database), |
|
731
|
|
|
|
|
|
|
and the original, untranslated string will be returned. |
|
732
|
|
|
|
|
|
|
Honestly, because this is almost always an error, the xgettext(1) |
|
733
|
|
|
|
|
|
|
program will bail out with a fatal error when it comes across that |
|
734
|
|
|
|
|
|
|
string in your code. |
|
735
|
|
|
|
|
|
|
|
|
736
|
|
|
|
|
|
|
There are two workarounds for that: |
|
737
|
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
printf __"This is the %s %s.\n", $color, $thing; |
|
739
|
|
|
|
|
|
|
|
|
740
|
|
|
|
|
|
|
But that has several disadvantages: Your translator will only |
|
741
|
|
|
|
|
|
|
see the isolated string, and without the surrounding code it |
|
742
|
|
|
|
|
|
|
is almost impossible to interpret it correctly. Of course, GNU |
|
743
|
|
|
|
|
|
|
emacs and other software capable of editing PO translation files |
|
744
|
|
|
|
|
|
|
will allow you to examine the context in the source code, but it |
|
745
|
|
|
|
|
|
|
is more likely that your translator will look for a less challenging |
|
746
|
|
|
|
|
|
|
translation project when she frequently comes across such messages. |
|
747
|
|
|
|
|
|
|
|
|
748
|
|
|
|
|
|
|
And even if she does understand the underlying programming, what |
|
749
|
|
|
|
|
|
|
if she has to reorder the color and the thing like in French: |
|
750
|
|
|
|
|
|
|
|
|
751
|
|
|
|
|
|
|
msgid "This is the red car.\n"; |
|
752
|
|
|
|
|
|
|
msgstr "Cela est la voiture rouge.\n" |
|
753
|
|
|
|
|
|
|
|
|
754
|
|
|
|
|
|
|
Zut alors! While it is possible to reorder the arguments to printf() |
|
755
|
|
|
|
|
|
|
and friends, it requires a syntax that is is nothing that you want to |
|
756
|
|
|
|
|
|
|
learn. |
|
757
|
|
|
|
|
|
|
|
|
758
|
|
|
|
|
|
|
So what? The Perl backend to GNU gettext has defined an alternative |
|
759
|
|
|
|
|
|
|
format for interpolatable strings: |
|
760
|
|
|
|
|
|
|
|
|
761
|
|
|
|
|
|
|
"This is the {color} {thing}.\n"; |
|
762
|
|
|
|
|
|
|
|
|
763
|
|
|
|
|
|
|
Instead of Perl variables you use place-holders (legal Perl variables |
|
764
|
|
|
|
|
|
|
are also legal place-holders) in curly braces, and then you call |
|
765
|
|
|
|
|
|
|
|
|
766
|
|
|
|
|
|
|
print __x ("This is the {color} {thing}.\n", |
|
767
|
|
|
|
|
|
|
thing => $thang, |
|
768
|
|
|
|
|
|
|
color => $color); |
|
769
|
|
|
|
|
|
|
|
|
770
|
|
|
|
|
|
|
The function __x() will take the additional hash and replace all |
|
771
|
|
|
|
|
|
|
occurencies of the hash keys in curly braces with the corresponding |
|
772
|
|
|
|
|
|
|
values. Simple, readable, understandable to translators, what else |
|
773
|
|
|
|
|
|
|
would you want? And if the translator forgets, misspells or otherwise |
|
774
|
|
|
|
|
|
|
messes up some "variables", the msgfmt(1) program, that is used to |
|
775
|
|
|
|
|
|
|
compile the textual translation file into its binary representation |
|
776
|
|
|
|
|
|
|
will even choke on these errors and refuse to compile the translation. |
|
777
|
|
|
|
|
|
|
|
|
778
|
|
|
|
|
|
|
=item B<__n MSGID, MSGID_PLURAL, COUNT> |
|
779
|
|
|
|
|
|
|
|
|
780
|
|
|
|
|
|
|
Whew! That looks complicated ... It is best explained with an example. |
|
781
|
|
|
|
|
|
|
We'll have another look at your vintage code: |
|
782
|
|
|
|
|
|
|
|
|
783
|
|
|
|
|
|
|
if ($files_deleted > 1) { |
|
784
|
|
|
|
|
|
|
print "All files have been deleted.\n"; |
|
785
|
|
|
|
|
|
|
} else { |
|
786
|
|
|
|
|
|
|
print "One file has been deleted.\n"; |
|
787
|
|
|
|
|
|
|
} |
|
788
|
|
|
|
|
|
|
|
|
789
|
|
|
|
|
|
|
Your intent is clear, you wanted to avoid the cumbersome |
|
790
|
|
|
|
|
|
|
"1 files deleted". This is okay for English, but other languages |
|
791
|
|
|
|
|
|
|
have more than one plural form. For example in Russian it makes |
|
792
|
|
|
|
|
|
|
a difference whether you want to say 1 file, 3 files or 6 files. |
|
793
|
|
|
|
|
|
|
You will use three different forms of the noun 'file' in each |
|
794
|
|
|
|
|
|
|
case. [Note: Yep, very smart you are, the Russian word for 'file' |
|
795
|
|
|
|
|
|
|
is in fact the English word, and it is an invariable noun, but if you |
|
796
|
|
|
|
|
|
|
know that, you will also understand the rest despite this little |
|
797
|
|
|
|
|
|
|
simplification ...]. |
|
798
|
|
|
|
|
|
|
|
|
799
|
|
|
|
|
|
|
That is the reason for the existance of the function ngettext(), |
|
800
|
|
|
|
|
|
|
that __n() is a short-cut for: |
|
801
|
|
|
|
|
|
|
|
|
802
|
|
|
|
|
|
|
print __n"One file has been deleted.\n", |
|
803
|
|
|
|
|
|
|
"All files have been deleted.\n", |
|
804
|
|
|
|
|
|
|
$files_deleted; |
|
805
|
|
|
|
|
|
|
|
|
806
|
|
|
|
|
|
|
Alternatively: |
|
807
|
|
|
|
|
|
|
|
|
808
|
|
|
|
|
|
|
print __n ("One file has been deleted.\n", |
|
809
|
|
|
|
|
|
|
"All files have been deleted.\n", |
|
810
|
|
|
|
|
|
|
$files_deleted); |
|
811
|
|
|
|
|
|
|
|
|
812
|
|
|
|
|
|
|
The effect is always the same: libintl-perl will find out which |
|
813
|
|
|
|
|
|
|
plural form to pick for your user's language, and the output string |
|
814
|
|
|
|
|
|
|
will always look okay. |
|
815
|
|
|
|
|
|
|
|
|
816
|
|
|
|
|
|
|
=item B<__nx MSGID, MSGID_PLURAL, COUNT, VAR1 =E<gt> VAL1, VAR2 =E<gt> VAL2, ...> |
|
817
|
|
|
|
|
|
|
|
|
818
|
|
|
|
|
|
|
Bringing it all together: |
|
819
|
|
|
|
|
|
|
|
|
820
|
|
|
|
|
|
|
print __nx ("One file has been deleted.\n", |
|
821
|
|
|
|
|
|
|
"{count} files have been deleted.\n", |
|
822
|
|
|
|
|
|
|
$num_files, |
|
823
|
|
|
|
|
|
|
count => $num_files); |
|
824
|
|
|
|
|
|
|
|
|
825
|
|
|
|
|
|
|
The function __nx() picks the correct plural form (also for English!) |
|
826
|
|
|
|
|
|
|
I<and> it is capable of interpolating variables into strings. |
|
827
|
|
|
|
|
|
|
|
|
828
|
|
|
|
|
|
|
Have a close look at the order of arguments: The first argument is the |
|
829
|
|
|
|
|
|
|
string in the singular, the second one is the plural string. The third |
|
830
|
|
|
|
|
|
|
one is an integer indicating the number of items. This third argument |
|
831
|
|
|
|
|
|
|
is I<only> used to pick the correct translation. The optionally |
|
832
|
|
|
|
|
|
|
following arguments make up the hash used for interpolation. In the |
|
833
|
|
|
|
|
|
|
beginning it is often a little confusing that the variable holding the |
|
834
|
|
|
|
|
|
|
number of items will usually be repeated somewhere in the interpolation |
|
835
|
|
|
|
|
|
|
hash. |
|
836
|
|
|
|
|
|
|
|
|
837
|
|
|
|
|
|
|
=item B<__xn MSGID, MSGID_PLURAL, COUNT, VAR1 =E<gt> VAL1, VAR2 =E<gt> VAL2, ...> |
|
838
|
|
|
|
|
|
|
|
|
839
|
|
|
|
|
|
|
Does exactly the same thing as __nx(). In fact it is a common typo |
|
840
|
|
|
|
|
|
|
promoted to a feature. |
|
841
|
|
|
|
|
|
|
|
|
842
|
|
|
|
|
|
|
=item B<__p MSGCTXT, MSGID> |
|
843
|
|
|
|
|
|
|
|
|
844
|
|
|
|
|
|
|
This is much like __. The "p" stands for "particular", and the MSGCTXT |
|
845
|
|
|
|
|
|
|
is used to provide context to the translator. This may be neccessary |
|
846
|
|
|
|
|
|
|
when your string is short, and could stand for multiple things. For example: |
|
847
|
|
|
|
|
|
|
|
|
848
|
|
|
|
|
|
|
print __p"Verb, to view", "View"; |
|
849
|
|
|
|
|
|
|
print __p"Noun, a view", "View"; |
|
850
|
|
|
|
|
|
|
|
|
851
|
|
|
|
|
|
|
The above may be "View" entries in a menu, where View->Source and File->View |
|
852
|
|
|
|
|
|
|
are different forms of "View", and likely need to be translated differently. |
|
853
|
|
|
|
|
|
|
|
|
854
|
|
|
|
|
|
|
A typical usage are GUI programs. Imagine a program with a main |
|
855
|
|
|
|
|
|
|
menu and the notorious "Open" entry in the "File" menu. Now imagine, |
|
856
|
|
|
|
|
|
|
there is another menu entry Preferences->Advanced->Policy where you have |
|
857
|
|
|
|
|
|
|
a choice between the alternatives "Open" and "Closed". In English, "Open" |
|
858
|
|
|
|
|
|
|
is the adequate text at both places. In other languages, it is very |
|
859
|
|
|
|
|
|
|
likely that you need two different translations. Therefore, you would |
|
860
|
|
|
|
|
|
|
now write: |
|
861
|
|
|
|
|
|
|
|
|
862
|
|
|
|
|
|
|
__p"File|", "Open"; |
|
863
|
|
|
|
|
|
|
__p"Preferences|Advanced|Policy", "Open"; |
|
864
|
|
|
|
|
|
|
|
|
865
|
|
|
|
|
|
|
In English, or if no translation can be found, the second argument |
|
866
|
|
|
|
|
|
|
(MSGID) is returned. |
|
867
|
|
|
|
|
|
|
|
|
868
|
|
|
|
|
|
|
This function was introduced in libintl-perl 1.17. |
|
869
|
|
|
|
|
|
|
|
|
870
|
|
|
|
|
|
|
=item B<__px MSGCTXT, MSGID, VAR1 =E<gt> VAL1, VAR2 =E<gt> VAL2, ...> |
|
871
|
|
|
|
|
|
|
|
|
872
|
|
|
|
|
|
|
Like __p(), but supports variable substitution in the string, like __x(). |
|
873
|
|
|
|
|
|
|
|
|
874
|
|
|
|
|
|
|
print __px("Verb, to view", "View {file}", file => $filename); |
|
875
|
|
|
|
|
|
|
|
|
876
|
|
|
|
|
|
|
See __p() and __x() for more details. |
|
877
|
|
|
|
|
|
|
|
|
878
|
|
|
|
|
|
|
This function was introduced in libintl-perl 1.17. |
|
879
|
|
|
|
|
|
|
|
|
880
|
|
|
|
|
|
|
=item B<__np MSGCTXT, MSGID, MSGID_PLURAL, COUNT> |
|
881
|
|
|
|
|
|
|
|
|
882
|
|
|
|
|
|
|
This adds context to plural calls. It should not be needed very often, |
|
883
|
|
|
|
|
|
|
if at all, due to the __nx() function. The type of variable substitution |
|
884
|
|
|
|
|
|
|
used in other gettext libraries (using sprintf-like sybols, like %s or %1) |
|
885
|
|
|
|
|
|
|
sometimes required context. For a (bad) example of this: |
|
886
|
|
|
|
|
|
|
|
|
887
|
|
|
|
|
|
|
printf (__np("[count] files have been deleted", |
|
888
|
|
|
|
|
|
|
"One file has been deleted.\n", |
|
889
|
|
|
|
|
|
|
"%s files have been deleted.\n", |
|
890
|
|
|
|
|
|
|
$num_files), |
|
891
|
|
|
|
|
|
|
$num_files); |
|
892
|
|
|
|
|
|
|
|
|
893
|
|
|
|
|
|
|
NOTE: The above usage is discouraged. Just use the __nx() call, which |
|
894
|
|
|
|
|
|
|
provides inline context via the key names. |
|
895
|
|
|
|
|
|
|
|
|
896
|
|
|
|
|
|
|
This function was introduced in libintl-perl 1.17. |
|
897
|
|
|
|
|
|
|
|
|
898
|
|
|
|
|
|
|
=item B<__npx MSGCTXT, MSGID, MSGID_PLURAL, COUNT, VAR1 =E<gt> VAL1, VAR2 =E<gt> VAL2, ...> |
|
899
|
|
|
|
|
|
|
|
|
900
|
|
|
|
|
|
|
This is provided for comleteness. It adds the variable interpolation |
|
901
|
|
|
|
|
|
|
into the string to the previous method, __np(). |
|
902
|
|
|
|
|
|
|
|
|
903
|
|
|
|
|
|
|
It's usage would be like so: |
|
904
|
|
|
|
|
|
|
|
|
905
|
|
|
|
|
|
|
print __npx ("Files being permenantly removed", |
|
906
|
|
|
|
|
|
|
"One file has been deleted.\n", |
|
907
|
|
|
|
|
|
|
"{count} files have been deleted.\n", |
|
908
|
|
|
|
|
|
|
$num_files, |
|
909
|
|
|
|
|
|
|
count => $num_files); |
|
910
|
|
|
|
|
|
|
|
|
911
|
|
|
|
|
|
|
I cannot think of any situations requiring this, but we can easily |
|
912
|
|
|
|
|
|
|
support it, so here it is. |
|
913
|
|
|
|
|
|
|
|
|
914
|
|
|
|
|
|
|
This function was introduced in libintl-perl 1.17. |
|
915
|
|
|
|
|
|
|
|
|
916
|
|
|
|
|
|
|
=item B<N__(ARG1)> |
|
917
|
|
|
|
|
|
|
|
|
918
|
|
|
|
|
|
|
A no-op function that simply echoes its arguments to the caller. Take |
|
919
|
|
|
|
|
|
|
the following piece of Perl: |
|
920
|
|
|
|
|
|
|
|
|
921
|
|
|
|
|
|
|
my @options = ( |
|
922
|
|
|
|
|
|
|
"Open", |
|
923
|
|
|
|
|
|
|
"Save", |
|
924
|
|
|
|
|
|
|
"Save As", |
|
925
|
|
|
|
|
|
|
); |
|
926
|
|
|
|
|
|
|
|
|
927
|
|
|
|
|
|
|
... |
|
928
|
|
|
|
|
|
|
|
|
929
|
|
|
|
|
|
|
my $option = $options[1]; |
|
930
|
|
|
|
|
|
|
|
|
931
|
|
|
|
|
|
|
Now say that you want to have this translatable. You could sometimes |
|
932
|
|
|
|
|
|
|
simply do: |
|
933
|
|
|
|
|
|
|
|
|
934
|
|
|
|
|
|
|
my @options = ( |
|
935
|
|
|
|
|
|
|
__"Open", |
|
936
|
|
|
|
|
|
|
__"Save", |
|
937
|
|
|
|
|
|
|
__"Save As", |
|
938
|
|
|
|
|
|
|
); |
|
939
|
|
|
|
|
|
|
|
|
940
|
|
|
|
|
|
|
... |
|
941
|
|
|
|
|
|
|
|
|
942
|
|
|
|
|
|
|
my $option = $options[1]; |
|
943
|
|
|
|
|
|
|
|
|
944
|
|
|
|
|
|
|
But often times this will not be what you want, for example when you |
|
945
|
|
|
|
|
|
|
also need the unmodified original string. Sometimes it may not even |
|
946
|
|
|
|
|
|
|
work, for example, when the preferred user language is not yet |
|
947
|
|
|
|
|
|
|
determined at the time that the list is initialized. |
|
948
|
|
|
|
|
|
|
|
|
949
|
|
|
|
|
|
|
In these cases you would write: |
|
950
|
|
|
|
|
|
|
|
|
951
|
|
|
|
|
|
|
my @options = ( |
|
952
|
|
|
|
|
|
|
N__"Open", |
|
953
|
|
|
|
|
|
|
N__"Save", |
|
954
|
|
|
|
|
|
|
N__"Save As", |
|
955
|
|
|
|
|
|
|
); |
|
956
|
|
|
|
|
|
|
|
|
957
|
|
|
|
|
|
|
... |
|
958
|
|
|
|
|
|
|
|
|
959
|
|
|
|
|
|
|
my $option = __($options[1]); |
|
960
|
|
|
|
|
|
|
# or: my $option = dgettext ('my-domain', $options[1]); |
|
961
|
|
|
|
|
|
|
|
|
962
|
|
|
|
|
|
|
Now all the strings in C<@options> will be left alone, since N__() |
|
963
|
|
|
|
|
|
|
returns its arguments (one ore more) unmodified. Nevertheless, the |
|
964
|
|
|
|
|
|
|
string extractor will be able to recognize the strings as being |
|
965
|
|
|
|
|
|
|
translatable. And you can still get the translation later by passing |
|
966
|
|
|
|
|
|
|
the variable instead of the string to one of the above translation |
|
967
|
|
|
|
|
|
|
functions. |
|
968
|
|
|
|
|
|
|
|
|
969
|
|
|
|
|
|
|
=item B<N__n (MSGID, MSGID_PLURAL, COUNT)> |
|
970
|
|
|
|
|
|
|
|
|
971
|
|
|
|
|
|
|
Does exactly the same as N__(). You will use this form if you have |
|
972
|
|
|
|
|
|
|
to mark the strings as having plural forms. |
|
973
|
|
|
|
|
|
|
|
|
974
|
|
|
|
|
|
|
=item B<N__p (MSGCTXT, MSGID)> |
|
975
|
|
|
|
|
|
|
|
|
976
|
|
|
|
|
|
|
Marks B<MSGID> as N__() does, but in the context B<MSGCTXT>. |
|
977
|
|
|
|
|
|
|
|
|
978
|
|
|
|
|
|
|
=item B<N__np (MSGCTXT, MSGID, MSGID_PLURAL, COUNT)> |
|
979
|
|
|
|
|
|
|
|
|
980
|
|
|
|
|
|
|
Marks B<MSGID> as N__n() does, but in the context B<MSGCTXT>. |
|
981
|
|
|
|
|
|
|
|
|
982
|
|
|
|
|
|
|
=back |
|
983
|
|
|
|
|
|
|
|
|
984
|
|
|
|
|
|
|
=head1 EXPORTED VARIABLES |
|
985
|
|
|
|
|
|
|
|
|
986
|
|
|
|
|
|
|
The module exports several variables into your namespace: |
|
987
|
|
|
|
|
|
|
|
|
988
|
|
|
|
|
|
|
=over 4 |
|
989
|
|
|
|
|
|
|
|
|
990
|
|
|
|
|
|
|
=item B<%__> |
|
991
|
|
|
|
|
|
|
|
|
992
|
|
|
|
|
|
|
A tied hash. Its keys are your original messages, the values are |
|
993
|
|
|
|
|
|
|
their translations: |
|
994
|
|
|
|
|
|
|
|
|
995
|
|
|
|
|
|
|
my $title = "<h1>$__{'My Homepage'}</h1>"; |
|
996
|
|
|
|
|
|
|
|
|
997
|
|
|
|
|
|
|
This is much better for your translation team than |
|
998
|
|
|
|
|
|
|
|
|
999
|
|
|
|
|
|
|
my $title = __"<h1>My Homepage</h1>"; |
|
1000
|
|
|
|
|
|
|
|
|
1001
|
|
|
|
|
|
|
In the second case the HTML code will make it into the translation |
|
1002
|
|
|
|
|
|
|
database and your translators have to be aware of HTML syntax when |
|
1003
|
|
|
|
|
|
|
translating strings. |
|
1004
|
|
|
|
|
|
|
|
|
1005
|
|
|
|
|
|
|
B<Warning:> Do I<not> use this hash outside of double-quoted strings! |
|
1006
|
|
|
|
|
|
|
The code in the tied hash object relies on the correct working of |
|
1007
|
|
|
|
|
|
|
the function caller() (see "perldoc -f caller"), and this function |
|
1008
|
|
|
|
|
|
|
will report incorrect results if the tied hash value is the argument |
|
1009
|
|
|
|
|
|
|
to a function from another package, for example: |
|
1010
|
|
|
|
|
|
|
|
|
1011
|
|
|
|
|
|
|
my $result = Other::Package::do_it ($__{'Some string'}); |
|
1012
|
|
|
|
|
|
|
|
|
1013
|
|
|
|
|
|
|
The tied hash code will see "Other::Package" as the calling package, |
|
1014
|
|
|
|
|
|
|
instead of your own package. Consequently it will look up the message |
|
1015
|
|
|
|
|
|
|
in the wrong text domain. There is no workaround for this bug. |
|
1016
|
|
|
|
|
|
|
Therefore: |
|
1017
|
|
|
|
|
|
|
|
|
1018
|
|
|
|
|
|
|
Never use the tied hash interpolated strings! |
|
1019
|
|
|
|
|
|
|
|
|
1020
|
|
|
|
|
|
|
=item B<$__> |
|
1021
|
|
|
|
|
|
|
|
|
1022
|
|
|
|
|
|
|
A reference to C<%__>, in case you prefer: |
|
1023
|
|
|
|
|
|
|
|
|
1024
|
|
|
|
|
|
|
my $title = "<h1>$__->{'My Homepage'}</h1>"; |
|
1025
|
|
|
|
|
|
|
|
|
1026
|
|
|
|
|
|
|
=back |
|
1027
|
|
|
|
|
|
|
|
|
1028
|
|
|
|
|
|
|
=head1 CLASS METHODS |
|
1029
|
|
|
|
|
|
|
|
|
1030
|
|
|
|
|
|
|
The following class methods are defined: |
|
1031
|
|
|
|
|
|
|
|
|
1032
|
|
|
|
|
|
|
=over 4 |
|
1033
|
|
|
|
|
|
|
|
|
1034
|
|
|
|
|
|
|
=item B<options> |
|
1035
|
|
|
|
|
|
|
|
|
1036
|
|
|
|
|
|
|
Returns a space-separated list of all '--keyword' and all '--flag' options |
|
1037
|
|
|
|
|
|
|
for B<xgettext(1)>, when extracing strings from Perl source files localized |
|
1038
|
|
|
|
|
|
|
with B<Locale::TextDomain>. |
|
1039
|
|
|
|
|
|
|
|
|
1040
|
|
|
|
|
|
|
The option should rather be called B<xgettextDefaultOptions>. With regard |
|
1041
|
|
|
|
|
|
|
to the typical use-case, a shorter name has been picked: |
|
1042
|
|
|
|
|
|
|
|
|
1043
|
|
|
|
|
|
|
xgettext `perl -MLocale::TextDomain -e 'print Locale::TextDomain->options'` |
|
1044
|
|
|
|
|
|
|
|
|
1045
|
|
|
|
|
|
|
See L<https://www.gnu.org/software/gettext/manual/html_node/xgettext-Invocation.html> |
|
1046
|
|
|
|
|
|
|
for more information about the xgettext options '--keyword' and '--flag'. |
|
1047
|
|
|
|
|
|
|
|
|
1048
|
|
|
|
|
|
|
If you want to disable the use of the xgettext default keywords, you |
|
1049
|
|
|
|
|
|
|
should pass an option '--keyword=""' to xgettext before the options returned |
|
1050
|
|
|
|
|
|
|
by this method. |
|
1051
|
|
|
|
|
|
|
|
|
1052
|
|
|
|
|
|
|
If you doubt the usefulness of this method, check the output on the |
|
1053
|
|
|
|
|
|
|
command-line: |
|
1054
|
|
|
|
|
|
|
|
|
1055
|
|
|
|
|
|
|
perl -MLocale::TextDomain -e 'print Locale::TextDomain->options' |
|
1056
|
|
|
|
|
|
|
|
|
1057
|
|
|
|
|
|
|
Nothing that you want to type yourself. |
|
1058
|
|
|
|
|
|
|
|
|
1059
|
|
|
|
|
|
|
This method was added in libintl-perl 1.28. |
|
1060
|
|
|
|
|
|
|
|
|
1061
|
|
|
|
|
|
|
=item B<keywords> |
|
1062
|
|
|
|
|
|
|
|
|
1063
|
|
|
|
|
|
|
Returns a space-separated list of all '--keyword' options for B<xgettext(1)> |
|
1064
|
|
|
|
|
|
|
so that all translatable strings are properly extracted. |
|
1065
|
|
|
|
|
|
|
|
|
1066
|
|
|
|
|
|
|
This method was added in libintl-perl 1.28. |
|
1067
|
|
|
|
|
|
|
|
|
1068
|
|
|
|
|
|
|
=item B<flags> |
|
1069
|
|
|
|
|
|
|
|
|
1070
|
|
|
|
|
|
|
Returns a space-separated list of all '--flag' options for B<xgettext(1)> |
|
1071
|
|
|
|
|
|
|
so that extracted strings are properly flagged. |
|
1072
|
|
|
|
|
|
|
|
|
1073
|
|
|
|
|
|
|
This method was added in libintl-perl 1.28. |
|
1074
|
|
|
|
|
|
|
|
|
1075
|
|
|
|
|
|
|
=back |
|
1076
|
|
|
|
|
|
|
|
|
1077
|
|
|
|
|
|
|
=head1 PERFORMANCE |
|
1078
|
|
|
|
|
|
|
|
|
1079
|
|
|
|
|
|
|
Message translation can be a time-consuming task. Take this little |
|
1080
|
|
|
|
|
|
|
example: |
|
1081
|
|
|
|
|
|
|
|
|
1082
|
|
|
|
|
|
|
1: use Locale::TextDomain ('my-domain'); |
|
1083
|
|
|
|
|
|
|
2: use POSIX (:locale_h); |
|
1084
|
|
|
|
|
|
|
3: |
|
1085
|
|
|
|
|
|
|
4: setlocale (LC_ALL, ''); |
|
1086
|
|
|
|
|
|
|
5: print __"Hello world!\n"; |
|
1087
|
|
|
|
|
|
|
|
|
1088
|
|
|
|
|
|
|
This will usually be quite fast, but in pathological cases it may |
|
1089
|
|
|
|
|
|
|
run for several seconds. A worst-case scenario would be a |
|
1090
|
|
|
|
|
|
|
Chinese user at a terminal that understands the codeset Big5-HKSCS. |
|
1091
|
|
|
|
|
|
|
Your translator for Chinese has however chosen to encode the translations |
|
1092
|
|
|
|
|
|
|
in the codeset EUC-TW. |
|
1093
|
|
|
|
|
|
|
|
|
1094
|
|
|
|
|
|
|
What will happen at run-time? First, the library will search and load a |
|
1095
|
|
|
|
|
|
|
(maybe large) message catalog for your textdomain 'my-domain'. Then |
|
1096
|
|
|
|
|
|
|
it will look up the translation for "Hello world!\n", it will find that |
|
1097
|
|
|
|
|
|
|
it is encoded in EUC-TW. Since that differs from the output codeset |
|
1098
|
|
|
|
|
|
|
Big5-HKSCS, it will first load a conversion table containing several |
|
1099
|
|
|
|
|
|
|
ten-thousands of codepoints for EUC-TW, then it does the same with |
|
1100
|
|
|
|
|
|
|
the smaller, but still very large conversion table for Big5-HKSCS, |
|
1101
|
|
|
|
|
|
|
it will convert the translation on the fly from EUC-TW into Big5-HKSCS, |
|
1102
|
|
|
|
|
|
|
and finally it will return the converted translation. |
|
1103
|
|
|
|
|
|
|
|
|
1104
|
|
|
|
|
|
|
A worst-case scenario but realistic. And for these five lines of codes, |
|
1105
|
|
|
|
|
|
|
there is not much you can do to make it any faster. You should understand, |
|
1106
|
|
|
|
|
|
|
however, I<when> the different steps will take place, so that you can |
|
1107
|
|
|
|
|
|
|
arrange your code for it. |
|
1108
|
|
|
|
|
|
|
|
|
1109
|
|
|
|
|
|
|
You have learned in the section L</DESCRIPTION> that line 1 is |
|
1110
|
|
|
|
|
|
|
responsible for locating your message database. However, the |
|
1111
|
|
|
|
|
|
|
use() will do nothing more than remembering your settings. It will |
|
1112
|
|
|
|
|
|
|
not search any directories, it will not load any catalogs or |
|
1113
|
|
|
|
|
|
|
conversion tables. |
|
1114
|
|
|
|
|
|
|
|
|
1115
|
|
|
|
|
|
|
Somewhere in your code you will always have a call to |
|
1116
|
|
|
|
|
|
|
POSIX::setlocale(), and the performance of this call may be time-consuming, |
|
1117
|
|
|
|
|
|
|
depending on the architecture of your system. On some systems, this |
|
1118
|
|
|
|
|
|
|
will consume very little time, on others it will only consume a |
|
1119
|
|
|
|
|
|
|
considerable amount of time for the first call, and on others it may |
|
1120
|
|
|
|
|
|
|
always be time-consuming. Since you cannot know, how setlocale() is |
|
1121
|
|
|
|
|
|
|
implemented on the target system, you should reduce the calls to |
|
1122
|
|
|
|
|
|
|
setlocale() to a minimum. |
|
1123
|
|
|
|
|
|
|
|
|
1124
|
|
|
|
|
|
|
Line 5 requests the translation for your string. Only now, the library |
|
1125
|
|
|
|
|
|
|
will actually load the message catalog, and only now will it load |
|
1126
|
|
|
|
|
|
|
eventually needed conversion tables. And from now on, all this information |
|
1127
|
|
|
|
|
|
|
will be cached in memory. This strategy is used throughout libintl-perl, |
|
1128
|
|
|
|
|
|
|
and you may describe it as 'load-on-first-access'. Getting the next |
|
1129
|
|
|
|
|
|
|
translation will consume very little resources. |
|
1130
|
|
|
|
|
|
|
|
|
1131
|
|
|
|
|
|
|
However, although the translation retrieval is somewhat obfuscated |
|
1132
|
|
|
|
|
|
|
by an operator-like function call, it is still a function call, and in |
|
1133
|
|
|
|
|
|
|
fact it even involves a chain of function calls. Consequently, the |
|
1134
|
|
|
|
|
|
|
following example is probably bad practice: |
|
1135
|
|
|
|
|
|
|
|
|
1136
|
|
|
|
|
|
|
foreach (1 .. 100_000) { |
|
1137
|
|
|
|
|
|
|
print __"Hello world!\n"; |
|
1138
|
|
|
|
|
|
|
} |
|
1139
|
|
|
|
|
|
|
|
|
1140
|
|
|
|
|
|
|
This example introduces a lot of overhead into your program. Better |
|
1141
|
|
|
|
|
|
|
do this: |
|
1142
|
|
|
|
|
|
|
|
|
1143
|
|
|
|
|
|
|
my $string = __"Hello world!\n"; |
|
1144
|
|
|
|
|
|
|
foreach (1 .. 100_000) { |
|
1145
|
|
|
|
|
|
|
print $string; |
|
1146
|
|
|
|
|
|
|
} |
|
1147
|
|
|
|
|
|
|
|
|
1148
|
|
|
|
|
|
|
The translation will never change, there is no need to retrieve it |
|
1149
|
|
|
|
|
|
|
over and over again. Although libintl-perl will of course cache |
|
1150
|
|
|
|
|
|
|
the translation read from the file system, you can still avoid the |
|
1151
|
|
|
|
|
|
|
overhead for the function calls. |
|
1152
|
|
|
|
|
|
|
|
|
1153
|
|
|
|
|
|
|
=head1 AUTHOR |
|
1154
|
|
|
|
|
|
|
|
|
1155
|
|
|
|
|
|
|
Copyright (C) 2002-2026 L<Guido Flohr|http://www.guido-flohr.net/> |
|
1156
|
|
|
|
|
|
|
(L<mailto:guido.flohr@cantanea.com>), all rights reserved. See the source |
|
1157
|
|
|
|
|
|
|
code for details!code for details! |
|
1158
|
|
|
|
|
|
|
|
|
1159
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
1160
|
|
|
|
|
|
|
|
|
1161
|
|
|
|
|
|
|
Locale::Messages(3pm), Locale::gettext_pp(3pm), perl(1), |
|
1162
|
|
|
|
|
|
|
gettext(1), gettext(3) |
|
1163
|
|
|
|
|
|
|
|
|
1164
|
|
|
|
|
|
|
=cut |
|
1165
|
|
|
|
|
|
|
Local Variables: |
|
1166
|
|
|
|
|
|
|
mode: perl |
|
1167
|
|
|
|
|
|
|
perl-indent-level: 4 |
|
1168
|
|
|
|
|
|
|
perl-continued-statement-offset: 4 |
|
1169
|
|
|
|
|
|
|
perl-continued-brace-offset: 0 |
|
1170
|
|
|
|
|
|
|
perl-brace-offset: -4 |
|
1171
|
|
|
|
|
|
|
perl-brace-imaginary-offset: 0 |
|
1172
|
|
|
|
|
|
|
perl-label-offset: -4 |
|
1173
|
|
|
|
|
|
|
cperl-indent-level: 4 |
|
1174
|
|
|
|
|
|
|
cperl-continued-statement-offset: 2 |
|
1175
|
|
|
|
|
|
|
tab-width: 4 |
|
1176
|
|
|
|
|
|
|
End: |