Commit b996f2f7 authored by gerv%gerv.net's avatar gerv%gerv.net

Bug 126955 - Bugzilla should support translated/localized templates. Patch by…

Bug 126955 - Bugzilla should support translated/localized templates. Patch by burnus; r=gerv, a=justdave.
parent 55108104
......@@ -22,6 +22,8 @@
# Jacob Steenhagen <jake@bugzilla.org>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Christopher Aillon <christopher@aillon.com>
# Tobias Burnus <burnus@net-b.de>
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 <language-range>;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
......
......@@ -25,6 +25,7 @@
# Zach Lipton <zach@zachlipton.com>
# Jacob Steenhagen <jake@bugzilla.org>
# Bradley Baetz <bbaetz@student.usyd.edu.au>
# Tobias Burnus <burnus@net-b.de>
#
#
# 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);
}
}
}
......
......@@ -202,6 +202,26 @@ sub check_netmask {
},
{
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 ' .
'Bugzilla cookies. Normally this is the URI portion of your URL ' .
......
......@@ -20,6 +20,7 @@
# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
# Zach Lipton <zach@zachlipton.com>
# David D. Kilzer <ddkilzer@kilzer.net>
# Tobias Burnus <burnus@net-b.de>
#
#################
......@@ -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 = <TMPL>;
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 = <TMPL>;
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;
......@@ -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");
......
......@@ -19,6 +19,7 @@
#
# Contributor(s): Jacob Steenhagen <jake@bugzilla.org>
# David D. Kilzer <ddkilzer@kilzer.net>
# Tobias Burnus <burnus@net-b.de>
#
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.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment