Filename | /var/www/foswikidev/core/lib/Foswiki/I18N.pm |
Statements | Executed 31 statements in 1.55ms |
Calls | P | F | Exclusive Time |
Inclusive Time |
Subroutine |
---|---|---|---|---|---|
1 | 1 | 1 | 478µs | 540µs | new | Foswiki::I18N::
1 | 1 | 1 | 14µs | 27µs | BEGIN@13 | Foswiki::I18N::
1 | 1 | 1 | 11µs | 126µs | BEGIN@16 | Foswiki::I18N::
1 | 1 | 1 | 10µs | 36µs | BEGIN@15 | Foswiki::I18N::
1 | 1 | 1 | 10µs | 14µs | BEGIN@14 | Foswiki::I18N::
1 | 1 | 1 | 6µs | 6µs | finish | Foswiki::I18N::
1 | 1 | 1 | 6µs | 6µs | BEGIN@18 | Foswiki::I18N::
1 | 1 | 1 | 6µs | 6µs | BEGIN@95 | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | __ANON__[:219] | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | __ANON__[:277] | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | __ANON__[:286] | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | _add_language | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | _discover_languages | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | _loadLexicon | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | _normalize_language_tag | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | available_languages | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | enabled_languages | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | language | Foswiki::I18N::
0 | 0 | 0 | 0s | 0s | maketext | Foswiki::I18N::
Line | State ments |
Time on line |
Calls | Time in subs |
Code |
---|---|---|---|---|---|
1 | # See bottom of file for license and copyright information | ||||
2 | |||||
3 | =begin TML | ||||
4 | |||||
5 | ---+ package Foswiki::I18N | ||||
6 | |||||
7 | Support for strings translation and language detection. | ||||
8 | |||||
9 | =cut | ||||
10 | |||||
11 | package Foswiki::I18N; | ||||
12 | |||||
13 | 2 | 29µs | 2 | 40µs | # spent 27µs (14+13) within Foswiki::I18N::BEGIN@13 which was called:
# once (14µs+13µs) by Foswiki::i18n at line 13 # spent 27µs making 1 call to Foswiki::I18N::BEGIN@13
# spent 13µs making 1 call to strict::import |
14 | 2 | 24µs | 2 | 18µs | # spent 14µs (10+4) within Foswiki::I18N::BEGIN@14 which was called:
# once (10µs+4µs) by Foswiki::i18n at line 14 # spent 14µs making 1 call to Foswiki::I18N::BEGIN@14
# spent 4µs making 1 call to warnings::import |
15 | 2 | 29µs | 2 | 61µs | # spent 36µs (10+26) within Foswiki::I18N::BEGIN@15 which was called:
# once (10µs+26µs) by Foswiki::i18n at line 15 # spent 36µs making 1 call to Foswiki::I18N::BEGIN@15
# spent 26µs making 1 call to Exporter::import |
16 | 2 | 52µs | 2 | 241µs | # spent 126µs (11+115) within Foswiki::I18N::BEGIN@16 which was called:
# once (11µs+115µs) by Foswiki::i18n at line 16 # spent 126µs making 1 call to Foswiki::I18N::BEGIN@16
# spent 115µs making 1 call to Error::import |
17 | |||||
18 | # spent 6µs within Foswiki::I18N::BEGIN@18 which was called:
# once (6µs+0s) by Foswiki::i18n at line 23 | ||||
19 | 1 | 5µs | if ( $Foswiki::cfg{UseLocale} ) { | ||
20 | require locale; | ||||
21 | import locale(); | ||||
22 | } | ||||
23 | 1 | 495µs | 1 | 6µs | } # spent 6µs making 1 call to Foswiki::I18N::BEGIN@18 |
24 | |||||
25 | 1 | 100ns | our $initialised; | ||
26 | 1 | 300ns | our @initErrors; | ||
27 | |||||
28 | =begin TML | ||||
29 | |||||
30 | ---++ ClassMethod available_languages | ||||
31 | |||||
32 | Lists languages tags for languages available at Foswiki installation. Returns a | ||||
33 | list containing the tags of the available languages. | ||||
34 | |||||
35 | __Note__: the languages available to users are determined in the =configure= | ||||
36 | interface. | ||||
37 | |||||
38 | =cut | ||||
39 | |||||
40 | sub available_languages { | ||||
41 | |||||
42 | my @available; | ||||
43 | |||||
44 | while ( my ( $langCode, $langOptions ) = | ||||
45 | each %{ $Foswiki::cfg{Languages} } ) | ||||
46 | { | ||||
47 | if ( $langOptions->{Enabled} ) { | ||||
48 | push( @available, _normalize_language_tag($langCode) ); | ||||
49 | } | ||||
50 | } | ||||
51 | |||||
52 | return @available; | ||||
53 | } | ||||
54 | |||||
55 | # utility function: normalize language tags like ab_CD to ab-cd | ||||
56 | # also renove any character there is not a letter [a-z] or a hyphen. | ||||
57 | sub _normalize_language_tag { | ||||
58 | my $tag = shift; | ||||
59 | $tag = lc( $tag || '' ); | ||||
60 | $tag =~ s/\_/-/g; | ||||
61 | $tag =~ s/[^a-z-]//g; | ||||
62 | return $tag; | ||||
63 | } | ||||
64 | |||||
65 | sub _loadLexicon { | ||||
66 | my ( $lang, $dir ) = @_; | ||||
67 | |||||
68 | $dir ||= $Foswiki::cfg{LocalesDir}; | ||||
69 | |||||
70 | my $langFile = "$dir/$lang.po"; | ||||
71 | |||||
72 | #print STDERR "langFile=$langFile\n"; | ||||
73 | |||||
74 | # Use the compressed version if it exists | ||||
75 | if ( $langFile =~ m/^(.*)\.po$/ | ||||
76 | && -f "$1.mo" ) | ||||
77 | { | ||||
78 | $langFile = "$1.mo"; | ||||
79 | } | ||||
80 | if ( -f $langFile ) { | ||||
81 | unless ( | ||||
82 | eval { | ||||
83 | Locale::Maketext::Lexicon->import( | ||||
84 | { _decode => 1, $lang => [ Gettext => $langFile ] } ); | ||||
85 | 1; | ||||
86 | } | ||||
87 | ) | ||||
88 | { | ||||
89 | push( @initErrors, "I18N - Error loading language $lang: $@\n" ); | ||||
90 | } | ||||
91 | } | ||||
92 | } | ||||
93 | |||||
94 | # initialisation block | ||||
95 | # spent 6µs within Foswiki::I18N::BEGIN@95 which was called:
# once (6µs+0s) by Foswiki::i18n at line 148 | ||||
96 | |||||
97 | # we only need to proceed if user wants internationalisation support | ||||
98 | 1 | 6µs | return unless $Foswiki::cfg{UserInterfaceInternationalisation}; | ||
99 | |||||
100 | # no languages enabled is the same as disabling | ||||
101 | # {UserInterfaceInternationalisation} | ||||
102 | my @languages = available_languages(); | ||||
103 | return unless ( scalar(@languages) ); | ||||
104 | |||||
105 | # we first assume it's ok | ||||
106 | $initialised = 1; | ||||
107 | |||||
108 | eval "use Locale::Maketext ()"; | ||||
109 | if ($@) { | ||||
110 | $initialised = 0; | ||||
111 | push( @initErrors, | ||||
112 | "I18N: Couldn't load required perl module Locale::Maketext: " | ||||
113 | . $@ | ||||
114 | . "\nInstall the module or turn off {UserInterfaceInternationalisation}" | ||||
115 | ); | ||||
116 | } | ||||
117 | else { | ||||
118 | @Foswiki::I18N::ISA = ('Locale::Maketext'); | ||||
119 | } | ||||
120 | |||||
121 | unless ( $Foswiki::cfg{LocalesDir} && -e $Foswiki::cfg{LocalesDir} ) { | ||||
122 | push( @initErrors, | ||||
123 | 'I18N: {LocalesDir} not configured. Define it or turn off {UserInterfaceInternationalisation}' | ||||
124 | ); | ||||
125 | $initialised = 0; | ||||
126 | } | ||||
127 | |||||
128 | # dynamically build languages to be loaded according to admin-enabled | ||||
129 | # languages. | ||||
130 | eval "use Locale::Maketext::Lexicon{ en => [ 'Auto' ] } ;"; | ||||
131 | if ($@) { | ||||
132 | $initialised = 0; | ||||
133 | push( @initErrors, | ||||
134 | "I18N - Couldn't load default English messages: $@\n" | ||||
135 | . "Install Locale::Maketext::Lexicon or turn off {UserInterfaceInternationalisation}" | ||||
136 | ); | ||||
137 | } | ||||
138 | |||||
139 | opendir( my $dh, "$Foswiki::cfg{LocalesDir}/" ) || next; | ||||
140 | my @subDirs = | ||||
141 | grep { !/^\./ && -d "$Foswiki::cfg{LocalesDir}/$_" } readdir $dh; | ||||
142 | closedir $dh; | ||||
143 | |||||
144 | foreach my $lang (@languages) { | ||||
145 | _loadLexicon($lang); | ||||
146 | _loadLexicon( $lang, "$Foswiki::cfg{LocalesDir}/$_" ) foreach @subDirs; | ||||
147 | } | ||||
148 | 1 | 803µs | 1 | 6µs | } # spent 6µs making 1 call to Foswiki::I18N::BEGIN@95 |
149 | |||||
150 | =begin TML | ||||
151 | |||||
152 | ---++ ClassMethod new ( $session ) | ||||
153 | |||||
154 | Constructor. Gets the language object corresponding to the current users | ||||
155 | language. If $session is not a Foswiki object reference, just calls | ||||
156 | Local::Maketext::new (the superclass constructor) | ||||
157 | |||||
158 | =cut | ||||
159 | |||||
160 | # spent 540µs (478+62) within Foswiki::I18N::new which was called:
# once (478µs+62µs) by Foswiki::i18n at line 2344 of /var/www/foswikidev/core/lib/Foswiki.pm | ||||
161 | 1 | 700ns | my $class = shift; | ||
162 | 1 | 500ns | my ($session) = @_; | ||
163 | |||||
164 | 1 | 8µs | 1 | 1µs | unless ( ref($session) && $session->isa('Foswiki') ) { # spent 1µs making 1 call to UNIVERSAL::isa |
165 | |||||
166 | # it's recursive | ||||
167 | return $class->SUPER::new(@_); | ||||
168 | } | ||||
169 | |||||
170 | 1 | 300ns | if (@initErrors) { | ||
171 | foreach my $error (@initErrors) { | ||||
172 | $session->logger->log( $initialised ? 'warning' : 'error', $error ); | ||||
173 | } | ||||
174 | } | ||||
175 | |||||
176 | # guesses the language from the CGI environment | ||||
177 | # TODO: | ||||
178 | # web/user/session setting must override the language detected from the | ||||
179 | # browser. | ||||
180 | 1 | 100ns | my $this; | ||
181 | 1 | 400ns | if ($initialised) { | ||
182 | $session->enterContext('i18n_enabled'); | ||||
183 | my $userLanguage = _normalize_language_tag( | ||||
184 | $session->{prefs}->getPreference('LANGUAGE') ); | ||||
185 | if ($userLanguage) { | ||||
186 | $this = Foswiki::I18N->get_handle($userLanguage); | ||||
187 | } | ||||
188 | else { | ||||
189 | $this = Foswiki::I18N->get_handle(); | ||||
190 | } | ||||
191 | } | ||||
192 | else { | ||||
193 | 1 | 71µs | require Foswiki::I18N::Fallback; | ||
194 | |||||
195 | 1 | 3µs | 1 | 8µs | $this = new Foswiki::I18N::Fallback(); # spent 8µs making 1 call to Foswiki::I18N::Fallback::new |
196 | |||||
197 | # we couldn't initialise 'optional' I18N infrastructure, warn that we | ||||
198 | # can only use English if I18N has been requested with configure | ||||
199 | 1 | 900ns | $session->logger->log( 'warning', | ||
200 | 'Could not load I18N infrastructure; falling back to English' ) | ||||
201 | if $Foswiki::cfg{UserInterfaceInternationalisation}; | ||||
202 | } | ||||
203 | |||||
204 | # keep a reference to the session object | ||||
205 | 1 | 700ns | $this->{session} = $session; | ||
206 | |||||
207 | # languages we know about | ||||
208 | 1 | 2µs | $this->{enabled_languages} = { en => 'English' }; | ||
209 | 1 | 400ns | $this->{checked_enabled} = undef; | ||
210 | |||||
211 | # what to do with failed translations (only needed when already initialised | ||||
212 | # and language is not English); | ||||
213 | 1 | 100ns | if ( $initialised and ( $this->language ne 'en' ) ) { | ||
214 | my $fallback_handle = Foswiki::I18N->get_handle('en'); | ||||
215 | $this->fail_with( | ||||
216 | sub { | ||||
217 | shift; # get rid of the handle | ||||
218 | return $fallback_handle->maketext(@_); | ||||
219 | } | ||||
220 | ); | ||||
221 | } | ||||
222 | |||||
223 | # finally! :-p | ||||
224 | 1 | 3µs | return $this; | ||
225 | } | ||||
226 | |||||
227 | =begin TML | ||||
228 | |||||
229 | ---++ ObjectMethod finish() | ||||
230 | Break circular references. | ||||
231 | |||||
232 | =cut | ||||
233 | |||||
234 | # Note to developers; please undef *all* fields in the object explicitly, | ||||
235 | # whether they are references or not. That way this method is "golden | ||||
236 | # documentation" of the live fields in the object. | ||||
237 | # spent 6µs within Foswiki::I18N::finish which was called:
# once (6µs+0s) by Foswiki::I18N::Fallback::finish at line 31 of /var/www/foswikidev/core/lib/Foswiki/I18N/Fallback.pm | ||||
238 | 1 | 700ns | my $this = shift; | ||
239 | 1 | 3µs | undef $this->{enabled_languages}; | ||
240 | 1 | 500ns | undef $this->{checked_enabled}; | ||
241 | 1 | 5µs | undef $this->{session}; | ||
242 | } | ||||
243 | |||||
244 | =begin TML | ||||
245 | |||||
246 | ---++ ObjectMethod maketext( $text ) -> $translation | ||||
247 | |||||
248 | Translates the given string (assumed to be written in English) into the | ||||
249 | current language, as detected in the constructor, and converts it into | ||||
250 | a binary UTF-8 string. | ||||
251 | |||||
252 | Wraps around Locale::Maketext's maketext method, adding charset conversion | ||||
253 | and checking. | ||||
254 | |||||
255 | Return value: translated string, or the argument itself if no translation is | ||||
256 | found for thet argument. | ||||
257 | |||||
258 | =cut | ||||
259 | |||||
260 | sub maketext { | ||||
261 | my ( $this, $text, @args ) = @_; | ||||
262 | |||||
263 | if ( $text =~ m/^_/ && $text ne '_language_name' ) { | ||||
264 | require CGI; | ||||
265 | import CGI(); | ||||
266 | |||||
267 | return CGI::span( | ||||
268 | { -class => 'foswikiAlert' }, | ||||
269 | "Error: MAKETEXT arguments can't start with an underscore (\"_\")." | ||||
270 | ); | ||||
271 | } | ||||
272 | |||||
273 | my $result = ''; | ||||
274 | try { | ||||
275 | $result = $this->SUPER::maketext( $text, @args ); | ||||
276 | return $result; | ||||
277 | } | ||||
278 | catch Error with { | ||||
279 | my $e = shift; | ||||
280 | print STDERR | ||||
281 | "#### Error: MAKETEXT - String translation failed for \"$text\". " | ||||
282 | . $e->stringify() | ||||
283 | if DEBUG; | ||||
284 | return | ||||
285 | "<span class='foswikiAlert'>ERROR: Translation failed, see server error log.</span>"; | ||||
286 | } | ||||
287 | } | ||||
288 | |||||
289 | =begin TML | ||||
290 | |||||
291 | ---++ ObjectMethod language() -> $language_tag | ||||
292 | |||||
293 | Indicates the language tag of the current user's language, as detected from the | ||||
294 | information sent by the browser. Returns the empty string if the language | ||||
295 | could not be determined. | ||||
296 | |||||
297 | =cut | ||||
298 | |||||
299 | sub language { | ||||
300 | my $this = shift; | ||||
301 | |||||
302 | return $this->language_tag(); | ||||
303 | } | ||||
304 | |||||
305 | =begin TML | ||||
306 | |||||
307 | ---++ ObjectMethod enabled_languages() -> %languages | ||||
308 | |||||
309 | Returns an array with language tags as keys and language (native) names as | ||||
310 | values, for all the languages enabled in this site. Useful for | ||||
311 | listing available languages to the user. | ||||
312 | |||||
313 | =cut | ||||
314 | |||||
315 | sub enabled_languages { | ||||
316 | my $this = shift; | ||||
317 | |||||
318 | unless ( $this->{checked_enabled} ) { | ||||
319 | _discover_languages($this); | ||||
320 | } | ||||
321 | |||||
322 | $this->{checked_enabled} = 1; | ||||
323 | return $this->{enabled_languages}; | ||||
324 | |||||
325 | } | ||||
326 | |||||
327 | # discovers the available language. | ||||
328 | sub _discover_languages { | ||||
329 | my $this = shift; | ||||
330 | my $cache_open = 0; | ||||
331 | |||||
332 | #use the cache, if available | ||||
333 | if ( open LANGUAGE, '<', "$Foswiki::cfg{WorkingDir}/languages.cache" ) { | ||||
334 | $cache_open = 1; | ||||
335 | foreach my $line ( map { Foswiki::decode_utf8($_) } <LANGUAGE> ) { | ||||
336 | my ( $key, $name ) = split( '=', $line ); | ||||
337 | |||||
338 | # Filter on enabled languages | ||||
339 | next | ||||
340 | unless ( $Foswiki::cfg{Languages}{$key} | ||||
341 | && $Foswiki::cfg{Languages}{$key}{Enabled} ); | ||||
342 | chop($name); | ||||
343 | _add_language( $this, $key, $name ); | ||||
344 | } | ||||
345 | } | ||||
346 | else { | ||||
347 | |||||
348 | # Rebuild the cache, filtering on enabled languages. | ||||
349 | $cache_open = | ||||
350 | open( LANGUAGE, '>', "$Foswiki::cfg{WorkingDir}/languages.cache" ); | ||||
351 | foreach my $tag ( available_languages() ) { | ||||
352 | my $h = Foswiki::I18N->get_handle($tag); | ||||
353 | my $name = eval { $h->maketext("_language_name") } or next; | ||||
354 | print LANGUAGE Foswiki::encode_utf8("$tag=$name\n") if $cache_open; | ||||
355 | |||||
356 | # Filter on enabled languages | ||||
357 | next | ||||
358 | unless ( $Foswiki::cfg{Languages}{$tag} | ||||
359 | && $Foswiki::cfg{Languages}{$tag}{Enabled} ); | ||||
360 | _add_language( $this, $tag, $name ); | ||||
361 | } | ||||
362 | } | ||||
363 | |||||
364 | close LANGUAGE if $cache_open; | ||||
365 | $this->{checked_enabled} = 1; | ||||
366 | |||||
367 | } | ||||
368 | |||||
369 | # private utility method: add a pair tag/language name | ||||
370 | sub _add_language { | ||||
371 | my ( $this, $tag, $name ) = @_; | ||||
372 | $this->{enabled_languages}->{$tag} = $name; | ||||
373 | } | ||||
374 | |||||
375 | 1 | 3µs | 1; | ||
376 | __END__ |