]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 126955 - Bugzilla should support translated/localized templates. Patch by burnus...
authorgerv%gerv.net <>
Fri, 24 Jan 2003 07:34:00 +0000 (07:34 +0000)
committergerv%gerv.net <>
Fri, 24 Jan 2003 07:34:00 +0000 (07:34 +0000)
Bugzilla/Template.pm
checksetup.pl
defparams.pl
t/004template.t
t/005no_tabs.t
t/Support/Templates.pm

index abcfd3ff60b6893875e2111781ae09cadd61aace..43478533204b364445446e33b4708e1902497c80 100644 (file)
@@ -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
index 59ebb195567430833b7b134a1fff03edffdbcfe4..f2c1ef7619bd2f2fa09c70510da6fa3d1a67659f 100755 (executable)
@@ -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);
+       }
     }
 }
 
index 922a9dfe2ebb11b69ff4a43e86fef137a5cf7fe8..f75ead4b275c96ce3be84dfc5e05f6100791a0f0 100644 (file)
@@ -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 ' .
index be0dd04ec65e8fa09542527ca1235891f25ff5e7..8429b774f8e80697d5548c85ed70224367f7d9b2 100644 (file)
@@ -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;
index f1d5f9be50410163594cff6e9ca6630b6c466644..51433fe134b3bc70709b61b7cfc56548a5964fad 100644 (file)
@@ -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");
index 4ef582de48e8c4c75d57e6c290506a8e0aed3ec0..e9056539249ae313c6cbc9ef370102c5622f771a 100644 (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.