Commit ba5a5c40 authored by mkanat%bugzilla.org's avatar mkanat%bugzilla.org

Bug 374227: Create a system for localizing basic installation strings

Patch By Max Kanat-Alexander <mkanat@bugzilla.org> (module owner) a=mkanat
parent af516107
......@@ -28,18 +28,20 @@ use strict;
use Bugzilla::Constants;
use File::Basename;
use POSIX ();
use Safe;
use base qw(Exporter);
our @EXPORT_OK = qw(
display_version_and_os
indicate_progress
install_string
vers_cmp
);
sub display_version_and_os {
# Display version information
printf "\n* This is Bugzilla " . BUGZILLA_VERSION . " on perl %vd\n", $^V;
my @os_details = POSIX::uname;
# 0 is the name of the OS, 2 is the major version,
my $os_name = $os_details[0] . ' ' . $os_details[2];
......@@ -47,8 +49,12 @@ sub display_version_and_os {
require Win32;
$os_name = Win32::GetOSName();
}
# 3 is the minor version.
print "* Running on $os_name $os_details[3]\n"
# $os_details[3] is the minor version.
print install_string('version_and_os', { bz_ver => BUGZILLA_VERSION,
perl_ver => sprintf('%vd', $^V),
os_name => $os_name,
os_ver => $os_details[3] })
. "\n";
}
sub indicate_progress {
......@@ -63,6 +69,126 @@ sub indicate_progress {
}
}
sub install_string {
my ($string_id, $vars) = @_;
_cache()->{template_include_path} ||= template_include_path();
my $path = _cache()->{template_include_path};
my $string_template;
# Find the first set of templates that defines this string.
foreach my $dir (@$path) {
my $file = "$dir/setup/strings.txt.pl";
next unless -e $file;
my $safe = new Safe;
$safe->rdo($file);
my %strings = %{$safe->varglob('strings')};
$string_template = $strings{$string_id};
last if $string_template;
}
die "No language defines the string '$string_id'" if !$string_template;
$vars ||= {};
my @replace_keys = keys %$vars;
foreach my $key (@replace_keys) {
my $replacement = $vars->{$key};
die "'$key' in '$string_id' is tainted: '$replacement'"
if is_tainted($replacement);
# We don't want people to start getting clever and inserting
# ##variable## into their values. So we check if any other
# key is listed in the *replacement* string, before doing
# the replacement. This is mostly to protect programmers from
# making mistakes.
if (grep($replacement =~ /##$key##/, @replace_keys)) {
die "Unsafe replacement for '$key' in '$string_id': '$replacement'";
}
$string_template =~ s/\Q##$key##\E/$replacement/g;
}
return $string_template;
}
sub template_include_path {
my ($params) = @_;
$params ||= {};
# Basically, the way this works is that we have a list of languages
# that we *want*, and a list of languages that Bugzilla actually
# supports. The caller tells us what languages they want, by setting
# $ENV{HTTP_ACCEPT_LANGUAGE} or $params->{only_language}. The languages
# we support are those specified in $params->{use_languages}. Otherwise
# we support every language installed in the template/ directory.
my @wanted;
if (defined $params->{only_language}) {
@wanted = ($params->{only_language});
}
else {
@wanted = _sort_accept_language($ENV{'HTTP_ACCEPT_LANGUAGE'} || '');
}
my @supported;
if (defined $params->{use_languages}) {
@supported = $params->{use_languages};
}
else {
my @dirs = glob(bz_locations()->{'templatedir'} . "/*");
@dirs = map(basename($_), @dirs);
@supported = grep($_ ne 'CVS', @dirs);
}
my @usedlanguages;
foreach my $wanted (@wanted) {
# If we support the language we want, or *any version* of
# the language we want, it gets pushed into @usedlanguages.
#
# Per RFC 1766 and RFC 2616, things like 'en' match 'en-us' and
# 'en-uk', but not the other way around. (This is unfortunately
# 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 /^\Q$wanted\E(-.+)?$/i, @supported) {
push (@usedlanguages, @found);
}
}
# If we didn't want *any* of the languages we support, just use all
# of the languages we said we support, in the order they were specified.
# This is only done when you ask for a certain set of languages, because
# otherwise @supported just came off the disk in alphabetical order,
# and it could give you de (German) when you speak English.
# (If @supported came off the disk, we fall back on English if no language
# is available--that happens below.)
if (!@usedlanguages && $params->{use_languages}) {
@usedlanguages = @supported;
}
# We always include English at the bottom if it's not there, even if
# somebody removed it from use_languages.
if (!grep($_ eq 'en', @usedlanguages)) {
push(@usedlanguages, 'en');
}
# Now, we add template directories in the order they will be searched:
# First, we add extension template directories, because extension templates
# override standard templates. Extensions may be localized in the same way
# that Bugzilla templates are localized.
my @include_path;
my @extensions = glob(bz_locations()->{'extensionsdir'} . "/*");
foreach my $extension (@extensions) {
foreach my $lang (@usedlanguages) {
_add_language_set(\@include_path, $lang, "$extension/template");
}
}
# Then, we add normal template directories, sorted by language.
foreach my $lang (@usedlanguages) {
_add_language_set(\@include_path, $lang);
}
return \@include_path;
}
# This is taken straight from Sort::Versions 1.5, which is not included
# with perl by default.
sub vers_cmp {
......@@ -106,6 +232,81 @@ sub vers_cmp {
@A <=> @B;
}
######################
# Helper Subroutines #
######################
# Used by template_include_path.
sub _add_language_set {
my ($array, $lang, $templatedir) = @_;
$templatedir ||= bz_locations()->{'templatedir'};
my @add = ("$templatedir/$lang/custom", "$templatedir/$lang/default");
my $project = bz_locations->{'project'};
push(@add, "$templatedir/$lang/$project") if $project;
foreach my $dir (@add) {
#if (-d $dir) {
trick_taint($dir);
push(@$array, $dir);
#}
}
}
# 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 _sort_accept_language {
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));
}
# This is like request_cache, but it's used only by installation code
# for setup.cgi and things like that.
our $_cache = {};
sub _cache {
if ($ENV{MOD_PERL}) {
require Apache2::RequestUtil;
return Apache2::RequestUtil->request->pnotes();
}
return $_cache;
}
###############################
# Copied from Bugzilla::Util #
##############################
sub trick_taint {
require Carp;
Carp::confess("Undef to trick_taint") unless defined $_[0];
my $match = $_[0] =~ /^(.*)$/s;
$_[0] = $match ? $1 : undef;
return (defined($_[0]));
}
sub is_tainted {
return not eval { my $foo = join('',@_), kill 0; 1; };
}
__END__
=head1 NAME
......@@ -173,6 +374,52 @@ ten items. Defaults to 1 if not specified.
=back
=item C<install_string>
=over
=item B<Description>
This is a very simple method of templating strings for installation.
It should only be used by code that has to run before the Template Toolkit
can be used. (See the comments at the top of the various L<Bugzilla::Install>
modules to find out when it's safe to use Template Toolkit.)
It pulls strings out of the F<strings.txt.pl> "template" and replaces
any variable surrounded by double-hashes (##) with a value you specify.
This allows for localization of strings used during installation.
=item B<Example>
Let's say your template string looks like this:
The ##animal## jumped over the ##plant##.
Let's say that string is called 'animal_jump_plant'. So you call the function
like this:
install_string('animal_jump_plant', { animal => 'fox', plant => 'tree' });
That will output this:
The fox jumped over the tree.
=item B<Params>
=over
=item C<$string_id> - The name of the string from F<strings.txt.pl>.
=item C<$vars> - A hashref containing the replacement values for variables
inside of the string.
=back
=item B<Returns>: The appropriate string, with variables replaced.
=back
=item C<vers_cmp>
=over
......
......@@ -62,6 +62,10 @@ require 5.008001 if ON_WINDOWS; # for CGI 2.93 or higher
# Live Code
######################################################################
# When we're running at the command line, we need to pick the right
# language before ever displaying any string.
$ENV{'HTTP_ACCEPT_LANGUAGE'} ||= setlocale(LC_CTYPE);
my %switch;
GetOptions(\%switch, 'help|h|?', 'check-modules', 'no-templates|t',
'verbose|v|no-silent', 'make-admin=s');
......@@ -116,10 +120,6 @@ Bugzilla->usage_mode(USAGE_MODE_CMDLINE);
Bugzilla->installation_mode(INSTALLATION_MODE_NON_INTERACTIVE) if $answers_file;
Bugzilla->installation_answers($answers_file);
# When we're running at the command line, we need to pick the right
# language before ever creating a template object.
$ENV{'HTTP_ACCEPT_LANGUAGE'} ||= setlocale(LC_CTYPE);
###########################################################################
# Check and update --LOCAL-- configuration
###########################################################################
......
# This file contains a single hash named %strings, which is used by the
# installation code to display strings before Template-Toolkit can safely
# be loaded.
#
# Each string supports a very simple substitution system, where you can
# have variables named like ##this## and they'll be replaced by the string
# variable with that name.
#
# Please keep the strings in alphabetical order by their name.
%strings = (
version_and_os => "* This is Bugzilla ##bz_ver## on perl ##perl_ver##\n"
. "* Running on ##os_name## ##os_ver##",
);
1;
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