File:
[LON-CAPA] /
loncom /
localize /
lonlocal.pm
Revision
1.49:
download - view:
text,
annotated -
select for diffs
Fri Sep 19 03:27:04 2008 UTC (15 years, 11 months ago) by
raeburn
Branches:
MAIN
CVS tags:
HEAD
Bugs: 5792 and 5798.
- Uses DateTime, DateTime::Locale, DateTime::Locale::Catalog.
- Disposition of events in calendar display now consistent with current time zone.
- Timezone in effect is displayed in Calendar header line.
- First day of week determined from date localization in effect.
- Extra empty row eliminated from end of calendar.
- Default date localization can be set for domain from Default auth/language/timezone section of Domain Configuration.
- Date localization can also be set for a specific course via "Set Course environement".
- When in course context, course-specific date localization applies (if set), otherwise default date localization of domain of course (if set). In other contexts, date localization of domain of user (if set) applies.
- Currently date localization is only used to determine starting day of the week (for building calendar display). Future uses might consider date order/format for display of dates.
- &timehash() and &maketime() routines in loncommon.pm modified to use time zones.
- &nextday() routine in lonannounce.pm modified to correctly identify next day.
- &getdatelocale() routine added to lonlocal.pm to retrieve date locale object.
1: # The LearningOnline Network with CAPA
2: # Localization routines
3: #
4: # $Id: lonlocal.pm,v 1.49 2008/09/19 03:27:04 raeburn Exp $
5: #
6: # Copyright Michigan State University Board of Trustees
7: #
8: # This file is part of the LearningOnline Network with CAPA (LON-CAPA).
9: #
10: # LON-CAPA is free software; you can redistribute it and/or modify
11: # it under the terms of the GNU General Public License as published by
12: # the Free Software Foundation; either version 2 of the License, or
13: # (at your option) any later version.
14: #
15: # LON-CAPA is distributed in the hope that it will be useful,
16: # but WITHOUT ANY WARRANTY; without even the implied warranty of
17: # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18: # GNU General Public License for more details.
19: #
20: # You should have received a copy of the GNU General Public License
21: # along with LON-CAPA; if not, write to the Free Software
22: # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23: #
24: # /home/httpd/html/adm/gpl.txt
25: #
26: # http://www.lon-capa.org/
27: #
28: ######################################################################
29: ######################################################################
30:
31: =pod
32:
33: =head1 NAME
34:
35: Apache::lonlocal - provides localization services
36:
37: =head1 SYNOPSIS
38:
39: lonlocal provides localization services for LON-CAPA programmers based
40: on Locale::Maketext. See
41: C<http://search.cpan.org/dist/Locale-Maketext/lib/Locale/Maketext.pod>
42: for more information on Maketext.
43:
44: =head1 OVERVIEWX<internationalization>
45:
46: As of LON-CAPA 1.1, we've started to localize LON-CAPA using the
47: Locale::Maketext module. Internationalization is the bulk of the work
48: right now (pre-1.1); localizing can be done anytime, and involves
49: little or no programming.
50:
51: The internationalization process involves putting a wrapper around
52: on-screen user messages and menus and turning them into keys,
53: which the MaketextX<Maketext> library translates into the desired
54: language output using a look-up table ("lexicon").X<lexicon>
55:
56: As keys we are currently using the plain English messages, and
57: Maketext is configured to replace the message by its own key if no
58: translation is found. This makes it easy to phase in the
59: internationalization without disturbing the screen output.
60:
61: Internationalization is somewhat tedious and effectively impossible
62: for a non-fluent speaker to perform, but is fairly easy to create
63: translations, requiring no programming skill. As a result, this is one
64: area where you can really help LON-CAPA out, even if you aren't a
65: programmer, and we'd really appreciate it.
66:
67: =head1 How To Localize Handlers For Programmers
68:
69: Into the "use" section of a module, we need to insert
70:
71: use Apache::lonlocal;
72:
73: Note that there are B<no parentheses>, we B<want> to pollute our
74: namespace.
75:
76: Inside might be something like this
77:
78: sub message {
79: my $status=shift;
80: my $message='Status unknown';
81: if ($status eq 'WON') {
82: $message='You have won.';
83: } elsif ($status eq 'LOST') {
84: $message='You are a total looser.';
85: }
86: return $message;
87: }
88: ...
89: $r->print('<h3>Gamble your Homework Points</h3>');
90: ...
91: $r->print(<<ENDMSG);
92: <font size="1">Rules:</font>
93: <font size="0">No purchase necessary. Illegal where not allowed.</font>
94: ENDMSG
95:
96: We have to now wrap the subroutine &mt()X<mt> ("maketext") around our
97: messages, but not around markup, etc. We also want minimal disturbance.
98: The first two examples are easy:
99:
100: sub message {
101: my $status=shift;
102: my $message='Status unknown';
103: if ($status eq 'WON') {
104: $message='You have won.';
105: } elsif ($status eq 'LOST') {
106: $message='You are a total looser.';
107: }
108: return &mt($message);
109: }
110: ...
111: $r->print('<h3>'.&mt('Gamble your Homework Points').'</h3>');
112:
113: The last one is a bummer, since you cannot call subroutines inside of
114: (<<MARKER). I have written a little subroutine to generate a translated
115: hash for that purpose:
116:
117: my %lt=&Apache::lonlocal::texthash('header' => 'Rules', 'disclaimer' =>
118: 'No purchase necessary. Illegal where not allowed.');
119: $r->print(<<ENDMSG);
120: <font size="1">$lt{'header'}:</font>
121: <font size="0">$lt{'disclaimer'}</font>
122: ENDMSG
123:
124: As a programmer, your job is done here. If everything worked, you
125: should see no changes on the screen.
126:
127: =head1 How To Localize LON-CAPA for Translators
128:
129: As a translator, you need to provide the lexicon for the keys, which in
130: this case is the plain text message. The lexicons sit in
131: loncom/localize/localize, with the language code as filename, for
132: example de.pm for the German translation. The file then simply looks
133: like this:
134:
135: 'You have won.'
136: => 'Sie haben gewonnen.',
137:
138: 'You are a total looser.'
139: => 'Sie sind der totale Verlierer.',
140:
141: 'Rules'
142: => 'Regeln',
143:
144: 'No purchase necessary. Illegal where not allowed.'
145: => 'Es ist erlaubt, einfach zu verlieren, und das ist Ihre Schuld.'
146:
147:
148: Comments may be added with the # symbol, which outside of a string
149: (the things with the apostrophe surrounding them, which are the
150: keys and translations) will cause the translation routines to
151: ignore the rest of the line.
152:
153: This is a relatively easy task, and any help is appreciated.
154:
155: Maketext can do a whole lot more, see
156: C<http://search.cpan.org/dist/Locale-Maketext/lib/Locale/Maketext.pod>
157: but for most purposes, we do not have to mess with that.
158:
159: =cut
160:
161: package Apache::lonlocal;
162:
163: use strict;
164: use Apache::localize;
165: use locale;
166: use POSIX qw(locale_h strftime);
167: use DateTime();
168: use DateTime::TimeZone;
169: use DateTime::Locale;
170:
171: require Exporter;
172:
173: our @ISA = qw (Exporter);
174: our @EXPORT = qw(mt mtn ns mt_user);
175:
176: # ========================================================= The language handle
177:
178: use vars qw($lh);
179:
180: # ===================================================== The "MakeText" function
181:
182: sub mt (@) {
183: # open(LOG,'>>/home/www/loncapa/loncom/localize/localize/newphrases.txt');
184: # print LOG (@_[0]."\n");
185: # close(LOG);
186: if ($lh) {
187: if ($_[0] eq '') {
188: if (wantarray) {
189: return @_;
190: } else {
191: return $_[0];
192: }
193: } else {
194: return $lh->maketext(@_);
195: }
196: } else {
197: if (wantarray) {
198: return @_;
199: } else {
200: return $_[0];
201: }
202: }
203: }
204:
205: sub mt_user {
206: my ($user_lh,@what) = @_;
207: if ($user_lh) {
208: if ($what[0] eq '') {
209: if (wantarray) {
210: return @what;
211: } else {
212: return $what[0];
213: }
214: } else {
215: return $user_lh->maketext(@what);
216: }
217: } else {
218: if (wantarray) {
219: return @what;
220: } else {
221: return $what[0];
222: }
223: }
224: }
225:
226: # ============================================================== What language?
227:
228: sub current_language {
229: if ($lh) {
230: my $lang=$lh->maketext('language_code');
231: return ($lang eq 'language_code'?'en':$lang);
232: }
233: return 'en';
234: }
235:
236: # ============================================================== What encoding?
237:
238: sub current_encoding {
239: my $default='UTF-8';
240: if ($Apache::lonnet::env{'browser.os'} eq 'win' &&
241: $Apache::lonnet::env{'browser.type'} eq 'explorer') {
242: $default='ISO-8859-1';
243: }
244: if ($lh) {
245: my $enc=$lh->maketext('char_encoding');
246: return ($enc eq 'char_encoding'?$default:$enc);
247: } else {
248: return $default;
249: }
250: }
251:
252: # =============================================================== Which locale?
253: # Refer to locale -a
254: #
255: sub current_locale {
256: if ($lh) {
257: my $enc=$lh->maketext('lang_locale');
258: return ($enc eq 'lang_locale'?'':$enc);
259: } else {
260: return undef;
261: }
262: }
263:
264: # ============================================================== Translate hash
265:
266: sub texthash {
267: my %hash=@_;
268: foreach (keys %hash) {
269: $hash{$_}=&mt($hash{$_});
270: }
271: return %hash;
272: }
273:
274: # ========= Get a handle (do not invoke in vain, leave this to access handlers)
275:
276: sub get_language_handle {
277: my $r=shift;
278: if ($r) {
279: my $headers=$r->headers_in;
280: $ENV{'HTTP_ACCEPT_LANGUAGE'}=$headers->{'Accept-language'};
281: }
282: my @languages=&Apache::loncommon::preferred_languages;
283: $ENV{'HTTP_ACCEPT_LANGUAGE'}='';
284: $lh=Apache::localize->get_handle(@languages);
285: if ($r) {
286: $r->content_languages([¤t_language()]);
287: }
288: ### setlocale(LC_ALL,¤t_locale);
289: }
290:
291: # ========================================================== Localize localtime
292: sub gettimezone {
293: my $timezone;
294: if ($Apache::lonnet::env{'course.'.$Apache::lonnet::env{'request.course.id'}.'.timezone'}) {
295: $timezone = $Apache::lonnet::env{'course.'.$Apache::lonnet::env{'request.course.id'}.'.timezone'};
296: } elsif ($Apache::lonnet::env{'request.course.id'} ne '') {
297: my $cdom = $Apache::lonnet::env{'course.'.$Apache::lonnet::env{'request.course.id'}.'.domain'};
298: if ($cdom ne '') {
299: my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom);
300: if ($domdefaults{'timezone_def'} ne '') {
301: $timezone = $domdefaults{'timezone_def'};
302: }
303: }
304: }
305: if ($timezone ne '') {
306: if (DateTime::TimeZone->is_valid_name($timezone)) {
307: return $timezone;
308: }
309: }
310: return 'local';
311: }
312:
313: sub locallocaltime {
314: my $thistime=shift;
315: if (!defined($thistime) || $thistime eq '') {
316: return &mt('Never');
317: }
318: if (($thistime < 0) || ($thistime eq 'NaN')) {
319: &Apache::lonnet::logthis("Unexpected time (negative or NaN) '$thistime' passed to lonlocal::locallocaltime");
320: return &mt('Never');
321: }
322: if ($thistime !~ /^\d+$/) {
323: &Apache::lonnet::logthis("Unexpected non-numeric time '$thistime' passed to lonlocal::locallocaltime");
324: return &mt('Never');
325: }
326:
327: my $dt = DateTime->from_epoch(epoch => $thistime)
328: ->set_time_zone(&gettimezone());
329: if ((¤t_language=~/^en/) || (!$lh)) {
330:
331: return $dt->strftime("%a %b %e %I:%M:%S %P %Y (%Z)");
332: } else {
333: my $format=$lh->maketext('date_locale');
334: if ($format eq 'date_locale') {
335: return $dt->strftime("%a %b %e %I:%M:%S %P %Y (%Z)");
336: }
337: my $time_zone = $dt->time_zone_short_name();
338: my $seconds = $dt->second();
339: my $minutes = $dt->minute();
340: my $twentyfour = $dt->hour();
341: my $day = $dt->day_of_month();
342: my $mon = $dt->month()-1;
343: my $year = $dt->year();
344: my $wday = $dt->wday();
345: if ($wday==7) { $wday=0; }
346: my $month =(split(/\,/,$lh->maketext('date_months')))[$mon];
347: my $weekday=(split(/\,/,$lh->maketext('date_days')))[$wday];
348: if ($seconds<10) {
349: $seconds='0'.$seconds;
350: }
351: if ($minutes<10) {
352: $minutes='0'.$minutes;
353: }
354: my $twelve=$twentyfour;
355: my $ampm;
356: if ($twelve>12) {
357: $twelve-=12;
358: $ampm=$lh->maketext('date_pm');
359: } else {
360: $ampm=$lh->maketext('date_am');
361: }
362: foreach ('seconds','minutes','twentyfour','twelve','day','year',
363: 'month','weekday','ampm') {
364: $format=~s/\$$_/eval('$'.$_)/gse;
365: }
366: return $format." ($time_zone)";
367: }
368: }
369:
370: sub getdatelocale {
371: my ($datelocale,$locale_obj);
372: if ($Apache::lonnet::env{'course.'.$Apache::lonnet::env{'request.course.id'}.'.datelocale'}) {
373: $datelocale = $Apache::lonnet::env{'course.'.$Apache::lonnet::env{'request.course.id'}.'.datelocale'};
374: } elsif ($Apache::lonnet::env{'request.course.id'} ne '') {
375: my $cdom = $Apache::lonnet::env{'course.'.$Apache::lonnet::env{'request.course.id'}.'.domain'};
376: if ($cdom ne '') {
377: my %domdefaults = &Apache::lonnet::get_domain_defaults($cdom);
378: if ($domdefaults{'datelocale_def'} ne '') {
379: $datelocale = $domdefaults{'datelocale_def'};
380: }
381: }
382: } elsif ($Apache::lonnet::env{'user.domain'} ne '') {
383: my %udomdefaults = &Apache::lonnet::get_domain_defaults($Apache::lonnet::env{'user.domain'});
384: if ($udomdefaults{'datelocale_def'} ne '') {
385: $datelocale = $udomdefaults{'datelocale_def'};
386: }
387: }
388: if ($datelocale ne '') {
389: eval {
390: $locale_obj = DateTime::Locale->load($datelocale);
391: };
392: if (!$@) {
393: if ($locale_obj->id() eq $datelocale) {
394: return $locale_obj;
395: }
396: }
397: }
398: return $locale_obj;
399: }
400:
401:
402: # ==================== Normalize string (reduce fragility in the lexicon files)
403:
404: # This normalizes a string to reduce fragility in the lexicon files of
405: # huge messages (such as are used by the helper), and allow useful
406: # formatting: reduce all consecutive whitespace to a single space,
407: # and remove all HTML
408: sub normalize_string {
409: my $s = shift;
410: $s =~ s/\s+/ /g;
411: $s =~ s/<[^>]+>//g;
412: # Pop off beginning or ending spaces, which aren't good
413: $s =~ s/^\s+//;
414: $s =~ s/\s+$//;
415: return $s;
416: }
417:
418: # alias for normalize_string; recommend using it only in the lexicon
419: sub ns {
420: return normalize_string(@_);
421: }
422:
423: # mtn: call the mt function and the normalization function easily.
424: # Returns original non-normalized string if there was no translation
425: sub mtn (@) {
426: my @args = @_; # don't want to modify caller's string; if we
427: # didn't care about that we could set $_[0]
428: # directly
429: $args[0] = normalize_string($args[0]);
430: my $translation = &mt(@args);
431: if ($translation ne $args[0]) {
432: return $translation;
433: } else {
434: return $_[0];
435: }
436: }
437:
438: # ---------------------------------------------------- Replace MT{...} in files
439:
440: sub transstatic {
441: my $strptr=shift;
442: $$strptr=~s/MT\{([^\}]*)\}/&mt($1)/gse;
443: }
444:
445: =pod
446:
447: =item * mt_escape
448:
449: mt_escape takes a string reference and escape the [] in there so mt
450: will leave them as is and not try to expand them
451:
452: =cut
453:
454: sub mt_escape {
455: my ($str_ref) = @_;
456: $$str_ref =~s/~/~~/g;
457: $$str_ref =~s/([\[\]])/~$1/g;
458: }
459:
460: 1;
461:
462: __END__
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>