Localisation layer for Bugzilla, separated from templates

Languages are put into i18n/<lang>/ and consist of:
* messages.pl - messages for translating templates, substituted at compile time
* runtime.pl - messages that are needed in runtime, setup/strings.txt.pl is moved here
* messages.js - messages for JavaScripts

Templates aren't subdivided into languages anymore; the single
'template/localized' directory is used, and [% L('Message text') %]
or [% L('Message $1 text is $2', 'Param1', 'Param2') %] is used in
the templates. Both forms are substituted at template compile time.

Localised templates are auto-generated from English ones by the
contrib/extract-strings.pl script which also uses i18n/en/blacklist.pl
to determine what strings shouldn't be translated.

So, the commit includes:
* contrib/extract-strings.pl -- script for translating TT templates
* i18n/en/blacklist.pl -- blacklist of messages that should not be auto-extracted
* Template adjustments for more correct string extraction
  (for example, TT does not allow expressions in hash keys)
* Template adjustments for string extraction from JavaScript blocks
* js/*.js adjustments for using i18n messages
* Compile-time i18n message substitution implementation
* Bugzilla::Language class that manages loading of i18n messages
i18n
Vitaliy Filippov 2014-09-24 19:48:03 +04:00
parent 349af79c2e
commit 8432b888fe
84 changed files with 15832 additions and 751 deletions

View File

@ -36,6 +36,7 @@ use Bugzilla::DB;
use Bugzilla::Install::Localconfig qw(read_localconfig);
use Bugzilla::Install::Requirements qw(OPTIONAL_MODULES);
use Bugzilla::Install::Util;
use Bugzilla::Language;
use Bugzilla::Template;
use Bugzilla::User;
use Bugzilla::Error;
@ -329,22 +330,40 @@ sub send_mail
}
}
sub i18n
{
my $class = shift;
my $cache = $class->request_cache;
if (!$cache->{i18n})
{
# First try to check language cookie, then user preference, then Accept-Language
# Accept-Language will be checked by Bugzilla::Language itself
my $user = $class->login(LOGIN_OPTIONAL);
my $lang = $class->cookies->{LANG} || ($user && $user->{settings}->{lang}->{value});
$cache->{i18n} = Bugzilla::Language->new({ selected_language => $lang });
}
return $cache->{i18n};
}
sub messages
{
my $class = shift;
return $class->i18n->runtime_messages;
}
sub template
{
my $class = shift;
$class->request_cache->{language} = "";
$class->request_cache->{template} ||= Bugzilla::Template->create();
return $class->request_cache->{template};
my $lang = $class->i18n->language;
return $class->request_cache->{template}->{$lang} ||= Bugzilla::Template->create(language => $lang);
}
sub template_inner
{
my ($class, $lang) = @_;
$lang = defined($lang) ? $lang : ($class->request_cache->{language} || "");
$class->request_cache->{language} = $lang;
$class->request_cache->{"template_inner_$lang"}
||= Bugzilla::Template->create();
return $class->request_cache->{"template_inner_$lang"};
$lang = $lang || $class->request_cache->{templater_inner_lang} || $class->i18n->language;
$class->request_cache->{template_inner_lang} = $lang;
return $class->request_cache->{template}->{$lang} ||= Bugzilla::Template->create(language => $lang);
}
sub session
@ -397,7 +416,6 @@ sub save_session_data
);
}
our $extension_packages;
sub extensions
{
my ($class) = @_;
@ -569,8 +587,7 @@ sub login
$type = $class->params->{requirelogin} ? LOGIN_REQUIRED : LOGIN_NORMAL;
}
# Allow templates to know that we're in a page that always requires
# login.
# Allow templates to know that we're in a page that always requires login.
if ($type == LOGIN_REQUIRED)
{
$class->request_cache->{page_requires_login} = 1;
@ -710,27 +727,6 @@ sub dbh_main
return $class->request_cache->{dbh_main};
}
sub languages
{
my $class = shift;
return $class->request_cache->{languages}
if $class->request_cache->{languages};
my @files = glob(catdir(bz_locations->{templatedir}, '*'));
my @languages;
foreach my $dir_entry (@files)
{
# It's a language directory only if it contains "default" or
# "custom". This auto-excludes CVS directories as well.
next unless (-d catdir($dir_entry, 'default') || -d catdir($dir_entry, 'custom'));
$dir_entry = basename($dir_entry);
# Check for language tag format conforming to RFC 1766.
next unless $dir_entry =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
push(@languages, $dir_entry);
}
return $class->request_cache->{languages} = \@languages;
}
sub error_mode
{
my ($class, $newval) = @_;
@ -840,23 +836,6 @@ sub switch_to_main_db
return $class->dbh_main;
}
sub messages
{
my $class = shift;
my $lc = $class->cookies->{LANG} || 'en';
$lc =~ s/\W+//so;
if (!$INC{'Bugzilla::Language::'.$lc})
{
eval { require 'Bugzilla/Language/'.$lc.'.pm' };
if ($@)
{
$lc = 'en';
require 'Bugzilla/Language/en.pm';
}
}
return $Bugzilla::messages->{$lc};
}
sub cache_fields
{
my $class = shift;

View File

@ -157,8 +157,6 @@ sub SendFlag
my $message;
$template->process("request/email.txt.tmpl", $email, \$message)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($message);
Bugzilla::Hook::process('flag-notify-post-send', { vars => $email });
@ -184,7 +182,6 @@ sub SendVotesRemoved
MessageToMTA($msg);
push @to, $voter->login;
}
Bugzilla->template_inner('');
return { sent => \@to, excluded => [] };
}
@ -649,9 +646,8 @@ sub sendMail
my $template = Bugzilla->template_inner($user->settings->{lang}->{value});
Bugzilla::Hook::process('bugmail-pre_template', { tmpl => \$tmpl, vars => $vars });
$tmpl = "email/newchangedmail.txt.tmpl" unless $template->template_exists($tmpl);
$template->process($tmpl, $vars, \$msg) || ThrowTemplateError($template->error());
Bugzilla->template_inner("");
$template->process("email/newchangedmail.txt.tmpl", $vars, \$msg)
|| ThrowTemplateError($template->error());
logMail($vars);
MessageToMTA($msg);

View File

@ -117,6 +117,8 @@ use Cwd qw(abs_path);
SENDMAIL_EXE
SENDMAIL_PATH
FIELD_TYPE_NAMES
FIELD_TYPE_UNKNOWN
FIELD_TYPE_FREETEXT
FIELD_TYPE_SINGLE_SELECT
@ -393,6 +395,21 @@ use constant FIELD_TYPE_NUMERIC => 30;
use constant FIELD_TYPE_EXTURL => 31;
use constant FIELD_TYPE_BUG_ID_REV => 32;
use constant FIELD_TYPE_NAMES => {
FIELD_TYPE_UNKNOWN() => 'UNKNOWN',
FIELD_TYPE_FREETEXT() => 'FREETEXT',
FIELD_TYPE_SINGLE_SELECT() => 'SINGLE_SELECT',
FIELD_TYPE_MULTI_SELECT() => 'MULTI_SELECT',
FIELD_TYPE_TEXTAREA() => 'TEXTAREA',
FIELD_TYPE_DATETIME() => 'DATETIME',
FIELD_TYPE_BUG_ID() => 'BUG_ID',
FIELD_TYPE_BUG_URLS() => 'BUG_URLS',
FIELD_TYPE_KEYWORDS() => 'KEYWORDS',
FIELD_TYPE_NUMERIC() => 'NUMERIC',
FIELD_TYPE_EXTURL() => 'EXTURL',
FIELD_TYPE_BUG_ID_REV() => 'BUG_ID_REV',
};
use constant FLAG_VISIBLE => 0;
use constant FLAG_NULLABLE => -1;
use constant FLAG_CLONED => -2;

View File

@ -57,8 +57,7 @@ use constant SETTINGS => {
# 2006-08-04 wurblzap@gmail.com -- Bug 322693
skin => { subclass => 'Skin', default => 'Mozilla' },
# 2006-12-10 LpSolit@gmail.com -- Bug 297186
lang => { subclass => 'Lang',
default => ${Bugzilla->languages}[0] },
lang => { subclass => 'Lang', default => 'en' },
# 2007-07-02 altlist@gmail.com -- Bug 225731
quote_replies => { options => ['quoted_reply', 'simple_reply', 'off'],
default => "quoted_reply" },

View File

@ -26,6 +26,7 @@ use strict;
use Bugzilla::Constants;
use Bugzilla::Extension;
use Bugzilla::Language;
use File::Basename;
use POSIX qw(setlocale LC_CTYPE);
@ -38,7 +39,6 @@ our @EXPORT_OK = qw(
get_version_and_os
indicate_progress
install_string
include_languages
template_include_path
vers_cmp
get_console_locale
@ -97,21 +97,10 @@ sub indicate_progress
sub install_string
{
my ($string_id, $vars) = @_;
_cache()->{install_string_path} ||= template_include_path();
my $path = _cache()->{install_string_path};
my $string_template;
# Find the first template that defines this string.
foreach my $dir (@$path)
{
my $base = "$dir/setup/strings";
if (!defined $string_template)
{
$string_template = _get_string_from_file($string_id, "$base.txt.pl");
}
last if defined $string_template;
}
my $lang = (_cache()->{language} ||= Bugzilla::Language->new());
my $string_template = $lang->runtime_messages->{install_strings}->{$string_id};
if (!defined $string_template)
{
# Don't throw an error, it's a stupid way -- <vitalif@yourcmc.ru>
@ -142,128 +131,27 @@ sub install_string
return $string_template;
}
sub include_languages
{
# If we are in CGI mode (not in checksetup.pl) and if the function has
# been called without any parameter, then we cache the result of this
# function in Bugzilla->request_cache. This is done to improve the
# performance of the template processing.
my $to_be_cached = 0;
if (not @_)
{
my $cache = _cache();
if (exists $cache->{include_languages})
{
return @{ $cache->{include_languages} };
}
$to_be_cached = 1;
}
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}, using the "LANG" cookie or setting
# $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 ($params->{only_language})
{
# We can pass several languages at once as an arrayref
# or a single language.
if (ref $params->{only_language})
{
@wanted = @{ $params->{only_language} };
}
else
{
@wanted = ($params->{only_language});
}
}
else
{
@wanted = _sort_accept_language($ENV{HTTP_ACCEPT_LANGUAGE} || '');
# Don't use the cookie if we are in "checksetup.pl". The test
# with $ENV{SERVER_SOFTWARE} is the same as in
# Bugzilla:Util::i_am_cgi.
if (exists $ENV{SERVER_SOFTWARE} && defined (my $lang = Bugzilla->cookies->{LANG}))
{
unshift @wanted, $lang;
}
}
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);
}
}
# 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');
}
# Cache the result if we are in CGI mode and called without parameter
# (see the comment at the top of this function).
if ($to_be_cached)
{
_cache()->{include_languages} = \@usedlanguages;
}
return @usedlanguages;
}
# Used by template_include_path
sub _template_lang_directories
sub _template_custom_directories
{
my ($languages, $templatedir) = @_;
my ($templatedir) = @_;
my @add = qw(custom default);
my $project = bz_locations->{project};
unshift(@add, $project) if $project;
unshift @add, $project if $project;
my @result;
foreach my $lang (@$languages)
foreach my $dir (@add)
{
foreach my $dir (@add)
my $full_dir = "$templatedir/localized/$dir";
if (-d $full_dir)
{
my $full_dir = "$templatedir/$lang/$dir";
if (-d $full_dir)
{
trick_taint($full_dir);
push(@result, $full_dir);
}
trick_taint($full_dir);
push(@result, $full_dir);
}
}
return @result;
}
# Used by template_include_path.
# Used by template_include_path
sub _template_base_directories
{
my @template_dirs;
@ -284,22 +172,21 @@ sub _template_base_directories
sub template_include_path
{
my ($params) = @_;
my @used_languages = include_languages($params);
# Now, we add template directories in the order they will be searched:
my $template_dirs = _template_base_directories();
my @include_path;
foreach my $template_dir (@$template_dirs)
{
my @lang_dirs = _template_lang_directories(\@used_languages, $template_dir);
my @dirs = _template_custom_directories($template_dir);
# Hooks get each set of extension directories separately.
if ($params->{hook})
{
push @include_path, \@lang_dirs if @lang_dirs;
push @include_path, \@dirs if @dirs;
}
# Whereas everything else just gets a whole INCLUDE_PATH.
else
{
push @include_path, @lang_dirs;
push @include_path, @dirs;
}
}
# Allow to fallback to full template path - not a security risk,
@ -388,33 +275,6 @@ sub _get_string_from_file
return $strings{$string_id};
}
# 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
{
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 { $b->{qvalue} <=> $a->{qvalue} } @qlanguages));
}
sub get_console_locale
{
require Locale::Language;
@ -498,24 +358,12 @@ sub _cache
sub trick_taint
{
require Carp;
Carp::confess("Undef to trick_taint") unless defined $_[0];
return undef unless defined $_[0];
my $match = $_[0] =~ /^(.*)$/s;
$_[0] = $match ? $1 : undef;
return (defined($_[0]));
}
sub trim
{
my ($str) = @_;
if ($str)
{
$str =~ s/^\s+//g;
$str =~ s/\s+$//g;
}
return $str;
}
1;
__END__
@ -685,12 +533,6 @@ Each extension has its own directory.
Note that languages are sorted by the user's preference (as specified
in their browser, usually), and extensions are sorted alphabetically.
=item C<include_languages>
Used by L<Bugzilla::Template> to determine the languages' list which
are compiled with the browser's I<Accept-Language> and the languages
of installed templates.
=item C<vers_cmp>
=over

182
Bugzilla/Language.pm Normal file
View File

@ -0,0 +1,182 @@
#!/usr/bin/perl
# Localisation layer, responsible for loading i18n messages
# FIXME: Allow extensions to have their own i18n with the same structure
# License: Dual-license GPL 3.0+ or MPL 1.1+
# Author: Vitaliy Filippov <vitalif@mail.ru>, 2014
package Bugzilla::Language;
use utf8;
use strict;
use Bugzilla::Constants;
use File::Spec;
use File::Basename;
my $message_cache = {};
my $mtime = {};
# Bugzilla::Language is used in Bugzilla::Install::Util, so it shouldn't depend on Bugzilla::Util
sub trick_taint
{
return undef unless defined $_[0];
my $match = $_[0] =~ /^(.*)$/s;
$_[0] = $match ? $1 : undef;
return (defined($_[0]));
}
sub new
{
my $class = shift;
$class = ref($class) || $class;
my ($params) = @_;
return bless { selected_language => $params->{selected_language} }, $class;
}
# 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 get_accept_language
{
my $self = shift;
return $self->{accept_language} if $self->{accept_language};
my $accept_language = $ENV{HTTP_ACCEPT_LANGUAGE};
# 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 $self->{accept_language} = [ map { $_->{language} } sort { $b->{qvalue} <=> $a->{qvalue} } @qlanguages ];
}
sub supported_languages
{
my $self = shift;
return $self->{languages} if $self->{languages};
my @files = glob(File::Spec->catfile(bz_locations->{cgi_path}, 'i18n', '*'));
my @languages;
foreach my $dir_entry (@files)
{
# It's a language directory only if it contains "messages.pl".
# This auto-excludes various VCS directories as well.
next unless -f File::Spec->catfile($dir_entry, 'messages.pl');
$dir_entry = basename($dir_entry);
# Check for language tag format conforming to RFC 1766.
next unless $dir_entry =~ /^[a-zA-Z]{1,8}(-[a-zA-Z]{1,8})?$/;
trick_taint($dir_entry);
push @languages, $dir_entry;
}
return $self->{languages} = \@languages;
}
sub language
{
my $self = shift;
if (!$self->{language})
{
$self->{language} = $self->{selected_language};
my $supported = {};
for (@{$self->supported_languages})
{
$supported->{$_} = $_;
if (/^(.*)-(.*)$/so && !$supported->{$1})
{
# 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)
$supported->{$1} = $_;
}
}
if (!$self->{language})
{
($self->{language}) = map { $supported->{$_} || () } @{$self->get_accept_language};
}
else
{
$self->{language} = $supported->{$self->{language}};
}
$self->{language} ||= 'en';
}
return $self->{language};
}
sub runtime_messages
{
my $self = shift;
my ($lang) = @_;
$lang ||= $self->language;
if (!$self->{runtime_checked}->{$lang})
{
my $file = File::Spec->catfile(bz_locations->{cgi_path}, 'i18n', $lang, 'runtime.pl');
my $m = [ stat $file ]->[9];
if (!$message_cache->{$lang} || $m && $mtime->{$lang} < $m)
{
$mtime->{$lang} = $m;
$message_cache->{$lang} = do $file;
warn $@ if $@;
# Substitute $terms.key and $constants.key during load
for my $sect (values %{$message_cache->{$lang} || {}})
{
for (values %$sect)
{
s/\$terms.(\w+)/$message_cache->{$lang}->{terms}->{$1}/ges;
s/\$constants.(\w+)/eval { Bugzilla::Constants->$1() } || '$constants.'.$1/ges;
}
}
}
$self->{runtime_checked}->{$lang} = 1;
}
return $message_cache->{$lang} || {};
}
sub template_messages
{
my $self = shift;
my ($lang) = @_;
$lang ||= $self->language;
if (!$self->{template_messages}->{$lang})
{
# Template messages are needed only during compilation,
# so they are only loaded for a single request
my $file = File::Spec->catfile(bz_locations->{cgi_path}, 'i18n', $lang, 'messages.pl');
if (-e $file)
{
$self->{template_messages}->{$lang} = do $file;
warn $@ if $@;
}
}
return $self->{template_messages}->{$lang};
}
sub template_messages_mtime
{
my $self = shift;
my ($lang) = @_;
$lang ||= $self->language;
if (!exists $self->{template_message_mtime}->{$lang})
{
my $file = File::Spec->catfile(bz_locations->{cgi_path}, 'i18n', $lang, 'messages.pl');
$self->{template_message_mtime}->{$lang} = [ stat $file ]->[9];
}
return $self->{template_message_mtime}->{$lang};
}
1;
__END__

View File

@ -1,241 +0,0 @@
#!/usr/bin/perl
# Internationalisation messages for English Bugzilla
package Bugzilla::Language::en;
use strict;
use Bugzilla::Constants;
my $terms = {
bug => 'bug',
Bug => 'Bug',
abug => 'a bug',
Abug => 'A bug',
aBug => 'a Bug',
ABug => 'A Bug',
bugs => 'bugs',
Bugs => 'Bugs',
zeroSearchResults => 'Zarro Boogs found',
Bugzilla => 'Bugzilla',
};
$Bugzilla::messages->{en} = {
terms => $terms,
operator_descs => {
not => 'NOT',
noop => '---',
equals => 'is equal to',
notequals => 'is not equal to',
anyexact => 'is equal to any of the strings',
substring => 'contains the string',
notsubstring => 'does not contain the string',
casesubstring => 'contains the string (exact case)',
notcasesubstring => 'does not contain the string (exact case)',
anywordssubstr => 'contains any of the strings',
allwordssubstr => 'contains all of the strings',
nowordssubstr => 'contains none of the strings',
regexp => 'matches regular expression',
notregexp => 'does not match regular expression',
lessthan => 'is less than',
lessthaneq => 'is less than or equal to',
greaterthan => 'is greater than',
greaterthaneq => 'is greater than or equal to',
anywords => 'contains any of the words',
allwords => 'contains all of the words',
nowords => 'contains none of the words',
matches => 'matches',
notmatches => 'does not match',
insearch => 'matched by saved search',
notinsearch => 'not matched by saved search',
changedbefore => 'changed before',
changedafter => 'changed after',
changedfrom => 'changed from',
changedto => 'changed to',
changedby => 'changed by',
# Names with other_ prefix are used with correlated search terms
other_changedbefore => 'change $1 before',
other_changedafter => 'change $1 after',
other_changedfrom => 'change $1 from',
other_changedto => 'change $1 to',
other_changedby => 'change $1 by',
desc_between => 'between $1 and $2',
desc_before => 'before $2',
desc_after => 'after $1',
desc_by => 'by $1',
desc_fields => 'one of $1',
},
field_types => {
FIELD_TYPE_UNKNOWN() => 'Unknown Type',
FIELD_TYPE_FREETEXT() => 'Free Text',
FIELD_TYPE_SINGLE_SELECT() => 'Drop Down',
FIELD_TYPE_MULTI_SELECT() => 'Multiple-Selection Box',
FIELD_TYPE_TEXTAREA() => 'Large Text Box',
FIELD_TYPE_DATETIME() => 'Date/Time',
FIELD_TYPE_BUG_ID() => $terms->{Bug}.' ID',
FIELD_TYPE_BUG_URLS() => $terms->{Bug}.' URLs',
FIELD_TYPE_KEYWORDS() => '(Invalid type) Keywords',
FIELD_TYPE_NUMERIC() => 'Numeric',
FIELD_TYPE_EXTURL() => 'External URL',
FIELD_TYPE_BUG_ID_REV() => $terms->{Bug}.' ID reverse',
},
control_options => {
CONTROLMAPNA() => 'NA',
CONTROLMAPSHOWN() => 'Shown',
CONTROLMAPDEFAULT() => 'Default',
CONTROLMAPMANDATORY() => 'Mandatory',
},
field_descs => {
alias => 'Alias',
assigned_to => 'Assignee',
blocked => 'Blocks',
bug_file_loc => 'URL',
bug_group => 'Group',
bug_id => $terms->{Bug}.' ID',
bug_severity => 'Severity',
bug_status => 'Status',
cc => 'CC',
classification => 'Classification',
cclist_accessible => 'CC list accessible',
component_id => 'Component ID',
component => 'Component',
content => 'Content',
comment => 'Comment',
changes => 'Changed',
'[Bug creation]' => 'Creation date',
opendate => 'Creation date',
creation_ts => 'Creation date',
deadline => 'Deadline',
changeddate => 'Changed',
delta_ts => 'Changed',
dependson => 'Depends on',
dup_id => 'Duplicate of',
estimated_time => 'Orig. Est.',
everconfirmed => 'Ever confirmed',
keywords => 'Keywords',
newcc => 'CC',
op_sys => 'OS',
owner_idle_time => 'Time Since Assignee Touched',
days_elapsed => 'Days since bug changed',
percentage_complete => '% Complete',
priority => 'Priority',
product_id => 'Product ID',
product => 'Product',
qa_contact => 'QA Contact',
remaining_time => 'Hours Left',
rep_platform => 'Hardware',
reporter => 'Reporter',
reporter_accessible => 'Reporter accessible',
resolution => 'Resolution',
see_also => 'See Also',
setting => 'Setting',
settings => 'Settings',
short_desc => 'Summary',
status_whiteboard => 'Whiteboard',
target_milestone => 'Target Milestone',
version => 'Version',
votes => 'Votes',
comment0 => 'First Comment',
interval_time => 'Period Worktime',
work_time => 'Hours Worked',
actual_time => 'Hours Worked',
longdesc => 'Comment',
commenter => 'Commenter',
'longdescs.isprivate' => 'Comment is private',
'attachments.description' => 'Attachment description',
'attachments.filename' => 'Attachment filename',
'attachments.mimetype' => 'Attachment mime type',
'attachments.ispatch' => 'Attachment is patch',
'attachments.isobsolete' => 'Attachment is obsolete',
'attachments.isprivate' => 'Attachment is private',
'attachments.submitter' => 'Attachment creator',
'flagtypes.name' => 'Flags and Requests',
'requestees.login_name' => 'Flag Requestee',
'setters.login_name' => 'Flag Setter',
# Names with other_ prefix are to be used with correlated search terms
other_work_time => 'Hours Worked $1',
other_longdesc => 'Comment $1',
other_commenter => 'Comment $1 author',
'other_longdescs.isprivate' => 'Comment $1 is private',
'other_attachments.description' => 'Attachment $1 description',
'other_attachments.filename' => 'Attachment $1 filename',
'other_attachments.mimetype' => 'Attachment $1 mime type',
'other_attachments.ispatch' => 'Attachment $1 is patch',
'other_attachments.isobsolete' => 'Attachment $1 is obsolete',
'other_attachments.isprivate' => 'Attachment $1 is private',
'other_attachments.submitter' => 'Attachment $1 creator',
'other_flagtypes.name' => 'Flag $1 Type',
'other_requestees.login_name' => 'Flag $1 Requestee',
'other_setters.login_name' => 'Flag $1 Setter',
},
setting_descs => {
'comment_sort_order' => "When viewing $terms->{abug}, show comments in this order",
'csv_colsepchar' => 'Field separator character for CSV files',
'display_quips' => "Show a quip at the top of each $terms->{bug} list",
'zoom_textareas' => 'Zoom textareas large when in use (requires JavaScript)',
'newest_to_oldest' => 'Newest to Oldest',
'newest_to_oldest_desc_first' => 'Newest to Oldest, but keep Description at the top',
'off' => 'Off',
'oldest_to_newest' => 'Oldest to Newest',
'on' => 'On',
'post_bug_submit_action' => "After changing $terms->{abug}",
'next_bug' => "Show next $terms->{bug} in my list",
'same_bug' => "Show the updated $terms->{bug}",
'standard' => 'Classic',
'skin' => "$terms->{Bugzilla}'s general appearance (skin)",
'nothing' => 'Do Nothing',
'state_addselfcc' => "Automatically add me to the CC list of $terms->{bugs} I change",
'always' => 'Always',
'never' => 'Never',
'cc_unless_role' => 'Only if I have no role on them',
'lang' => 'Language used in email',
'quote_replies' => 'Quote the associated comment when you click on its reply link',
'quoted_reply' => 'Quote the full comment',
'simple_reply' => 'Reference the comment number only',
'timezone' => 'Timezone used to display dates and times',
'local' => 'Same as the server',
'remind_me_about_worktime' => 'Remind me to track worktime for each comment',
'remind_me_about_worktime_newbug' => 'Remind me to track worktime for new bugs',
'remind_me_about_flags' => 'Remind me about flag requests',
'saved_searches_position' => 'Position of Saved Searches bar',
'footer' => 'Page footer',
'header' => 'Page header',
'both' => 'Both',
'csv_charset' => 'Character set for CSV import and export',
'silent_affects_flags' => 'Do not send flag email under Silent mode',
'send' => 'Send',
'do_not_send' => 'Don\'t send',
'showhide_comments' => 'Which comments can be marked as collapsed',
'none' => 'None',
'worktime' => 'With worktime only',
'all' => 'All',
'comment_width' => 'Show comments in the full screen width',
'preview_long_comments' => 'Fold long comments',
'clear_requests_on_close' => 'Clear flag requests when closing bugs',
'show_gravatars' => 'Show avatar images (Gravatars)',
},
system_groups => {
admin => 'Administrator group. Usually allows to access <b>all</b> administrative functions.',
admin_index => 'Allows to <a href="admin.cgi">enter Administration area</a>, granted automatically if you can access any of the administrative functions.',
tweakparams => 'Allows to <a href="editparams.cgi">change Parameters</a>.',
editusers => 'Allows to <a href="editusers.cgi">edit or disable users</a> and include/exclude them from <b>all</b> groups.',
creategroups => 'Allows to <a href="editgroups.cgi">create, destroy and edit groups</a>.',
createproducts => 'Allows to <a href="editproducts.cgi">create new products</a>.',
editclassifications => 'Allows to <a href="editclassifications.cgi">create, destroy and edit classifications</a>.',
editcomponents => 'Allows to <a href="editproducts.cgi">create, destroy and edit all products, components, versions and milestones</a>.',
editkeywords => 'Allows to <a href="editvalues.cgi?field=keywords">create, destroy and edit keywords</a>.',
editbugs => 'Allows to edit all fields of all bugs.',
canconfirm => 'Allows to confirm bugs or mark them as duplicates.',
bz_canusewhineatothers => 'Allows to <a href="editwhines.cgi">configure whine reports for other users</a>.',
bz_canusewhines => 'Allows to <a href="editwhines.cgi">configure whine reports for self</a>.',
bz_sudoers => 'Allows to <a href="relogin.cgi?action=prepare-sudo">impersonate other users</a> and perform actions as them.',
bz_sudo_protect => 'Forbids other users to impersonate you.',
bz_editcheckers => 'Allows to <a href="editcheckers.cgi">edit Predicates</a> ("Correctness Checkers").',
editfields => 'Allows to <a href="editfields.cgi">create, destroy and edit properties of bug fields</a>.',
editvalues => 'Allows to <a href="editvalues.cgi">edit allowed values for all bug fields</a>.',
importxls => 'Allows to <a href="importxls.cgi">create or update many bugs at once using Excel and CSV files</a>.',
worktimeadmin => 'Allows to register working time for other users and for dates in the past (see "Fix Worktime" link under a bug list).',
editflagtypes => 'Allows to <a href="editflagtypes.cgi">create, destroy and edit flag types</a>.',
},
};
__END__

View File

@ -37,7 +37,7 @@ use Bugzilla::Bug;
use Bugzilla::Constants;
use Bugzilla::Hook;
use Bugzilla::Install::Requirements;
use Bugzilla::Install::Util qw(install_string template_include_path include_languages);
use Bugzilla::Install::Util qw(install_string template_include_path);
use Bugzilla::Keyword;
use Bugzilla::Util;
use Bugzilla::User;
@ -45,6 +45,7 @@ use Bugzilla::Error;
use Bugzilla::Status;
use Bugzilla::Token;
use Bugzilla::Template::Plugin::Bugzilla;
use Bugzilla::Template::Plugin::Hook;
use Cwd qw(abs_path);
use MIME::Base64;
@ -60,6 +61,9 @@ use Scalar::Util qw(blessed);
use base qw(Template);
our $CURRENT_TEMPLATE;
our $COMPILE_LANGUAGE;
my ($custom_proto, $custom_proto_regex, $custom_proto_cached, $custom_wiki_urls, $custom_wiki_proto);
# Convert the constants in the Bugzilla::Constants module into a hash we can
@ -85,21 +89,6 @@ sub _load_constants
return \%constants;
}
# 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
# Templates may also be found in the extensions/ tree
sub getTemplateIncludePath
{
my $cache = Bugzilla->request_cache;
my $lang = $cache->{language} || '';
$cache->{"template_include_path_$lang"} ||= template_include_path({
use_languages => Bugzilla->languages,
only_language => $lang,
});
return $cache->{"template_include_path_$lang"};
}
sub get_format
{
my $self = shift;
@ -142,20 +131,6 @@ sub get_format
};
}
# check whether a template exists
sub template_exists
{
my $self = shift;
my $file = shift or return;
my $include_path = $self->context->load_templates->[0]->include_path;
return unless ref $include_path eq 'ARRAY';
foreach my $path (@$include_path)
{
return $path if -e "$path/$file";
}
return undef;
}
sub makeTables
{
my ($comment) = @_;
@ -533,10 +508,10 @@ sub get_bug_link
$bug = blessed($bug) ? $bug : new Bugzilla::Bug($bug);
return $link_text if !$bug;
my $title = get_text('get_status', { status => $bug->bug_status_obj->name });
my $title = $bug->bug_status_obj->name;
if ($bug->resolution)
{
$title .= ' ' . get_text('get_resolution', { resolution => $bug->resolution_obj->name });
$title .= ' ' . $bug->resolution_obj->name;
}
my $cansee = Bugzilla->user->can_see_bug($bug);
if (Bugzilla->params->{unauth_bug_details} || $cansee)
@ -569,6 +544,7 @@ sub get_bug_link
use Template::Directive;
use Template::Stash;
use Template::Exception;
no warnings 'redefine';
# The Template Toolkit throws an error if a loop iterates >1000 times.
# We want to raise that limit.
@ -576,6 +552,51 @@ use Template::Exception;
# If you do not re-run checksetup.pl, the change you make will not apply
$Template::Directive::WHILE_MAX = 1000000;
# Override ident() to allow compile-time substitution of i18n strings
our $old_ident;
$old_ident ||= \&Template::Directive::ident;
*Template::Directive::ident = sub
{
my ($self, $ident) = @_;
if (@$ident == 2 && $ident->[0] eq "'L'" &&
$ident->[1] =~ /^\s*\[\s*\'((?:[^\\\']+|\\.)+)\'\s*(,.+)?\]\s*$/so)
{
my $id = $1;
my $params = $2;
$id =~ s/\\(.)/$1/gso;
my $msg = Bugzilla->i18n->template_messages($COMPILE_LANGUAGE)->{$CURRENT_TEMPLATE};
my $rtmsg;
$msg = $msg->{$id} || $id;
$msg =~ s/\$terms.(\w+)/$rtmsg ||= Bugzilla->i18n->runtime_messages($COMPILE_LANGUAGE); $rtmsg->{terms}->{$1}/ges;
$msg =~ s/\$constants.(\w+)/eval { Bugzilla::Constants->$1() || "<Unknown Constant>" }/ges;
$msg =~ s/([\\\'])/\\$1/gso;
$msg = "'$msg'";
if ($params)
{
$params =~ s/^[,\s]+//so;
$params =~ s/[,\s]+$//so;
my @a;
while ($params =~ s/^((?:
[^,\(\)\{\}\[\]\"\']+ |
\"(?:(?:[^\\\"]+|\\.)*)\" |
\'(?:(?:[^\\\']+|\\.)*)\' |
\((?:(?1),?)*\) |
\{(?:(?1),?)*\} |
\[(?:(?1),?)*\]
)+),?\s*//xiso)
{
push @a, $1;
}
if ($msg =~ s/\$(\d+)/"'.".$a[$1-1].".'"/ges)
{
$msg = "($msg)";
}
}
return $msg;
}
return &$old_ident($self, $ident);
};
# Allow keys to start with an underscore or a dot.
$Template::Stash::PRIVATE = undef;
@ -642,12 +663,16 @@ sub create
my $class = shift;
my %opts = @_;
$opts{language} ||= 'en';
my $lc_messages = Bugzilla->i18n->runtime_messages($opts{language});
# IMPORTANT - If you make any FILTER changes here, make sure to
# make them in t/004.template.t also, if required.
my $config = {
# Colon-separated list of directories containing templates.
INCLUDE_PATH => $opts{include_path} || getTemplateIncludePath(),
INCLUDE_PATH => $opts{include_path} || template_include_path(),
# Remove white-space before template directives (PRE_CHOMP) and at the
# beginning and end of templates and template blocks (TRIM) for better
@ -664,10 +689,7 @@ sub create
ABSOLUTE => 1,
RELATIVE => $ENV{MOD_PERL} ? 0 : 1,
COMPILE_DIR => bz_locations()->{datadir} . "/template",
# Initialize templates (f.e. by loading plugins like Hook).
PRE_PROCESS => ["global/initialize.none.tmpl"],
COMPILE_DIR => bz_locations()->{datadir} . "/template/" . $opts{language},
ENCODING => Bugzilla->params->{utf8} ? 'UTF-8' : undef,
@ -944,14 +966,21 @@ sub create
PLUGIN_BASE => 'Bugzilla::Template::Plugin',
CONSTANTS => _load_constants(),
# Default variables for all templates
VARIABLES => {
terms => Bugzilla->messages->{terms},
field_descs => Bugzilla->messages->{field_descs},
lc_messages => Bugzilla->messages,
LANGUAGE => $opts{language},
Bugzilla => Bugzilla::Template::Plugin::Bugzilla->new,
constants => _load_constants(),
terms => $lc_messages->{terms},
field_descs => $lc_messages->{field_descs},
lc_messages => $lc_messages,
# All template messages should be substituted during compilation
L => sub { die "Non-constant message keys are not supported" },
# html_quote in the form of a function
html => \&html_quote,
# html_quote in the form of a function
html => \&html_quote,
@ -1020,14 +1049,6 @@ sub create
# If an sudo session is in progress, this is the user we're faking
user => sub { return Bugzilla->user; },
# Currenly active language
# FIXME Eventually this should probably be replaced with something like Bugzilla->language.
current_language => sub
{
my ($language) = include_languages();
return $language;
},
# If an sudo session is in progress, this is the user who
# started the session.
sudoer => sub { return Bugzilla->sudoer; },
@ -1036,10 +1057,16 @@ sub create
urlbase => sub { return Bugzilla::Util::correct_urlbase(); },
# Allow templates to access docs url with users' preferred language
docs_urlbase => sub {
my ($language) = include_languages();
docs_urlbase => sub
{
my $docs_urlbase = Bugzilla->params->{docs_urlbase};
$docs_urlbase =~ s/\%lang\%/$language/;
my $lang = $opts{language};
# FIXME maybe use accept_languages?
if (!-d File::Spec->catfile(bz_locations()->{cgi_dir}, 'docs', $lang))
{
$lang = 'en';
}
$docs_urlbase =~ s/\%lang\%/$lang/;
return $docs_urlbase;
},
@ -1089,15 +1116,18 @@ sub create
};
local $Template::Config::CONTEXT = 'Bugzilla::Template::Context';
local $Template::Config::PROVIDER = 'Bugzilla::Template::Provider';
Bugzilla::Hook::process('template_before_create', { config => $config });
my $template = $class->new($config)
|| die("Template creation failed: " . $class->error());
my $template = $class->new($config) || die("Template creation failed: " . $class->error());
$template->context->{CONFIG}->{VARIABLES}->{Hook} = Bugzilla::Template::Plugin::Hook->new($template->context);
$template->{bz_language} = $template->context->{bz_language} = $opts{language};
return $template;
}
# Used as part of the two subroutines below.
our %_templates_to_precompile;
sub precompile_templates
{
my ($output) = @_;
@ -1126,21 +1156,34 @@ sub precompile_templates
print install_string('template_precompile') if $output;
my $paths = template_include_path({
use_languages => Bugzilla->languages,
only_language => Bugzilla->languages,
});
my $templates_to_precompile = {};
my $paths = template_include_path();
foreach my $dir (@$paths)
{
my $template = Bugzilla::Template->create(include_path => [$dir]);
%_templates_to_precompile = ();
# Traverse the template hierarchy.
find({ wanted => \&_precompile_push, no_chdir => 1 }, $dir);
find({
wanted => sub
{
my $name = $File::Find::name;
return if -d $name;
return if $name =~ /\/(CVS|\.svn|\.git|\.hg|\.bzr)\//;
return if $name !~ /\.tmpl$/;
$templates_to_precompile->{substr($name, 1+length $dir)} = 1;
},
no_chdir => 1,
}, $dir);
}
# FIXME: Do not precompile ALL languages when we'll have more than several.
my $languages = Bugzilla->i18n->supported_languages;
foreach my $lang (@$languages)
{
my $template = Bugzilla::Template->create(language => $lang);
# The sort isn't totally necessary, but it makes debugging easier
# by making the templates always be compiled in the same order.
foreach my $file (sort keys %_templates_to_precompile)
foreach my $file (sort keys %$templates_to_precompile)
{
$file =~ s{^\Q$dir\E/}{};
local $Bugzilla::Template::CURRENT_TEMPLATE = $file;
# Compile the template but throw away the result. This has the side-
# effect of writing the compiled version to disk.
$template->context->template($file);
@ -1166,16 +1209,6 @@ sub precompile_templates
print install_string('done') . "\n" if $output;
}
# Helper for precompile_templates
sub _precompile_push
{
my $name = $File::Find::name;
return if -d $name;
return if $name =~ /\/(CVS|\.svn|\.git|\.hg|\.bzr)\//;
return if $name !~ /\.tmpl$/;
$_templates_to_precompile{$name} = 1;
}
# Helper for precompile_templates
sub _do_template_symlink
{

View File

@ -60,6 +60,12 @@ sub process
return $result;
}
sub template {
my $self = shift;
local $Bugzilla::Template::COMPILE_LANGUAGE = $self->{bz_language};
return $self->SUPER::template(@_);
}
# This method is called by Template-Toolkit exactly once per template or
# block (look at a compiled template) so this is an ideal place for us to
# modify the variables before a template or block runs.

View File

@ -25,7 +25,7 @@ use strict;
use base qw(Template::Plugin);
use Bugzilla::Constants;
use Bugzilla::Install::Util qw(include_languages template_include_path);
use Bugzilla::Install::Util qw(template_include_path);
use Bugzilla::Util;
use Bugzilla::Error;
@ -66,15 +66,12 @@ sub process
# Hooks are named like this:
my $extension_template = "$path$template_name-$hook_name.$type.tmpl";
# Get the hooks out of the cache if they exist. Otherwise, read them
# from the disk.
# Get the hooks out of the cache if they exist. Otherwise, read them from the disk.
my $cache = Bugzilla->request_cache->{template_plugin_hook_cache} ||= {};
my $lang = Bugzilla->request_cache->{language} || '';
$cache->{"${lang}__$extension_template"}
||= $self->_get_hooks($extension_template);
$cache->{"${lang}__$extension_template"} ||= $self->_get_hooks($extension_template);
# process() accepts an arrayref of templates, so we just pass the whole
# arrayref.
# process() accepts an arrayref of templates, so we just pass the whole arrayref.
$context->{bz_in_hook} = 1; # See Bugzilla::Template::Context
return $context->process($cache->{"${lang}__$extension_template"});
}
@ -106,14 +103,8 @@ sub _get_hooks
sub _template_hook_include_path
{
my $cache = Bugzilla->request_cache;
my $language = $cache->{language} || '';
my $cache_key = "template_plugin_hook_include_path_$language";
$cache->{$cache_key} ||= template_include_path({
use_languages => Bugzilla->languages,
only_language => $language,
hook => 1,
});
return $cache->{$cache_key};
$cache->{template_hook_include_path} ||= template_include_path({ hook => 1 });
return $cache->{template_hook_include_path};
}
1;

View File

@ -0,0 +1,24 @@
# Make template mtime dependent on i18n messages mtime
# License: Dual-license MPL 1.1+ or GPL 3.0+
# Author(s): Vitaliy Filippov <vitalif@mail.ru>
package Bugzilla::Template::Provider;
use strict;
use base 'Template::Provider';
sub _template_modified
{
my $self = shift;
my $template = shift || return;
my $mtime = (stat $template)[9];
if ($mtime)
{
my $msgmtime = Bugzilla->i18n->template_messages_mtime($Bugzilla::Template::COMPILE_LANGUAGE);
$mtime = $msgmtime if $msgmtime > $mtime;
}
return $mtime;
}
1;
__END__

View File

@ -124,7 +124,6 @@ sub IssueEmailChangeToken {
$template->process("account/email/change-new.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($message);
}
@ -161,7 +160,6 @@ sub IssuePasswordToken {
$vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($message);
}
@ -302,7 +300,6 @@ sub Cancel {
$template->process("account/cancel-token.txt.tmpl", $vars, \$message)
|| ThrowTemplateError($template->error());
Bugzilla->template_inner("");
MessageToMTA($message);
# Delete the token from the database.

View File

@ -1,5 +1,3 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
@ -21,21 +19,20 @@
package Bugzilla::User::Setting::Lang;
use strict;
use base qw(Bugzilla::User::Setting);
use Bugzilla::Constants;
sub legal_values {
sub legal_values
{
my ($self) = @_;
return $self->{'legal_values'} if defined $self->{'legal_values'};
return $self->{legal_values} if defined $self->{legal_values};
return $self->{'legal_values'} = Bugzilla->languages;
return $self->{legal_values} = Bugzilla->i18n->supported_languages;
}
1;
__END__
=head1 NAME

View File

@ -780,6 +780,7 @@ sub clean_text
# from quoteUrls() for each comment. This leaded to TERRIBLE performance
# on "long" bugs compared to Bugzilla 2.x!
# FIXME: Rework it to just use Bugzilla::Language instead of calling templates.
sub get_text
{
my ($name, $vars) = @_;

View File

@ -1,2 +1,2 @@
#!/bin/sh
perl checksetup.pl --no-chmod --no-templates
perl checksetup.pl --no-chmod --no-templates $*

363
contrib/extract-strings.pl Normal file
View File

@ -0,0 +1,363 @@
#!/usr/bin/perl
# Extract strings for localisation from Bugzilla Template Toolkit templates
# and automatically replaces them with with L('...') calls.
# Reads blacklist from i18n/en/blacklist.pl, processes all templates. Outputs:
# 1) Translated versions of all templates to template/localized/
# 2) Extracted messages to i18n/en/messages.pl
# 3) For convenience, also writes messages that were added or removed since
# the previous run to i18n/en/added.pl and i18n/en/removed.pl.
use strict;
use Encode;
use lib qw(..);
use Cwd;
use File::Basename qw(dirname);
use File::Path qw(make_path);
use File::Find;
my $bz_root = Cwd::abs_path(dirname(__FILE__).'/..');
my $msg_path = "$bz_root/i18n/en";
-d $msg_path or make_path($msg_path);
my $blacklist;
if (-f "$msg_path/blacklist.pl")
{
$blacklist = do "$msg_path/blacklist.pl";
$blacklist or die "blacklist.pl should define \$blacklist = { 'template' => { 'string' => anything } };";
}
my $strings = {};
#$File::Find::name = '/var/home/www/localhost/bugs3/template/en/default/global/header.html.tmpl';
#process_file();
#exit;
File::Find::find(\&process_file, "$bz_root/template/en", glob("$bz_root/extensions/*/template/en"));
my ($removed, $added) = diff_strings("$msg_path/messages.pl", $strings);
write_dump("$msg_path/messages.pl", $strings);
write_dump("$msg_path/removed.pl", $removed) if $removed && %$removed;
write_dump("$msg_path/added.pl", $added) if $added && %$added;
#print_duplicated($strings);
exit;
sub process_file
{
my $n = $File::Find::name;
if ($n =~ /\.(txt|html)\.tmpl$/ && open FD, '<', $n)
{
local $/ = undef;
my $s = <FD>;
close FD;
my $rel = substr($n, 1 + length $bz_root);
$rel =~ s!^extensions/([^/]*)/template/en/(default|custom)/(.*)$!extensions/$1/$3!so;
$rel =~ s!^template/en/(default|custom)/(.*)$!$2!so;
($s, $strings->{$rel}) = translate_template($s, $n =~ /\.txt\.tmpl$/ ? 1 : 0, $blacklist->{$rel});
delete $strings->{$rel} if !%{$strings->{$rel}};
my $tr = $n;
$tr =~ s!/en/!/localized/!;
-d dirname($tr) or make_path(dirname($tr));
open FD, '>', $tr or die "Can't write $tr";
print FD $s;
close FD;
}
}
sub diff_strings
{
my ($fn, $strings) = @_;
my ($removed, $added);
-f $fn or return;
my $old_strings = do $fn or return;
for (keys %$old_strings)
{
$strings->{$_} = $old_strings->{$_} if !/\.tmpl$/so;
}
my %all_keys = map { map { $_ => 1 } keys %$_ } ($old_strings, $strings);
for my $fn (keys %all_keys)
{
$removed->{$fn}->{''} = [ grep { !exists $strings->{$fn}->{$_} } keys %{$old_strings->{$fn} || {}} ];
$added->{$fn}->{''} = [ grep { !exists $old_strings->{$fn}->{$_} } @{$strings->{$fn} ? $strings->{$fn}->{''} : []} ];
for ($removed, $added)
{
delete $_->{$fn} if !@{$_->{$fn}->{''}};
}
}
return ($removed, $added);
}
sub write_dump
{
my ($fn, $strings) = @_;
my $dump = "use utf8;\n{\n";
for my $file (sort keys %$strings)
{
$dump .= " '$file' => {\n";
for my $s (@{$strings->{$file}->{''}})
{
$s =~ s/([\\\'])/\\$1/gso;
$dump .= " '$s' =>\n '$s',\n";
}
$dump .= " },\n";
}
$dump .= "};\n";
Encode::_utf8_off($dump);
open FD, '>', $fn or die "Can't write $fn";
print FD $dump;
close FD;
}
sub print_duplicated
{
my $all = {};
for my $file (sort keys %$strings)
{
for my $s (@{$strings->{$file}->{''}})
{
push @{$all->{$s}}, $file;
}
}
for (sort keys %$all)
{
if (@{$all->{$_}} > 1)
{
print "$_\n";
for (@{$all->{$_}})
{
print " $_\n";
}
}
}
}
sub translate_template
{
my ($template, $is_txt, $blacklist) = @_;
my $translated = '';
my $strings = {};
my $last = '';
my $last_raw = '';
my $outtag; # translated tag/directive/whatever
my $pre_nl; # newlines before non-special string match (to preserve them if not translating)
while ($is_txt ? $template =~ s/^(.*?)(\[\%|<|\n\s*\n|$)//so : $template =~ s/^(.*?)(\[\%|<|$)//so)
{
$outtag = $pre_nl = '';
my ($pre, $tag) = ($1, $2);
my $pre_raw = $last_raw.$pre;
$pre = $last.$pre;
$last_raw = '';
$last = '';
$pre =~ s/\s+$//so and $outtag .= $&;
$pre =~ s/^\s+//so and $pre_nl = $&;
$pre_raw =~ s/^\s+//so;
$pre_raw =~ s/\s+$//so;
$pre =~ s/\s+/ /gso;
if ($tag eq '[%')
{
if ($template =~ s/^([\-\+]?)\s*terms.(\w+)\s*-?\%\]//so)
{
$outtag = ' ' if $outtag;
$last_raw = $pre_nl.$pre.$outtag.'[%'.$1.' terms.'.$2.' %]';
$last = $pre_nl.$pre.$outtag.($1 eq '+' ? ' ' : '').'$terms.'.$2;
next;
}
$outtag .= '[%' . tt_dir($template, $strings, $blacklist);
}
elsif ($tag eq '<')
{
if ($template =~ s/^(\/?)(u|b|i|strong|em)>//iso)
{
$outtag = ' ' if $outtag;
$last_raw = $last = $pre_nl.$pre.$outtag.'<'.$1.$2.'>';
next;
}
$outtag .= '<';
my ($script) = $template =~ /^\s*(script|style)/iso;
my ($tagname) = $template =~ /^\/?(\S+)/so;
my $is_button = 0;
while ($template =~ s/^(.*?)(\[\%|\s+|>)//so)
{
$outtag .= $1 . $2;
my $d = $2;
if ($d eq '[%')
{
$outtag .= tt_dir($template, $strings, $blacklist, 1);
}
elsif ($d eq '>')
{
last;
}
elsif ($template =~ s/^((title|alt)\s*=[\"\'])//iso ||
$tagname eq 'input' && $template =~ s/^((type)\s*=[\"\'])//iso ||
$is_button && $template =~ s/^((value)\s*=[\"\'])//iso)
{
$outtag .= $1;
if (lc($2) eq 'type')
{
$is_button = $template =~ /^(submit|button)/iso ? 1 : 0;
next;
}
my $last = '';
my $last_raw = '';
while ($template =~ s/^([^\"\']*?)(\[\%|\"|\')//so)
{
my $pre = $last.$1;
my $pre_raw = $last_raw.$1;
my $n = $2;
my $outtag2 = '';
$last = '';
$last_raw = '';
$pre =~ s/\s+/ /gso;
$pre =~ s/^\s*//so;
$pre_raw =~ s/^\s*//so;
if ($n ne '"' && $n ne "'")
{
if ($template =~ s/^-?\s*terms.(\w+)\s*-?\%\]//so)
{
$last = $pre.'$terms.'.$1;
$last_raw = $pre.'[%'.$&;
next;
}
elsif ($template =~ s/^\s*\%\]//so)
{
# empty directive
$last_raw = $last = $pre;
next;
}
$outtag2 .= '[%' . tt_dir($template, $strings, $blacklist);
}
else
{
$outtag2 .= $n;
}
if (add_str($strings, $pre, $blacklist))
{
$pre =~ s/([\'\\])/\\$1/gso;
$outtag .= ($outtag =~ /\%\]\s+$/ ? '[%+' : '[%')." L('$pre') %]";
}
else
{
$outtag .= $pre_raw;
}
$outtag .= $outtag2;
last if $n eq '"' || $n eq "'";
}
}
}
if ($script)
{
if (lc $script eq 'script')
{
# Extract messages from TT directives inside <script>..</script>
while ($template =~ s/^(.*?)(\[\%|<\/\s*script\s*>)//iso)
{
$outtag .= $1 . $2;
if ($2 eq '[%')
{
$outtag .= tt_dir($template, $strings, $blacklist);
}
else
{
last;
}
}
}
else
{
$template =~ s!.*?</$script\s*>!!is and $outtag .= $&;
}
}
}
elsif ($tag ne '')
{
$outtag = $tag;
}
$translated .= $pre_nl;
if (add_str($strings, $pre, $blacklist))
{
$pre =~ s/([\'\\])/\\$1/gso;
$translated .= ($translated =~ /\%\]\s+$/ ? '[%+' : '[%')." L('$pre') %]";
if ($translated =~ /\%\](\s*)$/ && ($1 || $outtag =~ /^\s+\[\%/))
{
# Preserve space...
$outtag =~ s/^(\s*)\[\%(?!\+|\s*\#)-?/$1\[\%\+/;
}
}
else
{
$translated .= $pre_raw;
}
$translated .= $outtag;
last if $tag eq '';
}
$translated .= "\n";
return ($translated, $strings);
}
sub tt_dir
{
my (undef, $strings, $blacklist, $ignore) = @_;
my $out = '';
while ($_[0] =~ s/^\s*#(?:(?:[^\n\%]+|\%[^\]])*)//so || $_[0] =~ s/^(?:
[^\"\'\%]+ |
\%(?!\]) |
\"((?:[^\"\\]+|\\.)*)\" |
\'((?:[^\'\\]+|\\.)*)\')//xso)
{
my $m = $&;
my $s = $1 || $2;
$1 ? $s =~ s/\\([\\\"])/$1/gso : $s =~ s/\\([\\\'])/$1/gso;
if ($s ne '')
{
while ($_[0] =~ s/^\s*_\s*(?:
\"((?:[^\"\\]+|\\.)*)\" |
\'((?:[^\'\\]+|\\.)*)\')//xso)
{
$m .= $&;
my $a = $1 || $2;
$1 ? $a =~ s/\\([\\\"])/$1/gso : $a =~ s/\\([\\\'])/$1/gso;
$s .= $a;
}
}
if (!$ignore && length $s > 2 && $s =~ /[^\x00-\x80]|[a-z].*[A-Z]|[A-Z].*[a-z]|\w.*\W|\W.*\w/so &&
add_str($strings, $s, $blacklist))
{
$s =~ s/([\'\\])/\\$1/gso;
$out .= "L('$s')";
}
else
{
if ($s)
{
#print STDERR "Skip: $s\n";
}
$out .= $m;
}
}
$_[0] =~ s/\s*\%\]//so or die $_[0];
$out .= "%]";
return $out;
}
sub add_str
{
my ($strings, $s, $blacklist) = @_;
my $chk = $s;
$chk =~ s/<[^>]*>//gso;
$chk =~ s/&([a-z]+|#x[0-9a-f]+|#\d+);|\$[a-z]+(\.[a-z_]+)?/ /giso;
return 0 if $chk !~ /[^\W\d]|[^\x00-\x80]/ ||
$chk =~ /^[a-z0-9_\-]*\.cgi(\?\S*)?$|^[a-z0-9_\-\/]*\.html(\.tmpl)?(#\S*)?$|^\/*skins.*\.css$|^\/*js.*\.js$/;
$s =~ s/\n[ \t]+/\n/gso;
return 0 if $blacklist->{$s};
$_[1] = $s;
if (!$strings->{$s})
{
$strings->{$s} = 1;
push @{$strings->{''}}, $s;
}
return 1;
}

1393
i18n/en/blacklist.pl Normal file

File diff suppressed because it is too large Load Diff

72
i18n/en/messages.js Normal file
View File

@ -0,0 +1,72 @@
// English messages for Bugzilla4Intranet JavaScripts
// License: Dual-license GPL 3.0+ or MPL 1.1+
// Author: Vitaliy Filippov <vitalif@mail.ru>
add_i18n({
'Hide \u25B4':
'Hide \u25B4',
'Show \u25BE':
'Show \u25BE',
'Undo delete':
'Undo delete',
'Delete':
'Delete',
'&lt;no groups&gt;':
'&lt;no groups&gt;',
'You cannot paste images from clipboard because Java support is not enabled in your browser. Please download Java plugin from http://java.com/':
'You cannot paste images from clipboard because Java support is not enabled in your browser. Please download Java plugin from http://java.com/',
'You must enter a Description for this attachment.':
'You must enter a Description for this attachment.',
'Collapse the comment.':
'Collapse the comment.',
'Expand the comment.':
'Expand the comment.',
'Hide full text':
'Hide full text',
'Show full text':
'Show full text',
'reply':
'reply',
'other':
'other',
'same':
'same',
'ext':
'ext',
'int':
'int',
' product':
' product',
'(table removed)':
'(table removed)',
'Please, verify working time:':
'Please, verify working time:',
'Choose a component to see its description.':
'Choose a component to see its description.',
'Your comment will be lost. Leave page?':
'Your comment will be lost. Leave page?',
'Show Obsolete':
'Show Obsolete',
'Hide Obsolete':
'Hide Obsolete',
'Save All Changes':
'Save All Changes',
'Please enter a summary sentence for this bug.':
'Please enter a summary sentence for this bug.',
'No keywords found':
'No keywords found',
'Describe new keyword':
'Describe new keyword',
'You must set the login and password before logging in.':
'You must set the login and password before logging in.',
'Type at least 3 letters':
'Type at least 3 letters',
'No users found':
'No users found',
'January February March April May June July August September October November December':
'January February March April May June July August September October November December',
'Mon Tue Wed Thu Fri Sat Sun':
'Mon Tue Wed Thu Fri Sat Sun',
'Cancel':
'Cancel'
});

13087
i18n/en/messages.pl Normal file

File diff suppressed because it is too large Load Diff

330
i18n/en/runtime.pl Normal file
View File

@ -0,0 +1,330 @@
{
install_strings => {
any => 'any',
blacklisted => '(blacklisted)',
checking_for => 'Checking for',
checking_dbd => 'Checking available perl DBD modules...',
checking_optional => 'The following Perl modules are optional:',
checking_modules => 'Checking perl modules...',
chmod_failed => '##path##: Failed to change permissions: ##error##',
chown_failed => '##path##: Failed to change ownership: ##error##',
commands_dbd => <<EOT,
YOU MUST RUN ONE OF THE FOLLOWING COMMANDS (depending on which database
you use):
EOT
commands_optional => 'COMMANDS TO INSTALL OPTIONAL MODULES:',
commands_required => <<EOT,
COMMANDS TO INSTALL REQUIRED MODULES (You *must* run all these commands
and then re-run this script):
EOT
done => 'done.',
extension_must_return_name => <<END,
##file## returned ##returned##, which is not a valid name for an extension.
Extensions must return their name, not <code>1</code> or a number. See
the documentation of Bugzilla::Extension for details.
END
feature_auth_ldap => 'LDAP Authentication',
feature_auth_radius => 'RADIUS Authentication',
feature_graphical_reports => 'Graphical Reports',
feature_inbound_email => 'Inbound Email',
feature_jobqueue => 'Mail Queueing',
feature_jsonrpc => 'JSON-RPC Interface',
feature_new_charts => 'New Charts',
feature_old_charts => 'Old Charts',
feature_mod_perl => 'mod_perl',
feature_moving => 'Move Bugs Between Installations',
feature_patch_viewer => 'Patch Viewer',
feature_rand_security => 'Improve cookie and token security',
feature_smtp_auth => 'SMTP Authentication',
feature_updates => 'Automatic Update Notifications',
feature_xmlrpc => 'XML-RPC Interface',
feature_fulltext_stem => 'Snowball stemmers in full-text search',
header => '* This is Bugzilla ##bz_ver## on perl ##perl_ver##
* Running on ##os_name## ##os_ver##',
install_all => <<EOT,
To attempt an automatic install of every required and optional module
with one command, do:
##perl## install-module.pl --all
EOT
install_data_too_long => <<EOT,
WARNING: Some of the data in the ##table##.##column## column is longer than
its new length limit of ##max_length## characters. The data that needs to be
fixed is printed below with the value of the ##id_column## column first and
then the value of the ##column## column that needs to be fixed:
EOT
install_module => 'Installing ##module## version ##version##...',
installation_failed => '*** Installation aborted. Read the messages above. ***',
max_allowed_packet => <<EOT,
WARNING: You need to set the max_allowed_packet parameter in your MySQL
configuration to at least ##needed##. Currently it is set to ##current##.
You can set this parameter in the [mysqld] section of your MySQL
configuration file.
EOT
min_version_required => 'Minimum version required: ',
modules_message_db => <<EOT,
***********************************************************************
* DATABASE ACCESS *
***********************************************************************
* In order to access your database, Bugzilla requires that the *
* correct "DBD" module be installed for the database that you are *
* running. See below for the correct command to run to install the *
* appropriate module for your database. *
EOT
modules_message_optional => <<EOT,
***********************************************************************
* OPTIONAL MODULES *
***********************************************************************
* Certain Perl modules are not required by Bugzilla, but by *
* installing the latest version you gain access to additional *
* features. *
* *
* The optional modules you do not have installed are listed below, *
* with the name of the feature they enable. Below that table are the *
* commands to install each module. *
EOT
modules_message_required => <<EOT,
***********************************************************************
* REQUIRED MODULES *
***********************************************************************
* Bugzilla requires you to install some Perl modules which are either *
* missing from your system, or the version on your system is too old. *
* See below for commands to install these modules. *
EOT
module_found => 'found v##ver##',
module_not_found => 'not found',
module_ok => 'ok',
module_unknown_version => 'found unknown version',
no_such_module => 'There is no Perl module on CPAN named ##module##.',
ppm_repo_add => <<EOT,
***********************************************************************
* Note For Windows Users *
***********************************************************************
* In order to install the modules listed below, you first have to run *
* the following command as an Administrator: *
* *
* ppm repo add theory58S ##theory_url##
EOT
ppm_repo_up => <<EOT,
* *
* Then you have to do (also as an Administrator): *
* *
* ppm repo up theory58S *
* *
* Do that last command over and over until you see "theory58S" at the *
* top of the displayed list. *
EOT
template_precompile => 'Precompiling templates...',
template_removal_failed => <<END,
WARNING: The directory '##datadir##/template' could not be removed.
It has been moved into '##datadir##/deleteme', which should be
deleted manually to conserve disk space.
END
template_removing_dir => 'Removing existing compiled templates...',
},
terms => {
bug => 'bug',
Bug => 'Bug',
abug => 'a bug',
Abug => 'A bug',
aBug => 'a Bug',
ABug => 'A Bug',
bugs => 'bugs',
Bugs => 'Bugs',
bugmail => 'bugmail',
Bugmail => 'Bugmail',
zeroSearchResults => 'Zarro Boogs found',
Bugzilla => 'Bugzilla',
},
operator_descs => {
not => 'NOT',
noop => '---',
equals => 'is equal to',
notequals => 'is not equal to',
anyexact => 'is equal to any of the strings',
substring => 'contains the string',
notsubstring => 'does not contain the string',
casesubstring => 'contains the string (exact case)',
notcasesubstring => 'does not contain the string (exact case)',
anywordssubstr => 'contains any of the strings',
allwordssubstr => 'contains all of the strings',
nowordssubstr => 'contains none of the strings',
regexp => 'matches regular expression',
notregexp => 'does not match regular expression',
lessthan => 'is less than',
lessthaneq => 'is less than or equal to',
greaterthan => 'is greater than',
greaterthaneq => 'is greater than or equal to',
anywords => 'contains any of the words',
allwords => 'contains all of the words',
nowords => 'contains none of the words',
matches => 'matches',
notmatches => 'does not match',
insearch => 'matched by saved search',
notinsearch => 'not matched by saved search',
changedbefore => 'changed before',
changedafter => 'changed after',
changedfrom => 'changed from',
changedto => 'changed to',
changedby => 'changed by',
desc_between => 'between $1 and $2',
desc_before => 'before $2',
desc_after => 'after $1',
desc_by => 'by $1',
desc_fields => 'one of $1',
},
field_types => {
UNKNOWN => 'Unknown Type',
FREETEXT => 'Free Text',
SINGLE_SELECT => 'Drop Down',
MULTI_SELECT => 'Multiple-Selection Box',
TEXTAREA => 'Large Text Box',
DATETIME => 'Date/Time',
BUG_ID => '$terms.Bug ID',
BUG_URLS => '$terms.Bug URLs',
KEYWORDS => '(Invalid type) Keywords',
NUMERIC => 'Numeric',
EXTURL => 'External URL',
BUG_ID_REV => '$terms.Bug ID reverse',
},
field_descs => {
alias => 'Alias',
assigned_to => 'Assignee',
blocked => 'Blocks',
bug_file_loc => 'URL',
bug_group => 'Group',
bug_id => '$terms.Bug ID',
bug_severity => 'Severity',
bug_status => 'Status',
cc => 'CC',
classification => 'Classification',
cclist_accessible => 'CC list accessible',
component_id => 'Component ID',
component => 'Component',
content => 'Content',
comment => 'Comment',
changes => 'Changed',
'[Bug creation]' => 'Creation date',
opendate => 'Creation date',
creation_ts => 'Creation date',
deadline => 'Deadline',
changeddate => 'Changed',
delta_ts => 'Changed',
dependson => 'Depends on',
dup_id => 'Duplicate of',
estimated_time => 'Orig. Est.',
everconfirmed => 'Ever confirmed',
keywords => 'Keywords',
newcc => 'CC',
op_sys => 'OS',
owner_idle_time => 'Time Since Assignee Touched',
days_elapsed => 'Days since bug changed',
percentage_complete => '% Complete',
priority => 'Priority',
product_id => 'Product ID',
product => 'Product',
qa_contact => 'QA Contact',
remaining_time => 'Hours Left',
rep_platform => 'Hardware',
reporter => 'Reporter',
reporter_accessible => 'Reporter accessible',
resolution => 'Resolution',
see_also => 'See Also',
setting => 'Setting',
settings => 'Settings',
short_desc => 'Summary',
status_whiteboard => 'Whiteboard',
target_milestone => 'Target Milestone',
version => 'Version',
votes => 'Votes',
comment0 => 'First Comment',
interval_time => 'Period Worktime',
work_time => 'Hours Worked',
actual_time => 'Hours Worked',
longdesc => 'Comment',
commenter => 'Commenter',
'longdescs.isprivate' => 'Comment is private',
'attachments.description' => 'Attachment description',
'attachments.filename' => 'Attachment filename',
'attachments.mimetype' => 'Attachment mime type',
'attachments.ispatch' => 'Attachment is patch',
'attachments.isobsolete' => 'Attachment is obsolete',
'attachments.isprivate' => 'Attachment is private',
'attachments.submitter' => 'Attachment creator',
'flagtypes.name' => 'Flags and Requests',
'requestees.login_name' => 'Flag Requestee',
'setters.login_name' => 'Flag Setter',
},
setting_descs => {
comment_sort_order => 'When viewing $terms.abug, show comments in this order',
csv_colsepchar => 'Field separator character for CSV files',
display_quips => 'Show a quip at the top of each $terms.bug list',
zoom_textareas => 'Zoom textareas large when in use (requires JavaScript)',
newest_to_oldest => 'Newest to Oldest',
newest_to_oldest_desc_first => 'Newest to Oldest, but keep Description at the top',
off => 'Off',
oldest_to_newest => 'Oldest to Newest',
on => 'On',
post_bug_submit_action => 'After changing $terms.abug',
next_bug => 'Show next $terms.bug in my list',
same_bug => 'Show the updated $terms.bug',
standard => 'Classic',
skin => '$terms.Bugzilla\'s general appearance (skin)',
nothing => 'Do Nothing',
state_addselfcc => 'Automatically add me to the CC list of $terms.bugs I change',
always => 'Always',
never => 'Never',
cc_unless_role => 'Only if I have no role on them',
lang => 'Default language for UI and emails',
quote_replies => 'Quote the associated comment when you click on its reply link',
quoted_reply => 'Quote the full comment',
simple_reply => 'Reference the comment number only',
timezone => 'Timezone used to display dates and times',
local => 'Same as the server',
remind_me_about_worktime => 'Remind me to track worktime for each comment',
remind_me_about_worktime_newbug => 'Remind me to track worktime for new bugs',
remind_me_about_flags => 'Remind me about flag requests',
saved_searches_position => 'Position of Saved Searches bar',
footer => 'Page footer',
header => 'Page header',
both => 'Both',
csv_charset => 'Character set for CSV import and export',
silent_affects_flags => 'Do not send flag email under Silent mode',
send => 'Send',
do_not_send => 'Don\'t send',
showhide_comments => 'Which comments can be marked as collapsed',
none => 'None',
worktime => 'With worktime only',
all => 'All',
comment_width => 'Show comments in the full screen width',
preview_long_comments => 'Fold long comments',
clear_requests_on_close => 'Clear flag requests when closing bugs',
show_gravatars => 'Show avatar images (Gravatars)',
},
system_groups => {
admin => 'Administrator group. Usually allows to access <b>all</b> administrative functions.',
admin_index => 'Allows to <a href="admin.cgi">enter Administration area</a>, granted automatically if you can access any of the administrative functions.',
tweakparams => 'Allows to <a href="editparams.cgi">change Parameters</a>.',
editusers => 'Allows to <a href="editusers.cgi">edit or disable users</a> and include/exclude them from <b>all</b> groups.',
creategroups => 'Allows to <a href="editgroups.cgi">create, destroy and edit groups</a>.',
editclassifications => 'Allows to <a href="editclassifications.cgi">create, destroy and edit classifications</a>.',
editcomponents => 'Allows to <a href="editproducts.cgi">create, destroy and edit all products, components, versions and milestones</a>.',
editkeywords => 'Allows to <a href="editvalues.cgi?field=keywords">create, destroy and edit keywords</a>.',
editbugs => 'Allows to edit all fields of all bugs.',
canconfirm => 'Allows to confirm bugs or mark them as duplicates.',
bz_canusewhineatothers => 'Allows to <a href="editwhines.cgi">configure whine reports for other users</a>.',
bz_canusewhines => 'Allows to <a href="editwhines.cgi">configure whine reports for self</a>.',
bz_sudoers => 'Allows to <a href="relogin.cgi?action=prepare-sudo">impersonate other users</a> and perform actions as them.',
bz_sudo_protect => 'Forbids other users to impersonate you.',
bz_editcheckers => 'Allows to <a href="editcheckers.cgi">edit Predicates</a> ("Correctness Checkers").',
editfields => 'Allows to <a href="editfields.cgi">create, destroy and edit properties of bug fields</a>.',
editvalues => 'Allows to <a href="editvalues.cgi">edit allowed values for all bug fields</a>.',
importxls => 'Allows to <a href="importxls.cgi">create or update many bugs at once using Excel and CSV files</a>.',
worktimeadmin => 'Allows to register working time for other users and for dates in the past (see "Fix Worktime" link under a bug list).',
editflagtypes => 'Allows to <a href="editflagtypes.cgi">create, destroy and edit flag types</a>.',
},
};

View File

@ -11,12 +11,12 @@ function helpToggle(btn_id, div_id)
var d = document.getElementById(div_id);
if (d.style.display == 'none')
{
b.value = 'Hide \u25B4';
b.value = L('Hide \u25B4');
d.style.display = '';
}
else
{
b.value = 'Show \u25BE';
b.value = L('Show \u25BE');
d.style.display = 'none';
}
}
@ -120,7 +120,7 @@ function deleteGroup(el_link, grp_id)
el_othercontrol.setAttribute('disabled', true);
clearSelectedOption(el_membercontrol);
clearSelectedOption(el_othercontrol);
el_link.innerHTML = 'Undo delete';
el_link.innerHTML = L('Undo delete');
}
else
{
@ -131,7 +131,7 @@ function deleteGroup(el_link, grp_id)
el_othercontrol.removeAttribute('disabled');
clearSelectedOption(el_membercontrol);
clearSelectedOption(el_othercontrol);
el_link.innerHTML = 'Delete';
el_link.innerHTML = L('Delete');
}
highlightButton();
}
@ -154,7 +154,7 @@ function addNewGroup()
var cell_empty = row.insertCell(3);
var cell_action = row.insertCell(4);
cell_action.innerHTML = '<a href="#" class="icon-delete" onclick="deleteGroup(this, ' + count_rows + '); return false;">Delete</a>';
cell_action.innerHTML = '<a href="#" class="icon-delete" onclick="deleteGroup(this, ' + count_rows + '); return false;">'+L('Delete')+'</a>';
var etalon_group = document.getElementById('etalon_groups');
var new_group = document.createElement('select');
new_group.id = 'group_' + count_rows;
@ -191,7 +191,7 @@ function deleteGroupCheckbox(el_id)
added_li = document.createElement('li');
added_li.id = 'li_' + params_arr[0] + '_empty';
added_li.className = 'group_empty';
added_li.innerHTML = '&lt;no groups&gt;';
added_li.innerHTML = L('&lt;no groups&gt;');
exsist_list.appendChild(added_li);
}
highlightButton();

View File

@ -21,9 +21,9 @@
* Marc Schumann <wurblzap@gmail.com>
*/
var SUPA_JAVA_DISABLED_ERROR = 'You cannot paste images from clipboard because Java support'+
var SUPA_JAVA_DISABLED_ERROR = L('You cannot paste images from clipboard because Java support'+
' is not enabled in your browser. Please download Java plugin'+
' from http://java.com/';
' from http://java.com/');
function htmlspecialchars_decode(str)
{
@ -121,7 +121,7 @@ function validateAttachmentForm(theform)
var desc = theform.description.value.replace(/^\s+|\s+$/, '');
if (desc == '')
{
alert(BUGZILLA.string.attach_desc_required);
alert(L('You must enter a Description for this attachment.'));
return false;
}
if (!encodeSupaContent())

View File

@ -35,7 +35,7 @@ function showhide_comment(comment_id, show)
var link = document.getElementById('comment_link_' + comment_id);
var comment = document.getElementById('comment_text_' + comment_id);
link.innerHTML = show ? "[-]" : "[+]";
link.title = (show ? "Collapse" : "Expand")+" the comment.";
link.title = L(show ? "Collapse the comment." : "Expand the comment.");
if (show)
removeClass(comment, 'collapsed');
else
@ -60,7 +60,7 @@ function showhide_comment_preview(comment_id)
body.style.display = 'block';
addClass(link, "shown");
}
link.innerHTML = (!show ? "Hide" : "Show") + " full text";
link.innerHTML = L(!show ? "Hide full text" : "Show full text");
return false;
}
@ -86,7 +86,7 @@ function addCollapseLink(id)
' <a href="#" class="bz_collapse_comment"'+
' id="comment_link_' + id +
'" onclick="toggle_comment_display(' + id +
'); return false;" title="'+(c ? 'Collapse' : 'Expand')+' the comment.">['+
'); return false;" title="'+L(c ? 'Collapse the comment.' : 'Expand the comment.')+'">['+
(c ? '-' : '+')+']<\/a> ';
}
@ -100,16 +100,16 @@ function addReplyLink(num, id)
if (user_settings.quote_replies != 'off')
{
s += '<a href="#add_comment" onclick="replyToComment(' +
num + ', ' + id + '); return false;">reply<' + '/a>';
num + ', ' + id + '); return false;">'+L('reply')+'<' + '/a>';
}
s += ', clone to <a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;cloned_comment='+num+'">other</a>';
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;product='+encodeURIComponent(bug_info.product)+'&amp;cloned_comment='+num+'">same</a>';
s += ', clone to <a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;cloned_comment='+num+'">'+L('other')+'</a>';
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;product='+encodeURIComponent(bug_info.product)+'&amp;cloned_comment='+num+'">'+L('same')+'</a>';
// 4Intranet Bug 69514 - Clone to external product button
if (bug_info.extprod)
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;product='+encodeURIComponent(bug_info.extprod)+'&amp;cloned_comment='+num+'">ext</a>';
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;product='+encodeURIComponent(bug_info.extprod)+'&amp;cloned_comment='+num+'">'+L('ext')+'</a>';
else if (bug_info.intprod)
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;product='+encodeURIComponent(bug_info.intprod)+'&amp;cloned_comment='+num+'">int</a>';
s += ' product]';
s += '/<a href="enter_bug.cgi?cloned_bug_id='+bug_info.id+'&amp;product='+encodeURIComponent(bug_info.intprod)+'&amp;cloned_comment='+num+'">'+L('int')+'</a>';
s += L(' product')+']';
e.innerHTML += s;
}
@ -172,7 +172,7 @@ function getText(element)
/* Adds the reply text to the `comment' textarea */
function replyToComment(num, id)
{
var prefix = "(In reply to comment #" + num + ")\n";
var prefix = "(In reply to comment #"+num+")\n";
var replytext = "";
if (user_settings.quote_replies == 'quoted_reply')
{
@ -204,7 +204,7 @@ function replyToComment(num, id)
replytext += "\n";
}
else if (!prev_ist)
replytext += "> (table removed)\n";
replytext += "> "+L("(table removed)")+"\n";
prev_ist = ist;
}
@ -273,7 +273,7 @@ function changeform_onsubmit()
(wt === null || wt === undefined || wt != wt ||
notimetracking && wt != 0 || !notimetracking && wt == 0))
{
awt = prompt("Please, verify working time:", !wt || wt != wt ? "0" : wt);
awt = prompt(L("Please, verify working time:"), !wt || wt != wt ? "0" : wt);
if (awt === null || awt === undefined || (""+awt).length <= 0)
{
wtInput.focus();
@ -389,7 +389,7 @@ addListener(window, 'beforeunload', function(e)
if (window.checkCommentOnUnload && ta && ta.value.trim() != '')
{
e = e || window.event;
return (e.returnValue = 'Your comment will be lost. Leave page?');
return (e.returnValue = L('Your comment will be lost. Leave page?'));
}
});
@ -455,7 +455,7 @@ function showEditComment(comment_id)
but_wrapper.className = 'edit_comment_submit';
var submit_but = document.createElement('input');
submit_but.type = 'submit';
submit_but.value = 'Save All Changes';
submit_but.value = L('Save All Changes');
but_wrapper.appendChild(submit_but);
parent.appendChild(but_wrapper);
showhide_comment(key, false);
@ -478,7 +478,7 @@ function toggle_obsolete_attachments(link)
for (var i = 0; i < rs.length; i++)
if (hasClass(rs[i], 'bz_tr_obsolete'))
r0 = toggleClass(rs[i], 'bz_default_hidden');
link.innerHTML = r0 ? 'Show Obsolete' : 'Hide Obsolete';
link.innerHTML = r0 ? L('Show Obsolete') : L('Hide Obsolete');
var newHeight = table.offsetHeight;
// This scrolling makes the window appear to not move at all.

View File

@ -11,10 +11,11 @@
* </script>
*/
var Calendar = {
month_names: ["January","February","March","April","May","June","July","August","September","October","November","December"],
weekdays: ["Mon","Tue","Wed","Thu","Fri","Sat","Sun"],
month_names: L("January February March April May June July August September October November December").split(' '),
weekdays: L("Mon Tue Wed Thu Fri Sat Sun").split(' '),
sunday: 6,
month_days: [31,28,31,30,31,30,31,31,30,31,30,31],
cancel_text: L('Cancel'),
//Get today's date - year, month, day and date
today : new Date(),
opt : {},
@ -144,7 +145,7 @@ var Calendar = {
this.wrt("</tr>");
}
this.wrt("</table>");
this.wrt("<input type='button' value='Cancel' class='calendar-cancel' onclick='Calendar.hideCalendar();' />");
this.wrt("<input type='button' value='"+this.cancel_text+"' class='calendar-cancel' onclick='Calendar.hideCalendar();' />");
document.getElementById(this.opt['calendar']).innerHTML = this.data.join("");
this.data = [];

View File

@ -90,7 +90,7 @@ function component_change()
}
else
{
document.getElementById('comp_desc').innerHTML = 'Choose a component to see its description.';
document.getElementById('comp_desc').innerHTML = L('Choose a component to see its description.');
document.getElementById('comp_desc').style.color = 'red';
}
}
@ -141,7 +141,7 @@ function validateEntryForm(theform)
if (theform.short_desc.value == '')
{
alert('Please enter a summary sentence for this bug.');
alert(L('Please enter a summary sentence for this bug.'));
return false;
}
@ -165,7 +165,7 @@ function validateEntryForm(theform)
wt = 0;
if (wantsReminder && (wt === null || noTimeTracking == (wt != 0)))
{
wt = prompt("Please, verify working time:", "0");
wt = prompt(L("Please, verify working time:"), "0");
if (wt == null || wt == undefined || (""+wt).length <= 0)
{
theform.work_time.focus();

View File

@ -382,7 +382,7 @@ function keywordAutocomplete(hint)
function addKeywordsAutocomplete()
{
new SimpleAutocomplete("keywords", keywordAutocomplete,
{ emptyText: 'No keywords found', multipleDelimiter: "," });
{ emptyText: L('No keywords found'), multipleDelimiter: "," });
}
// CustIS bug 66910 - check new keywords and requery description for it
@ -421,7 +421,7 @@ function check_new_keywords(form)
{
this_value = document.getElementById('kd_' + i).value;
}
desc_html += "<div style='margin-top: 8px'><label>Describe new keyword <b>" + htmlspecialchars(non_exist_keywords[i]) +
desc_html += "<div style='margin-top: 8px'><label>"+L("Describe new keyword")+" <b>" + htmlspecialchars(non_exist_keywords[i]) +
"</b>:</label><br /><input type=\"text\" value=\"" + htmlspecialchars(this_value) + "\" class=\"text_input\" name=\"kd\" id=\"kd_" +
i + "\" data-key=\"" + htmlspecialchars(non_exist_keywords[i]) + "\" style=\"border: solid 1px red;\" /></div>";
}

View File

@ -104,7 +104,7 @@ function check_mini_login_fields(suffix)
if (mini_login.value != "" && mini_password.value != "" &&
mini_login.value != mini_login_constants.login)
return true;
window.alert(mini_login_constants.warning);
window.alert(L("You must set the login and password before logging in."));
return false;
}
@ -112,7 +112,7 @@ function set_language(value)
{
setCookie('LANG', value, {
expires: new Date('January 1, 2038'),
path: BUGZILLA.param.cookie_path
path: BUGZILLA.param.cookiepath
});
window.location.reload();
}
@ -166,9 +166,9 @@ function userAutocomplete(hint, emptyOptions, loadAllOnEmpty)
var data = convertUserList(r.users);
// FIXME "3" constant, messages: remove hardcode, also in Bugzilla::User::match()
if (data.length == 0 && hint.input.value.length < 3)
hint.emptyText = 'Type at least 3 letters';
hint.emptyText = L('Type at least 3 letters');
else
hint.emptyText = 'No users found';
hint.emptyText = L('No users found');
hint.replaceItems(data);
}
});

View File

@ -4,6 +4,28 @@
* Contributor(s): Vitaliy Filippov <vitalif@mail.ru>
*/
// Add i18n messages
function add_i18n(messages)
{
if (!BUGZILLA.i18n)
{
BUGZILLA.i18n = messages;
}
else
{
for (var k in messages)
{
BUGZILLA.i18n[k] = messages[k];
}
}
}
// Get i18n string
function L(str)
{
return BUGZILLA.i18n && BUGZILLA.i18n[str] || str;
}
// Get the position of 'obj' from the page top
// Returns [x, y], in pixels
function findPos(obj)

View File

@ -183,7 +183,6 @@ $vars->{report_columns} = [
];
# Boolean charts
my $opdescs = Bugzilla->messages->{operator_descs};
$vars->{chart_types} = Bugzilla::Search->CHART_OPERATORS_ORDER;
$vars->{text_types} = Bugzilla::Search->TEXT_OPERATORS_ORDER;

View File

@ -86,7 +86,6 @@
<!--
mini_login_constants = {
"login" : "login",
"warning" : "You must set the login and password before logging in."
};
onDomReady(function() {
init_mini_login_form('_[% qs_suffix %]');

View File

@ -53,7 +53,7 @@
</td>
</tr>
[% IF Param('rememberlogin') == 'defaulton' ||
[% IF Param('rememberlogin') == 'defaulton' ||
Param('rememberlogin') == 'defaultoff' %]
<tr>
<th>&nbsp;</th>

View File

@ -20,7 +20,7 @@
From: [% Param('mailfrom') %]
To: [% emailaddress %]
Subject: [% terms.Bugzilla %] Change Email Address Request
Subject: [% "$terms.Bugzilla Change Email Address Request" %]
X-Bugzilla-Type: admin
[%+ terms.Bugzilla %] has received a request to change the email address

View File

@ -25,7 +25,7 @@
From: [% Param('mailfrom') %]
To: [% emailaddress %]
Subject: [% terms.Bugzilla %] Change Email Address Request
Subject: [% "$terms.Bugzilla Change Email Address Request" %]
Importance: High
X-MSMail-Priority: High
X-Priority: 1

View File

@ -21,7 +21,7 @@
From: [% Param('mailfrom') %]
To: [% email %]
Subject: [% terms.Bugzilla %]: confirm account creation
Subject: [% "$terms.Bugzilla: confirm account creation" %]
X-Bugzilla-Type: admin
[%+ terms.Bugzilla %] has received a request to create a user account

View File

@ -20,7 +20,7 @@
From: [% Param('mailfrom') %]
To: [% emailaddress %]
Subject: [% terms.Bugzilla %] Change Password Request
Subject: [% "$terms.Bugzilla Change Password Request" %]
X-Bugzilla-Type: admin
You have (or someone impersonating you has) requested to change your

View File

@ -58,8 +58,8 @@ function SetCheckboxes(setting) {
}
}
document.write('<input type="button" value="Enable All Mail" onclick="SetCheckboxes(true); return false;" />\n');
document.write('<input type="button" value="Disable All Mail" onclick="SetCheckboxes(false); return false;" />\n');
document.write('<input type="button" value="[% L('Enable All Mail') %]" onclick="SetCheckboxes(true); return false;" />\n');
document.write('<input type="button" value="[% L('Disable All Mail') %]" onclick="SetCheckboxes(false); return false;" />\n');
// -->
</script>
@ -304,10 +304,10 @@ preferences for <u>their</u> relationship to the [% terms.bug %]
[%-# FIXME: remove hardcoded i18n message, also from js/field.js::userAutocomplete() %]
new SimpleAutocomplete("new_watchedusers",
function(h) { userAutocomplete(h, null); },
{ multipleDelimiter: ',', emptyText: 'No users found' });
{ multipleDelimiter: ',', emptyText: '[% L('No users found') %]' });
new SimpleAutocomplete("new_watchers",
function(h) { userAutocomplete(h, null); },
{ multipleDelimiter: ',', emptyText: 'No users found' });
{ multipleDelimiter: ',', emptyText: '[% L('No users found') %]' });
//-->
</script>

View File

@ -42,7 +42,7 @@
</tr>
<tr>
<td valign="top">Type:</td>
<td valign="top">[% field_types.${field.type} | html %]</td>
<td valign="top">[% lc_messages.field_types.${constants.FIELD_TYPE_NAMES.${field.type}} | html %]</td>
</tr>
</table>

View File

@ -25,7 +25,15 @@
[% END %]
[% END %]
[% javascript = BLOCK %]
[% PROCESS
global/header.html.tmpl
title = title
doc_section = "custom-fields.html#" _ (field.id ? "edit" : "add") _ "-custom-fields"
javascript_urls = [ "js/cf-edit.js" ]
%]
[%# FIXME Move to "js resource data" %]
<script language="JavaScript">
var constants = {
FIELD_TYPE_SINGLE_SELECT: [% constants.FIELD_TYPE_SINGLE_SELECT %],
FIELD_TYPE_MULTI_SELECT: [% constants.FIELD_TYPE_MULTI_SELECT %],
@ -33,14 +41,7 @@ var constants = {
FIELD_TYPE_EXTURL: [% constants.FIELD_TYPE_EXTURL %],
FIELD_TYPE_BUG_ID_REV: [% constants.FIELD_TYPE_BUG_ID_REV %]
};
[% END %]
[% PROCESS
global/header.html.tmpl
title = title
doc_section = "custom-fields.html#" _ (field.id ? "edit" : "add") _ "-custom-fields"
javascript_urls = [ "js/cf-edit.js" ]
%]
</script>
[% IF !field.id %]
<p style="font-size: 120%">
@ -123,15 +124,17 @@ var constants = {
<th align="left">Type:</th>
<td valign="top">
[% IF field.id %]
[% field_types.${field.type} | html %]
[% lc_messages.field_types.${constants.FIELD_TYPE_NAMES.${field.type}} | html %]
[% ELSE %]
<select id="type" name="type" onchange="onChangeType()">
[% FOREACH type = field_types.sort %]
[% FOREACH type = constants.FIELD_TYPE_NAMES.sort %]
[%# Types "Bug URLs" and "Keywords" are rudiments from original Bugzilla %]
[% NEXT IF type == constants.FIELD_TYPE_UNKNOWN ||
type == constants.FIELD_TYPE_BUG_URLS ||
type == constants.FIELD_TYPE_KEYWORDS %]
<option value="[% type | html %]">[% field_types.$type | html %]</option>
<option value="[% type | html %]">
[% lc_messages.field_types.${constants.FIELD_TYPE_NAMES.$type} | html %]
</option>
[% END %]
</select>
[% END %]
@ -172,7 +175,7 @@ var constants = {
[% IF !field.id || field.can_tweak('mailhead') %]
<tr>
<th align="left">
<label for="mailhead">Displayed in [% terms.bug %]mail for new [% terms.bugs %]:</label>
<label for="mailhead">Displayed in [% terms.bugmail %] for new [% terms.bugs %]:</label>
</th>
<td><input type="checkbox" id="mailhead" name="mailhead" value="1" [%- ' checked="checked"' IF field.in_new_bugmail %] /></td>
</tr>

View File

@ -44,8 +44,8 @@
},
{
name => "mailhead"
heading => "${terms.Bug}mail",
hint => "Included in ${terms.bug}mail for new $terms.bugs"
heading => terms.Bugmail,
hint => "Included in $terms.bugmail for new $terms.bugs"
content_values => { '0' => '', '1' => 'Yes' }
align => 'center'
},
@ -76,10 +76,10 @@
[%# We want to display the type name of fields, not their type ID. %]
[% overrides.type = {} %]
[% FOREACH field_type = field_types.keys %]
[% FOREACH field_type = constants.FIELD_TYPE_NAMES.keys %]
[% overrides.type.type.$field_type = {
override_content => 1
content => field_types.$field_type
content => lc_messages.field_types.${constants.FIELD_TYPE_NAMES.$field_type}
}
%]
[% END %]

View File

@ -194,7 +194,7 @@ var except_field_index = 0;
function showhide_allowdeny()
{
var chk = document.getElementById('deny_all').checked;
document.getElementById('except_fields_title').innerHTML = chk ? 'Но разрешать:' : '';
document.getElementById('except_fields_title').innerHTML = chk ? '[% L('Но разрешать:') %]' : '';
document.getElementById('except_fields_tr').style.backgroundColor = chk ? '#E0FFE0' : '#FFE0E0';
}
function add_field(fld, val)
@ -225,7 +225,7 @@ function check_trigger()
cc.value = fl.value = '';
else if (!cc.value && !fl.value)
{
alert('Задайте действие триггера!');
alert('[% L('Задайте действие триггера!') %]');
return false;
}
return true;

View File

@ -19,8 +19,7 @@
#%]
[% title = BLOCK %]
Delete Value '[% value.name | html %]' from the
'[% field.description | html %]' ([% field.name | html %]) field
[% L('Delete Value "$1" from the field "$2" ($3)', html(value.name), html(field.description), html(field.name)) %]
[% END %]
[% PROCESS global/header.html.tmpl

View File

@ -3,7 +3,7 @@
# Author(s): Vitaliy Filippov <vitalif@mail.ru>, Stas Fomin <stas-fomin@yandex.ru> %]
[% PROCESS global/header.html.tmpl
title = "Add/remove users in group: $group.name"
title = "Add/remove users in group: " _ group.name
%]
<form method="POST" action="?">

View File

@ -53,8 +53,8 @@
_ "<p>For added security, you can insert <tt>%bugid%</tt> into the URL,"
_ " which will be replaced with the ID of the current $terms.bug that"
_ " the attachment is on, when you access an attachment. This will limit"
_ " attachments to accessing only other attachments on the same"
_ " ${terms.bug}. Remember, though, that all those possible domain names "
_ " attachments to accessing only other attachments on the same $terms.bug"
_ ". Remember, though, that all those possible domain names "
_ " (such as <tt>1234.your.domain.com</tt>) must point to this same"
_ " $terms.Bugzilla instance.",
@ -77,7 +77,7 @@
"The maximum size (in kilobytes) of attachments <b>stored in the database</b>. " _
"$terms.Bugzilla will not accept attachments greater than this number " _
"of kilobytes in size. Setting this parameter to 0 will prevent " _
"attaching files to ${terms.bugs}.",
"attaching files to $terms.bugs" _ ".",
force_attach_bigfile =>
"If this option is on, all attachments will be stored as local files, not inside the database.",

View File

@ -71,7 +71,7 @@
<dl>
<dt>DB</dt>
<dd>
${terms.Bugzilla}'s built-in authentication. This is the most common
$terms.Bugzilla's built-in authentication. This is the most common
choice.
</dd>
<dt>RADIUS</dt>

View File

@ -49,19 +49,19 @@
letsubmitterchoosepriority =>
"If this is on, then people submitting $terms.bugs can " _
"choose an initial priority for that ${terms.bug}. " _
"choose an initial priority for that $terms.bug" _ ". " _
"If off, then all $terms.bugs initially have the default " _
"priority selected below."
letsubmitterchoosemilestone =>
"If this is on, then people submitting $terms.bugs can " _
"choose the Target Milestone for that ${terms.bug}. " _
"choose the Target Milestone for that $terms.bug" _ ". " _
"If off, then all $terms.bugs initially have the default " _
"milestone for the product being filed in."
musthavemilestoneonaccept =>
"If you are using Target Milestone, do you want to require that " _
"the milestone be set in order for a user to ACCEPT a ${terms.bug}?"
"the milestone be set in order for a user to ACCEPT a $terms.bug?"
commentonchange_resolution =>
"If this option is on, the user needs to enter a short " _

View File

@ -35,14 +35,14 @@
"move-to-url" => "The URL of the database we allow some of our $terms.bugs to be moved to.",
"move-to-address" => "To move ${terms.bugs}, an email is sent to the target database. This is " _
"the email address that database uses to listen for incoming ${terms.bugs}.",
"move-to-address" => "To move $terms.bugs, an email is sent to the target database. This is " _
"the email address that database uses to listen for incoming $terms.bugs" _ ".",
"moved-from-address" => "To move ${terms.bugs}, an email is sent to the target database. This is " _
"moved-from-address" => "To move $terms.bugs, an email is sent to the target database. This is " _
"the email address from which this mail, and error messages are sent.",
movers => "A list of people with permission to move $terms.bugs and reopen moved " _
"${terms.bugs} (in case the move operation fails).",
"$terms.bugs (in case the move operation fails).",
"moved-default-product" => "$terms.Bugs moved from other databases to here are assigned " _
"to this product.",

View File

@ -25,7 +25,7 @@
%]
[% param_descs = {
webdotbase => "It is possible to show graphs of dependent ${terms.bugs}. You may set
webdotbase => "It is possible to show graphs of dependent $terms.bugs" _ ". You may set
this parameter to any of the following:
<ul>
<li>

View File

@ -28,7 +28,7 @@
[% param_descs = {
maintainer =>
"The email address of the person who maintains this installation "
_ " of ${terms.Bugzilla}. The address need not be that of a valid Bugzilla account.",
_ " of $terms.Bugzilla" _ ". The address need not be that of a valid Bugzilla account.",
error_log =>
"Path to Bugzilla error log file or empty string if you don't want to write an error log. " _
@ -52,7 +52,7 @@
_ " documentation is available in that language).",
utf8 =>
"Use UTF-8 (Unicode) encoding for all text in ${terms.Bugzilla}. New"
"Use UTF-8 (Unicode) encoding for all text in $terms.Bugzilla" _ ". New"
_ " installations should set this to true to avoid character encoding"
_ " problems. <strong>Existing databases should set this to true"
_ " only after the data has been converted from existing legacy"

View File

@ -43,7 +43,7 @@
<hr />
[%# Strictly speaking, we should not have to check for a
classification if they are enabled, but I'm just being paranoid %]
# classification if they are enabled, but I'm just being paranoid %]
[% UNLESS no_add_product_link || !user.in_group("editcomponents") %]
[% IF Bugzilla.get_field('classification').enabled && classification %]
<p>

View File

@ -22,6 +22,13 @@
constants.CONTROLMAPMANDATORY,
] %]
[% control_names = {};
control_names.${constants.CONTROLMAPNA} = 'NA';
control_names.${constants.CONTROLMAPSHOWN} = 'Shown';
control_names.${constants.CONTROLMAPDEFAULT} = 'Default';
control_names.${constants.CONTROLMAPMANDATORY} = 'Mandatory';
%]
[% all_groups = product.group_controls_full_data.values.sort("name") %]
[% groups = product.group_controls.values.sort("name") %]
@ -36,7 +43,7 @@
[% BLOCK control_select %]
<select name="[% id %]" id="[% id %]" data-lastvalue="0">
[% FOR i = control_options %]
<option value="[% i %]" [% " selected=\"selected\"" IF control == i %]>[% lc_messages.control_options.$i %]</option>
<option value="[% i %]" [% " selected=\"selected\"" IF control == i %]>[% control_names.$i %]</option>
[% END %]
</select>
[% END %]

View File

@ -32,7 +32,7 @@
[% END %]
[% PROCESS global/header.html.tmpl
title = "Select product $classification_title"
title = "Select product: " _ classification_title
%]
[% edit_contentlink = BLOCK %]

View File

@ -70,11 +70,11 @@
</tr>
[% END %]
<tr>
<th><label for="disable_mail">[% terms.Bug %]mail Disabled:</label></th>
<th><label for="disable_mail">[% terms.Bugmail %] Disabled:</label></th>
<td>
<input type="checkbox" name="disable_mail" id="disable_mail" value="1"
[% IF otheruser.email_disabled %] checked="checked" [% END %] />
(This affects [% terms.bug %]mail and whinemail, not password-reset or other
(This affects [% terms.bug %], flag and whine emails, not password-reset or other
non-[% terms.bug %]-related emails)
</td>
</tr>

View File

@ -34,7 +34,7 @@
</font>
<p style="margin-bottom: 0">
You can either <a href="[% urlbase FILTER html %]attachment.cgi?bugid=[% bugid FILTER url_quote %]&action=enter">
create a new attachment</a> or [% "go back to $terms.bug $bugid" FILTER bug_link(bugid) FILTER none %].
create a new attachment</a> or [% "go back to $terms.bug " _ bugid FILTER bug_link(bugid) FILTER none %].
</p>
</td>
</tr>

View File

@ -222,13 +222,13 @@
var patchviewerinstalled = 1;
document.write('<iframe id="viewDiffFrame" style="height: 400px; width: 100%; display: none;"><\/iframe>');
[% END %]
document.write('<button type="button" id="editButton" onclick="editAsComment(patchviewerinstalled);">Edit Attachment As Comment<\/button>');
document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment(patchviewerinstalled);" style="display: none;">Undo Edit As Comment<\/button>');
document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment(patchviewerinstalled);" style="display: none;">Redo Edit As Comment<\/button>');
document.write('<button type="button" id="editButton" onclick="editAsComment(patchviewerinstalled);">[% L('Edit Attachment As Comment') %]<\/button>');
document.write('<button type="button" id="undoEditButton" onclick="undoEditAsComment(patchviewerinstalled);" style="display: none;">[% L('Undo Edit As Comment') %]<\/button>');
document.write('<button type="button" id="redoEditButton" onclick="redoEditAsComment(patchviewerinstalled);" style="display: none;">[% L('Redo Edit As Comment') %]<\/button>');
[% IF use_patchviewer %]
document.write('<button type="button" id="viewDiffButton" onclick="viewDiff(attachment_id, patchviewerinstalled);">View Attachment As Diff<\/button>');
document.write('<button type="button" id="viewDiffButton" onclick="viewDiff(attachment_id, patchviewerinstalled);">[% L('View Attachment As Diff') %]<\/button>');
[% END %]
document.write('<button type="button" id="viewRawButton" onclick="viewRaw(patchviewerinstalled);" style="display: none;">View Attachment As Raw<\/button>');
document.write('<button type="button" id="viewRawButton" onclick="viewRaw(patchviewerinstalled);" style="display: none;">[% L('View Attachment As Raw') %]<\/button>');
}
//-->
</script>

View File

@ -121,7 +121,7 @@
</span>
[% END %]
<a href="attachment.cgi?bugid=[% bugid %]&amp;action=enter" onclick="return to_attachment_page(this);">Add an attachment</a>
<span id="att_multi_link">| <a href="javascript:void(0)" onclick="document.getElementById('att_multi_link').style.display='none';iframeajax('page.cgi?id=attach-multiple.html', {'bug_id' : [% bugid %]})">Add multiple</a></span>
<span id="att_multi_link">| <a href="javascript:void(0)" onclick="document.getElementById('att_multi_link').style.display='none';iframeajax('page.cgi?id=attach-multiple.html', {'bug_id' : [% bugid %]})">Add multiple</a> </span>
(proposed patch, testcase, etc.)
</td>
</tr>

View File

@ -28,19 +28,19 @@
[% filtered_desc = bug.short_desc FILTER html %]
[% PROCESS global/header.html.tmpl
title = "Changes made to $terms.bug $bug.bug_id"
header = "Activity log for $terms.bug $bug.bug_id: $filtered_desc"
title = "Changes made to $terms.bug " _ bug.bug_id
header = "Activity log for $terms.bug " _ bug.bug_id _ ": " _ filtered_desc
%]
<p>
[% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug) FILTER none %]
[% "Back to $terms.bug " _ bug.bug_id FILTER bug_link(bug) FILTER none %]
</p>
[% PROCESS bug/activity/table.html.tmpl %]
[% IF operations.size > 0 %]
<p>
[% "Back to $terms.bug $bug.bug_id" FILTER bug_link(bug) FILTER none %]
[% "Back to $terms.bug " _ bug.bug_id FILTER bug_link(bug) FILTER none %]
</p>
[% END %]

View File

@ -2,7 +2,7 @@
# License: Dual-license MPL 1.1+ or GPL 3.0+
# Author(s): Vitaliy Filippov %]
[% INCLUDE global/header.html.tmpl title = "$terms.Bug $bug.id - Check access" %]
[% INCLUDE global/header.html.tmpl title = terms.Bug _ " " _ bug.id _ " - Check access" %]
<h2>[% IF user_list.size > 0 %][% user_list.size %] [%+ user_list.size > 1 ? 'users' : 'user' %][% ELSE %]Everyone[% END %] can see <a href="show_bug.cgi?id=[% bug.id %]">[% terms.Bug _ " " _ bug.id %]</a></h2>

View File

@ -192,8 +192,7 @@ function PutDescription() {
height: 5em; overflow: auto;">
<script type="text/javascript">
if ((document.getElementById) && (document.body.innerHTML)) {
document.write("\
Select a component to see its description here.");
document.write("[% L('Select a component to see its description here.') %]");
}
</script>
</div>

View File

@ -36,8 +36,8 @@
[% IF NOT multiple_bugs AND NOT doall %]
[% filtered_desc = short_desc FILTER html %]
[% title = "$title for $terms.bug $bug_id"
header = "$header for $terms.bug <a href=\"show_bug.cgi?id=$bug_id\">$bug_id</a>"
[% title = title _ " for $terms.bug " _ bug_id
header = header _ " for $terms.bug " _ "<a href=\"show_bug.cgi?id=$bug_id\">$bug_id</a>"
subheader = filtered_desc
%]
[% END %]

View File

@ -23,9 +23,8 @@
[% filtered_desc = blocked_tree.$bugid.short_desc FILTER html %]
[% PROCESS global/header.html.tmpl
title = "Dependency tree for $terms.Bug $bugid"
header = "Dependency tree for
<a href=\"show_bug.cgi?id=$bugid\">$terms.Bug $bugid</a>"
title = "Dependency tree for $terms.Bug " _ bugid
header = "Dependency tree for $terms.Bug " _ "<a href=\"show_bug.cgi?id=$bugid\">$bugid</a>"
javascript_urls = ["js/expanding-tree.js"]
style_urls = ["skins/standard/dependency-tree.css"]
subheader = filtered_desc

View File

@ -27,7 +27,7 @@ function addfield()
opt = sel.options[sel.selectedIndex];
tr = document.createElement('TR');
td = document.createElement('TD');
td.innerHTML = opt.text + ' for all bugs:&nbsp;';
td.innerHTML = opt.text + [% L(' for all bugs:') %] + '&nbsp;';
tr.appendChild(td);
td = document.createElement('TD');
td.innerHTML = '<input type="text" name="f_' + opt.value + '" value="" />';
@ -106,7 +106,7 @@ function checkColumns()
break;
}
if (!chk)
alert('Для импорта не выбрано ни одного бага из списка!');
alert('[% L('No bugs selected for importing!') %]');
return chk;
}
</script>

View File

@ -62,7 +62,7 @@
No, do not add the reporter to CC list on [% orig_bug FILTER none %]
</p>
<p>
[% "Throw away my changes, and revisit $terms.bug $duplicate_bug_id"
[% "Throw away my changes, and revisit $terms.bug " _ duplicate_bug_id
FILTER bug_link(duplicate_bug_id) FILTER none %]
</p>
<p>

View File

@ -94,7 +94,7 @@ You have the following choices:
[% END %]
<li>
Throw away my changes, and
[%+ "revisit $terms.bug $bug.id" FILTER bug_link(bug) FILTER none %]
[%+ "revisit $terms.bug " _ bug.id FILTER bug_link(bug) FILTER none %]
</li>
</ul>

View File

@ -43,14 +43,14 @@
'?' => "requested"
};
titles = {
'bug' => "Changes submitted for $link",
'dupe' => "Duplicate notation added to $link",
'dep' => "Checking for dependency changes on $link",
'votes' => "$Link confirmed by number of votes",
'created' => "$Link has been added to the database",
'move' => "$Link has been moved to another database",
'flag' => "Flag $new_flag.name " _ statuses.${new_flag.status},
'votes-removed' => "Votes removed from $Link in accordance with new product settings",
'bug' => "Changes submitted for " _ link,
'dupe' => "Duplicate notation added to " _ link,
'dep' => "Checking for dependency changes on " _ link,
'votes' => Link _ " confirmed by number of votes",
'created' => Link _ " has been added to the database",
'move' => Link _ " has been moved to another database",
'flag' => "Flag " _ new_flag.name _ " " _ statuses.${new_flag.status},
'votes-removed' => "Votes removed from " _ Link _ " in accordance with new product settings",
}
%]

View File

@ -21,7 +21,7 @@
[% header = title _ header _ " (and $terms.bugs blocking it)" %]
[% title = title _ "$terms.Bug $ids.0" %]
[% ELSE %]
[% title = title _ "($ids.size $terms.bugs selected)" %]
[% title = title _ "(" _ ids.size _ " $terms.bugs selected)" %]
[% header = title %]
[% END %]

View File

@ -115,7 +115,7 @@ var field = [
[%-# These values are meaningful for custom fields only. %]
[% IF x.custom %]
type: '[% x.type FILTER js %]',
type_desc: '[% field_types.${x.type} FILTER js %]',
type_desc: '[% lc_messages.field_types.${constants.FIELD_TYPE_NAMES.${x.type}} FILTER js %]',
enter_bug: '[% x.enter_bug FILTER js %]',
[% END %]
},

View File

@ -257,7 +257,7 @@
[%-# These values are meaningful for custom fields only. %]
[% IF item.custom %]
<bz:type>[% item.type FILTER html %]</bz:type>
<bz:type_desc>[% field_types.${item.type} FILTER html %]</bz:type_desc>
<bz:type_desc>[% lc_messages.field_types.${constants.FIELD_TYPE_NAMES.${item.type}} FILTER html %]</bz:type_desc>
<bz:enter_bug>[% item.enter_bug FILTER html %]</bz:enter_bug>
[% END %]
</bz:field>

View File

@ -20,7 +20,7 @@
From: [% Param('mailfrom') %]
To: [% Param('maintainer') %]
Subject: [[% terms.Bugzilla %]] Account Lock-Out: [% locked_user.login %] ([% attempts.0.ip_addr %])
Subject: [% "[$terms.Bugzilla] Account Lock-Out:" %] [% locked_user.login %] ([% attempts.0.ip_addr %])
X-Bugzilla-Type: admin
The IP address [% attempts.0.ip_addr %] failed too many login attempts (

View File

@ -112,8 +112,8 @@ Bug [% d.dep %] summary: [% d.short_desc %]
[% END %]
-- [%# Protect the trailing space of the signature marker %]
Configure [% terms.bug %]mail: [% urlbase %]userprefs.cgi?tab=email
------- You are receiving this mail because: -------
Configure [% terms.bugmail %]: [% urlbase %]userprefs.cgi?tab=email
------- [%+ "You are receiving this mail because:" %] -------
[% FOREACH relationship = reasons %]
[% SWITCH relationship %]
[% CASE constants.REL_ASSIGNEE %]
@ -266,7 +266,7 @@ body { font-family: Segoe UI, sans-serif; }
[% END %]
<p style="font-size: 12px; font-style: italic; margin-bottom: 0">
<a href="[% urlbase %]userprefs.cgi?tab=email">Configure [% terms.bug %]mail</a><br />
<a href="[% urlbase %]userprefs.cgi?tab=email">Configure [% terms.bugmail %]</a><br />
You are receiving this mail because:
</p>
<ul style="font-size: 12px; font-style: italic; margin: 0; padding-left: 2em"><li>

View File

@ -20,7 +20,7 @@
From: [% Param('mailfrom') %]
To: [% addressee %]
Subject: [[% terms.Bugzilla %]] Sanity Check Results
Subject: [% "[$terms.Bugzilla] Sanity Check Results" %]
X-Bugzilla-Type: sanitycheck
[%+ urlbase %]sanitycheck.cgi

View File

@ -21,8 +21,7 @@
Content-Type: text/plain
From: [% Param('mailfrom') %]
To: [% user.email %]
Subject: [[% terms.Bugzilla %]] Your account [% user.login -%]
is being impersonated
Subject: [% "[$terms.Bugzilla] Your account " _ user.login _ " is being impersonated" %]
X-Bugzilla-Type: admin
[%+ sudoer.identity %] has used the 'sudo' feature to access

View File

@ -20,7 +20,7 @@
From: [% Param('mailfrom') %]
To: [% to %]
Subject: [% terms.Bug %] [%+ bugid %] Some or all of your votes have been removed.
Subject: [% terms.Bug _ ' ' _ bugid _ " - Some or all of your votes have been removed." %]
X-Bugzilla-Type: voteremoved
Some or all of your votes have been removed from [% terms.bug %] [%+ bugid %].

View File

@ -20,7 +20,7 @@
From: [% Param("mailfrom") %]
To: [% email %][% Param("emailsuffix") %]
Subject: Your [% terms.Bugzilla %] [%+ terms.bug %] list needs attention.
Subject: [% "Your $terms.Bugzilla $terms.bug list needs attention." %]
X-Bugzilla-Type: whine
[This e-mail has been automatically generated.]

View File

@ -217,8 +217,6 @@
[% END %]
<script src="[% 'js/util.js' | ts_url %]" type="text/javascript"></script>
<script src="[% 'js/global.js' | ts_url %]" type="text/javascript"></script>
<script src="[% 'js/hinter.js' | ts_url %]" type="text/javascript"></script>
<script type="text/javascript">
<!--
[%# The language selector needs javascript to set its cookie,
@ -226,7 +224,8 @@
# If the browser can run javascript, it will then "unhide"
# the language selector using the following code.
#%]
function unhide_language_selector() {
function unhide_language_selector()
{
removeClass('lang_links_container', 'bz_default_hidden');
}
addListener(window, 'load', unhide_language_selector);
@ -238,17 +237,16 @@
var BUGZILLA = {
param: {
cookiepath: '[% Param('cookiepath') FILTER js %]'
},
string: {
attach_desc_required: 'You must enter a Description for this attachment.'
}
};
[% IF javascript %]
[% javascript %]
[% javascript %]
[% END %]
// -->
</script>
<script src="[% ('i18n/' _ LANGUAGE _ '/messages.js') | ts_url %]" type="text/javascript"></script>
<script src="[% 'js/global.js' | ts_url %]" type="text/javascript"></script>
<script src="[% 'js/hinter.js' | ts_url %]" type="text/javascript"></script>
[% IF javascript_urls %]
[% FOREACH javascript_url = javascript_urls %]
<script src="[% javascript_url | ts_url | html %]" type="text/javascript"></script>
@ -304,17 +302,17 @@
</tr>
</table>
[% IF Bugzilla.languages.size > 1 %]
[% IF Bugzilla.i18n.supported_languages.size > 1 %]
<table id="lang_links_container" cellpadding="0" cellspacing="0"
class="bz_default_hidden"><tr><td>
<ul class="links">
[% FOREACH lang = Bugzilla.languages.sort %]
[% FOREACH lang = Bugzilla.i18n.supported_languages.sort %]
<li>[% IF NOT loop.first %]<span class="separator"> | </span>[% END %]
[% IF lang == current_language %]
[% IF lang == LANGUAGE %]
<span class="lang_current">[% lang FILTER html FILTER upper %]</span>
[% ELSE %]
<a href="#" onclick="set_language('[% lang FILTER none %]');">
[%- lang FILTER html FILTER upper %]</a>
[%- lang FILTER html FILTER upper %]</a>
[% END %]
</li>
[% END %]

View File

@ -47,7 +47,7 @@
[% END %]
[% BLOCK msg_changed_attachment %]
[% SET title = "Changes Submitted to Attachment $id of $terms.Bug $bug_id" %]
[% SET title = "Changes Submitted to Attachment " _ id _ " of $terms.Bug " _ bug_id %]
<dl>
<dt>Changes to <a href="attachment.cgi?id=[% id %]&amp;action=edit">attachment [% id %]</a>
of [% "$terms.bug $bug_id" FILTER bug_link(bug_id) FILTER none %] submitted
@ -177,7 +177,7 @@
[% IF changed_fields.size
+ groups_added_to.size + groups_removed_from.size
+ groups_granted_rights_to_bless.size + groups_denied_rights_to_bless.size %]
[% title = "User $loginold updated" %]
[% title = "User " _ loginold _ " updated" %]
The following changes have been made to the user account
[%+ loginold | html %]:
<ul>
@ -230,14 +230,14 @@
[% END %]
</ul>
[% ELSE %]
[% title = "User $otheruser.login not changed" %]
[% title = "User " _ otheruser.login _ " not changed" %]
You didn't request any changes to the user's account
[%+ otheruser.login | html %].
[% END %]
[% END %]
[% BLOCK msg_account_deleted %]
[% title = "User $otheruser.login deleted" %]
[% title = "User " _ otheruser.login _ " deleted" %]
The user account [% otheruser.login | html %] has been deleted
successfully.
[% END %]
@ -498,18 +498,6 @@
[%+ flag_creation_error FILTER none %]
[% END %]
[% BLOCK msg_get_field_desc %]
[% field_descs.$field_name | html %]
[% END %]
[% BLOCK msg_get_resolution %]
[% resolution | html %]
[% END %]
[% BLOCK msg_get_status %]
[% status | html %]
[% END %]
[% BLOCK msg_group_created %]
[% title = "New Group Created" %]
The group <em>[% group.name | html %]</em> has been created.

View File

@ -543,7 +543,7 @@
[% BLOCK error_useless_customfield_type %]
[% title = "Can't create field of a useless type" %]
It is impossible to create "[% lc_messages.field_types.$type %]" fields,
It is impossible to create "[% lc_messages.field_types.${constants.FIELD_TYPE_NAMES.$type} %]" fields,
because functionality of this type is hard-coded to a single builtin field.
[% END %]
@ -574,16 +574,16 @@
[% BLOCK error_direct_field_needed_for_reverse %]
[% title = "Invalid Direct Field selected" %]
Each field of type "[% lc_messages.field_types.${constants.FIELD_TYPE_BUG_ID_REV} %]" must correspond
to one, and only one field of type "[% lc_messages.field_types.${constants.FIELD_TYPE_BUG_ID} %]" and
Each field of type "[% lc_messages.field_types.BUG_ID_REV %]" must correspond
to one, and only one field of type "[% lc_messages.field_types.BUG_ID %]" and
represent its "reverse relation". For example, it may be "Internal Bugs" for the corresponding
"External Bug" field.
[% END %]
[% BLOCK error_duplicate_reverse_field %]
[% title = "Duplicate Reverse Field" %]
It is prohibited to create more than one field of type "[% lc_messages.field_types.${constants.FIELD_TYPE_BUG_ID_REV} %]"
corresponding to a single "[% lc_messages.field_types.${constants.FIELD_TYPE_BUG_ID} %]" type field.
It is prohibited to create more than one field of type "[% lc_messages.field_types.BUG_ID_REV %]"
corresponding to a single "[% lc_messages.field_types.BUG_ID %]" type field.
[% END %]
[% BLOCK error_field_type_mismatch %]

View File

@ -34,6 +34,6 @@
[%-# FIXME: remove hardcoded i18n message, also from js/field.js::userAutocomplete() %]
new SimpleAutocomplete("[% id | js %]",
function(h) { userAutocomplete(h, [% custom_userlist ? json(custom_userlist) : "null" %], [% Param('usemenuforusers') ? 1 : 0 %]); },
{ emptyText: 'No users found'[% ', multipleDelimiter: ","' IF multiple %] });
{ emptyText: L('No users found')[% ', multipleDelimiter: ","' IF multiple %] });
//-->
</script>

View File

@ -49,11 +49,11 @@ function addSidebar() {
sidebarname="[% terms.Bugzilla %] "+sidebarname;
window.sidebar.addPanel (sidebarname, "[% urlbase FILTER html %]sidebar.cgi", "");
}
var quicksearch_message = "Enter [% terms.abug %] # or some search terms";
var quicksearch_message = '[% L('Enter $terms.abug # or some search terms') %]';
function checkQuicksearch( form ) {
if (form.quicksearch.value == '' || form.quicksearch.value == quicksearch_message ) {
alert('Please enter one or more search terms first.');
alert('[% L('Please enter one or more search terms first.') %]');
return false;
}
return true;

View File

@ -38,8 +38,8 @@ function SetCheckboxes(value)
item.checked = value;
}
}
document.write(' <input type="button" name="uncheck_all" value="Uncheck All" onclick="SetCheckboxes(false);" />');
document.write(' <input type="button" name="check_all" value="Check All" onclick="SetCheckboxes(true);" />');
document.write(' <input type="button" name="uncheck_all" value="[% L('Uncheck All') %]" onclick="SetCheckboxes(false);" />');
document.write(' <input type="button" name="check_all" value="[% L('Check All') %]" onclick="SetCheckboxes(true);" />');
//-->
</script>

View File

@ -144,9 +144,9 @@
}
}
document.write(' <input type="button" name="uncheck_all" '
+'value="Uncheck All" onclick="SetCheckboxes(false);" />');
+'value="[% L('Uncheck All') %]" onclick="SetCheckboxes(false);" />');
document.write(' <input type="button" name="check_all" '
+'value="Check All" onclick="SetCheckboxes(true);" />');
+'value="[% L('Check All') %]" onclick="SetCheckboxes(true);" />');
//--></script>
<input type="submit" id="update" value="Save Changes" />

View File

@ -150,7 +150,7 @@
addListener(window, 'load', function() {
filter_[% id %] = new SimpleAutocomplete("usr_filter_[% id %]",
function(h) { fieldBuglistAutocomplete(h, "[% id %]", emptyOptions[% id %]); },
{ emptyText: 'No keywords found', multipleDelimiter: "," }
{ emptyText: '[% L('No values found') %]', multipleDelimiter: "," }
);
});
//-->

View File

@ -42,7 +42,7 @@
{ name => "id", description => "$terms.Bug #" },
{ name => "count", description => "Dupe<br />Count" },
{ name => "delta",
description => "Change in last<br />$changedsince day(s)" },
description => "Change in last<br />" _ changedsince _ " day(s)" },
{ name => "component", description => field_descs.component },
{ name => "bug_severity", description => field_descs.bug_severity },
] %]

View File

@ -47,13 +47,13 @@ var queryform = "queryform"
[%# The decent help requires Javascript %]
<script type="text/javascript"> <!--
[% IF NOT Bugzilla.cgi.param("help") %]
document.write("<p><a href='query.cgi?help=1&amp;format=advanced'>Give me some help<\/a> (reloads page).<\/p>");
document.write("<p><a href='query.cgi?help=1&amp;format=advanced'>[% L('Give me some help') %]<\/a> [% L('(reloads page).') %]<\/p>");
[% ELSE %]
[% PROCESS "search/search-help.html.tmpl" %]
if (generateHelp())
document.write("<p>For help, mouse over the page elements.<\/p>");
document.write("<p>[% L('For help, mouse over the page elements.') %]<\/p>");
else
document.write("<p>Help initialization failed, no help available.<\/p>");
document.write("<p>[% L('Help initialization failed, no help available.') %]<\/p>");
[% END %]
// -->
</script>

View File

@ -167,7 +167,7 @@ function divide_hours_click()
var sum = bzParseTime(document.getElementById('divide_hours').value);
if (!sum || sum != sum)
{
alert('Нечего распределять! Введите число, время в формате HH:MM, или в днях 1d, 2d, 3d и т.п.');
alert('[% L('Нечего распределять! Введите число, время в формате HH:MM, или в днях 1d, 2d, 3d и т.п.') %]');
return;
}
if (!period_times_sorted)