From b996f2f722e069d479b6f948c4a7e271beab166a Mon Sep 17 00:00:00 2001 From: "gerv%gerv.net" <> Date: Fri, 24 Jan 2003 07:34:00 +0000 Subject: Bug 126955 - Bugzilla should support translated/localized templates. Patch by burnus; r=gerv, a=justdave. --- Bugzilla/Template.pm | 63 +++++++++++++++++++++++++- checksetup.pl | 110 ++++++++++++++++++++++----------------------- defparams.pl | 20 +++++++++ t/004template.t | 118 +++++++++++++++++++++++++++---------------------- t/005no_tabs.t | 9 ++-- t/Support/Templates.pm | 71 +++++++++++++++++++++++++---- 6 files changed, 269 insertions(+), 122 deletions(-) diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index abcfd3ff6..434785332 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -22,6 +22,8 @@ # Jacob Steenhagen # Bradley Baetz # Christopher Aillon +# Tobias Burnus + package Bugzilla::Template; @@ -35,6 +37,65 @@ use Date::Format (); use base qw(Template); +my $template_include_path; + +# Make an ordered list out of a HTTP Accept-Language header see RFC 2616, 14.4 +# We ignore '*' and ;q=0 +# For languages with the same priority q the order remains unchanged. +sub sortAcceptLanguage { + sub sortQvalue { $b->{'qvalue'} <=> $a->{'qvalue'} } + my $accept_language = $_[0]; + + # clean up string. + $accept_language =~ s/[^A-Za-z;q=0-9\.\-,]//g; + my @qlanguages; + my @languages; + foreach(split /,/, $accept_language) { + if (m/([A-Za-z\-]+)(?:;q=(\d(?:\.\d+)))?/) { + my $lang = $1; + my $qvalue = $2; + $qvalue = 1 if not defined $qvalue; + next if $qvalue == 0; + $qvalue = 1 if $qvalue > 1; + push(@qlanguages, {'qvalue' => $qvalue, 'language' => $lang}); + } + } + + return map($_->{'language'}, (sort sortQvalue @qlanguages)); +} + +# Returns the path to the templates based on the Accept-Language +# settings of the user and of the available languages +# If no Accept-Language is present it uses the defined default +sub getTemplateIncludePath () { + # Return cached value if available + if ($template_include_path) { + return $template_include_path; + } + my $languages = trim(Param('languages')); + if (not ($languages =~ /,/)) { + return $template_include_path = + ["template/$languages/custom", "template/$languages/default"]; + } + my @languages = sortAcceptLanguage($languages); + my @accept_language = sortAcceptLanguage($ENV{'HTTP_ACCEPT_LANGUAGE'} || "" ); + my @usedlanguages; + foreach my $lang (@accept_language) { + # Per RFC 1766 and RFC 2616 any language tag matches also its + # primary tag. That is 'en' (accept lanuage) matches 'en-us', + # 'en-uk' etc. but not the otherway round. (This is unfortunally + # not very clearly stated in those RFC; see comment just over 14.5 + # in http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.4) + if(my @found = grep /^$lang(-.+)?$/i, @languages) { + push (@usedlanguages, @found); + } + } + push(@usedlanguages, Param('defaultlanguage')); + return $template_include_path = + [map(("template/$_/custom", "template/$_/default"), @usedlanguages)]; +} + + ############################################################################### # Templatization Code @@ -100,7 +161,7 @@ sub create { return $class->new({ # Colon-separated list of directories containing templates. - INCLUDE_PATH => "template/en/custom:template/en/default", + INCLUDE_PATH => [\&getTemplateIncludePath], # Remove white-space before template directives (PRE_CHOMP) and at the # beginning and end of templates and template blocks (TRIM) for better diff --git a/checksetup.pl b/checksetup.pl index 59ebb1955..f2c1ef761 100755 --- a/checksetup.pl +++ b/checksetup.pl @@ -25,6 +25,7 @@ # Zach Lipton # Jacob Steenhagen # Bradley Baetz +# Tobias Burnus # # # Direct any questions on this source code to @@ -958,82 +959,81 @@ END } } + # Search for template directories + # We include the default and custom directories separately to make + # sure we compile all templates + my @templatepaths = (); + { + use File::Spec; + opendir(DIR, "template") || die "Can't open 'template': $!"; + my @files = grep { /^[a-z-]+$/i } readdir(DIR); + closedir DIR; + + foreach my $dir (@files) { + next if($dir =~ /^CVS$/i); + my $path = File::Spec->catdir('template', $dir, 'custom'); + push(@templatepaths, $path) if(-d $path); + $path = File::Spec->catdir('template', $dir, 'default'); + push(@templatepaths, $path) if(-d $path); + } + } + # Precompile stuff. This speeds up initial access (so the template isn't # compiled multiple times simulataneously by different servers), and helps # to get the permissions right. - eval("use Template"); - my $redir = ($^O =~ /MSWin32/i) ? "NUL" : "/dev/null"; - my $provider = Template::Provider->new( - { - # Colon-separated list of directories containing templates. - INCLUDE_PATH => "template/en/custom:template/en/default", - - PRE_CHOMP => 1 , - TRIM => 1 , - - COMPILE_DIR => 'data/', # becomes data/template/en/{custom,default} - - # These don't actually need to do anything here, just exist - FILTERS => - { - strike => sub { return $_; } , - js => sub { return $_; }, - html_linebreak => sub { return $_; }, - url_quote => sub { return $_; }, - xml => sub { return $_; }, - quoteUrls => sub { return $_; }, - bug_link => [ sub { return sub { return $_; } }, 1], - csv => sub { return $_; }, - time => sub { return $_; }, - }, - }) || die ("Could not create Template Provider: " - . Template::Provider->error() . "\n"); - sub compile { - # no_chdir doesn't work on perl 5.005 - - my $origDir = $File::Find::dir; my $name = $File::Find::name; return if (-d $name); return if ($name =~ /\/CVS\//); return if ($name !~ /\.tmpl$/); - $name =~ s!template/en/default/!!; # trim the bit we don't pass to TT - - chdir($::baseDir); + $name =~ s/\Q$::templatepath\E\///; # trim the bit we don't pass to TT # Do this to avoid actually processing the templates - my ($data, $err) = $provider->fetch($name); + my ($data, $err) = $::provider->fetch($name); die "Could not compile $name: " . $data . "\n" if $err; - - chdir($origDir); } + + eval("use Template"); { print "Precompiling templates ...\n" unless $silent; use File::Find; - use Cwd; - - $::baseDir = cwd(); - # Don't hang on templates which use the CGI library eval("use CGI qw(-no_debug)"); - - # Disable warnings which come from running the compiled templates - # This way is OK, because they're all runtime warnings. - # The reason we get these warnings here is that none of the required - # vars will be present. - local ($^W) = 0; - - # Traverse the default hierachy. Custom templates will be picked up - # via the INCLUDE_PATH, but we know that bugzilla will only be - # calling stuff which exists in en/default - # FIXME - if we start doing dynamic INCLUDE_PATH we may have to - # recurse all of template/, changing the INCLUDE_PATH each time - - find(\&compile, "template/en/default"); + foreach $::templatepath (@templatepaths) { + $::provider = Template::Provider->new( + { + # Directories containing templates. + INCLUDE_PATH => $::templatepath, + + PRE_CHOMP => 1 , + TRIM => 1 , + + # becomes data/template/{en, ...}/{custom,default} + COMPILE_DIR => 'data/', + + # These don't actually need to do anything here, just exist + FILTERS => + { + strike => sub { return $_; } , + js => sub { return $_; }, + html_linebreak => sub { return $_; }, + url_quote => sub { return $_; }, + xml => sub { return $_; }, + quoteUrls => sub { return $_; }, + bug_link => [ sub { return sub { return $_; } }, 1], + csv => sub { return $_; }, + time => sub { return $_; }, + }, + }) || die ("Could not create Template Provider: " + . Template::Provider->error() . "\n"); + + # Traverse the template hierachy. + find({ wanted => \&compile, no_chdir => 1 }, $::templatepath); + } } } diff --git a/defparams.pl b/defparams.pl index 922a9dfe2..f75ead4b2 100644 --- a/defparams.pl +++ b/defparams.pl @@ -201,6 +201,26 @@ sub check_netmask { checker => \&check_urlbase }, + { + name => 'languages' , + desc => 'A comma-separated list of RFC 1766 language tags. These ' . + 'identify the languages in which you wish Bugzilla output ' . + 'to be displayed. Note that you must install the appropriate ' . + 'language pack before adding a language to this Param. The ' . + 'language used is the one in this list with the highest ' . + 'q-value in the user\'s Accept-Language header.' , + type => 't' , + default => 'en' + }, + + { + name => 'defaultlanguage', + desc => 'The UI language Bugzilla falls back on if no suitable ' . + 'language is found in the user\'s Accept-Language header.' , + type => 't' , + default => 'en' + }, + { name => 'cookiepath', desc => 'Path, relative to your web document root, to which to restrict ' . diff --git a/t/004template.t b/t/004template.t index be0dd04ec..8429b774f 100644 --- a/t/004template.t +++ b/t/004template.t @@ -20,6 +20,7 @@ # Contributor(s): Jacob Steenhagen # Zach Lipton # David D. Kilzer +# Tobias Burnus # ################# @@ -37,8 +38,7 @@ use CGI qw(-no_debug); use File::Spec 0.82; use Template; -use Test::More tests => ( scalar(@Support::Templates::referenced_files) - + scalar(@Support::Templates::actual_files) * 2); +use Test::More tests => ( scalar(@referenced_files) + $num_actual_files * 2 ); # Capture the TESTOUT from Test::More or Test::Builder for printing errors. # This will handle verbosity for us automatically. @@ -54,72 +54,84 @@ my $fh; } } -my $include_path = $Support::Templates::include_path; +# Checks whether one of the passed files exists +sub existOnce { + foreach my $file (@_) { + return $file if -e $file; + } + return 0; +} # Check to make sure all templates that are referenced in # Bugzilla exist in the proper place. -foreach my $file(@Support::Templates::referenced_files) { - my $path = File::Spec->catfile($include_path, $file); - if (-e $path) { - ok(1, "$path exists"); - } else { - ok(0, "$path does not exist --ERROR"); +foreach my $lang (@languages) { + foreach my $file (@referenced_files) { + my @path = map(File::Spec->catfile($_, $file), + split(':', $include_path{$lang})); + if (my $path = existOnce(@path)) { + ok(1, "$path exists"); + } else { + ok(0, "$file cannot be located --ERROR"); + print $fh "Looked in:\n " . join("\n ", @path); + } } } -# Processes all the templates to make sure they have good syntax -my $provider = Template::Provider->new( -{ - INCLUDE_PATH => $include_path , - # Need to define filters used in the codebase, they don't - # actually have to function in this test, just be defined. - # See globals.pl for the actual codebase definitions. - FILTERS => +foreach my $include_path (@include_paths) { + # Processes all the templates to make sure they have good syntax + my $provider = Template::Provider->new( { - html_linebreak => sub { return $_; }, - js => sub { return $_ } , - strike => sub { return $_ } , - url_quote => sub { return $_ } , - xml => sub { return $_ } , - quoteUrls => sub { return $_ } , - bug_link => [ sub { return sub { return $_; } }, 1] , - csv => sub { return $_ } , - time => sub { return $_ } , - }, -} -); - -foreach my $file(@Support::Templates::actual_files) { - my $path = File::Spec->catfile($include_path, $file); - if (-e $path) { - my ($data, $err) = $provider->fetch($file); - - if (!$err) { - ok(1, "$file syntax ok"); + INCLUDE_PATH => $include_path , + # Need to define filters used in the codebase, they don't + # actually have to function in this test, just be defined. + # See globals.pl for the actual codebase definitions. + FILTERS => + { + html_linebreak => sub { return $_; }, + js => sub { return $_ } , + strike => sub { return $_ } , + url_quote => sub { return $_ } , + xml => sub { return $_ } , + quoteUrls => sub { return $_ } , + bug_link => [ sub { return sub { return $_; } }, 1] , + csv => sub { return $_ } , + time => sub { return $_ } , + }, + } + ); + + foreach my $file (@{$actual_files{$include_path}}) { + my $path = File::Spec->catfile($include_path, $file); + if (-e $path) { + my ($data, $err) = $provider->fetch($file); + + if (!$err) { + ok(1, "$file syntax ok"); + } + else { + ok(0, "$file has bad syntax --ERROR"); + print $fh $data . "\n"; + } } else { - ok(0, "$file has bad syntax --ERROR"); - print $fh $data . "\n"; + ok(1, "$path doesn't exist, skipping test"); } } - else { - ok(1, "$path doesn't exist, skipping test"); - } -} -# check to see that all templates have a version string: + # check to see that all templates have a version string: -foreach my $file(@Support::Templates::actual_files) { - my $path = File::Spec->catfile($include_path, $file); - open(TMPL, $path); - my $firstline = ; - if ($firstline =~ /\d+\.\d+\@[\w\.-]+/) { - ok(1,"$file has a version string"); - } else { - ok(0,"$file does not have a version string --ERROR"); + foreach my $file (@{$actual_files{$include_path}}) { + my $path = File::Spec->catfile($include_path, $file); + open(TMPL, $path); + my $firstline = ; + if ($firstline =~ /\d+\.\d+\@[\w\.-]+/) { + ok(1,"$file has a version string"); + } else { + ok(0,"$file does not have a version string --ERROR"); + } + close(TMPL); } - close(TMPL); } exit 0; diff --git a/t/005no_tabs.t b/t/005no_tabs.t index f1d5f9be5..51433fe13 100644 --- a/t/005no_tabs.t +++ b/t/005no_tabs.t @@ -34,12 +34,13 @@ use Support::Templates; use File::Spec 0.82; use Test::More tests => ( scalar(@Support::Files::testitems) - + scalar(@Support::Templates::actual_files)); + + $Support::Templates::num_actual_files); my @testitems = @Support::Files::testitems; -my @templates = map(File::Spec->catfile($Support::Templates::include_path, $_), - @Support::Templates::actual_files); -push(@testitems, @templates); +for my $path (@Support::Templates::include_paths) { + push(@testitems, map(File::Spec->catfile($path, $_), + Support::Templates::find_actual_files($path))); +} foreach my $file (@testitems) { open (FILE, "$file"); diff --git a/t/Support/Templates.pm b/t/Support/Templates.pm index 4ef582de4..e90565392 100644 --- a/t/Support/Templates.pm +++ b/t/Support/Templates.pm @@ -19,6 +19,7 @@ # # Contributor(s): Jacob Steenhagen # David D. Kilzer +# Tobias Burnus # package Support::Templates; @@ -26,18 +27,60 @@ package Support::Templates; use strict; use lib 't'; -use vars qw($include_path @referenced_files @actual_files); +use base qw(Exporter); +@Support::Templates::EXPORT = + qw(@languages @include_paths %include_path @referenced_files + %actual_files $num_actual_files); +use vars qw(@languages @include_paths %include_path @referenced_files + %actual_files $num_actual_files); use Support::Files; use File::Find; use File::Spec 0.82; -# Note that $include_path is assumed to only contain ONE path, not -# a list of colon-separated paths. -$include_path = File::Spec->catdir('template', 'en', 'default'); +# The available template languages +@languages = (); + +# The colon separated includepath per language +%include_path = (); + +# All include paths +@include_paths = (); + +# Files which are referenced in the cgi files @referenced_files = (); -@actual_files = (); + +# All files sorted by include_path +%actual_files = (); + +# total number of actual_files +$num_actual_files = 0; + +# Scan for the template available languages and include paths +{ + opendir(DIR, "template") || die "Can't open 'template': $!"; + my @files = grep { /^[a-z-]+$/i } readdir(DIR); + closedir DIR; + + foreach my $langdir (@files) { + next if($langdir =~ /^CVS$/i); + + my $path = File::Spec->catdir('template', $langdir, 'custom'); + my @dirs = (); + push(@dirs, $path) if(-d $path); + $path = File::Spec->catdir('template', $langdir, 'default'); + push(@dirs, $path) if(-d $path); + + next if(scalar(@dirs) == 0); + push(@languages, $langdir); + push(@include_paths, @dirs); + $include_path{$langdir} = join(":",@dirs); + } +} + + +my @files; # Local subroutine used with File::Find sub find_templates { @@ -59,13 +102,23 @@ sub find_templates { $filename = $_; } - push(@actual_files, $filename); + push(@files, $filename); } } -# Scan the template include path for templates then put them in -# in the @actual_files array to be used by various tests. -map(find(\&find_templates, $_), split(':', $include_path)); +# Scan the given template include path for templates +sub find_actual_files { + my $include_path = $_[0]; + @files = (); + find(\&find_templates, $include_path); + return @files; +} + + +foreach my $include_path (@include_paths) { + $actual_files{$include_path} = [ find_actual_files($include_path) ]; + $num_actual_files += scalar(@{$actual_files{$include_path}}); +} # Scan Bugzilla's perl code looking for templates used and put them # in the @referenced_files array to be used by the 004template.t test. -- cgit v1.2.3-24-g4f1b